From 1ce78b441ff904381bf861e55c94b5b443080e1b Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Tue, 16 May 2023 23:01:47 +0800 Subject: [PATCH 01/50] basic conversation agent done. --- agentverse/__init__.py | 12 +- agentverse/agents/__init__.py | 3 +- agentverse/agents/agent.py | 62 +++++---- agentverse/agents/base.py | 78 +++++++++++ agentverse/agents/conversation_agent.py | 74 ++++++++++ agentverse/agentverse.py | 22 +-- agentverse/environments/__init__.py | 2 + agentverse/environments/base.py | 57 ++------ agentverse/environments/basic.py | 94 +++++++++++++ .../environments/rules/describer/__init__.py | 2 + .../environments/rules/describer/base.py | 5 +- .../environments/rules/describer/basic.py | 16 +++ .../environments/rules/selector/__init__.py | 4 +- .../environments/rules/selector/base.py | 11 +- .../environments/rules/selector/basic.py | 27 ++++ .../environments/rules/updater/__init__.py | 4 +- agentverse/environments/rules/updater/base.py | 52 ++----- .../environments/rules/updater/basic.py | 69 ++++++++++ agentverse/initialization.py | 129 +++++++++--------- agentverse/llms/__init__.py | 2 + agentverse/llms/base.py | 30 ++++ agentverse/llms/openai.py | 115 ++++++++++++++++ agentverse/memory.py | 87 ------------ agentverse/memory/__init__.py | 2 + agentverse/memory/base.py | 18 +++ agentverse/memory/chat_history_message.py | 22 +++ agentverse/message.py | 12 +- agentverse/parser.py | 14 ++ agentverse/tasks/__init__.py | 5 +- .../nlp_classroom_3players_nolc/config.yaml | 63 +++++++++ .../output_parser.py | 31 +++++ 31 files changed, 833 insertions(+), 291 deletions(-) create mode 100644 agentverse/agents/base.py create mode 100644 agentverse/agents/conversation_agent.py create mode 100644 agentverse/environments/basic.py create mode 100644 agentverse/environments/rules/describer/basic.py create mode 100644 agentverse/environments/rules/selector/basic.py create mode 100644 agentverse/environments/rules/updater/basic.py create mode 100644 agentverse/llms/__init__.py create mode 100644 agentverse/llms/base.py create mode 100644 agentverse/llms/openai.py delete mode 100644 agentverse/memory.py create mode 100644 agentverse/memory/__init__.py create mode 100644 agentverse/memory/base.py create mode 100644 agentverse/memory/chat_history_message.py create mode 100644 agentverse/tasks/nlp_classroom_3players_nolc/config.yaml create mode 100644 agentverse/tasks/nlp_classroom_3players_nolc/output_parser.py diff --git a/agentverse/__init__.py b/agentverse/__init__.py index 10de7cad0..89277bd64 100644 --- a/agentverse/__init__.py +++ b/agentverse/__init__.py @@ -1,5 +1,6 @@ from .tasks import * -from .agents import Agent + +# from .agents import Agent from .environments import env_registry from .environments.rules import Rule from .environments.rules.order import order_registry @@ -8,4 +9,11 @@ from .environments.rules.updater import updater_registry from .environments.rules.visibility import visibility_registry from .agentverse import AgentVerse -from .initialization import prepare_task_config, load_agent, load_environment, load_tools, load_llm, load_memory \ No newline at end of file +from .initialization import ( + prepare_task_config, + load_agent, + load_environment, + load_tools, + load_llm, + load_memory, +) diff --git a/agentverse/agents/__init__.py b/agentverse/agents/__init__.py index d2361b7a3..1192972cf 100644 --- a/agentverse/agents/__init__.py +++ b/agentverse/agents/__init__.py @@ -1 +1,2 @@ -from .agent import Agent +# from .agent import Agent +from .conversation_agent import BaseAgent diff --git a/agentverse/agents/agent.py b/agentverse/agents/agent.py index c588b3a0c..f68210063 100644 --- a/agentverse/agents/agent.py +++ b/agentverse/agents/agent.py @@ -14,11 +14,16 @@ from langchain.memory import ChatMessageHistory from langchain.prompts import BasePromptTemplate, PromptTemplate from langchain.prompts.chat import ChatPromptTemplate, MessagesPlaceholder -from langchain.schema import (AgentAction, AIMessage, - BaseMessage, HumanMessage, SystemMessage) +from langchain.schema import ( + AgentAction, + AIMessage, + BaseMessage, + HumanMessage, + SystemMessage, +) from langchain.tools.base import BaseTool -from agentverse.memory import SummaryMemory +# from agentverse.memory.memory import SummaryMemory from agentverse.message import Message from agentverse.parser import OutputParseError @@ -33,7 +38,9 @@ if openai.proxy is None: openai.proxy = os.environ.get("HTTP_PROXY") if openai.api_key is None: - logging.warning("OpenAI API key is not set. Please set the environment variable OPENAI_API_KEY") + logging.warning( + "OpenAI API key is not set. Please set the environment variable OPENAI_API_KEY" + ) is_openai_available = False else: is_openai_available = True @@ -41,7 +48,7 @@ class Agent(langchainAgent): """Action unit in the environment. - + Args: name: Name of the agent role_description: Description of the role of the agent @@ -51,6 +58,7 @@ class Agent(langchainAgent): tools: List of tools that the agent can use receiver: List of receivers of the messages generated by this agent """ + name: str role_description: str memory: ChatMessageHistory @@ -84,14 +92,14 @@ def get_receiver(self) -> List[str]: def set_receiver(self, receiver: List[str]) -> None: self.receiver = receiver - def step(self, env_description: str="") -> Message: + def step(self, env_description: str = "") -> Message: """Generate the next message""" executor = AgentExecutor.from_agent_and_tools( agent=self, tools=self.tools, verbose=True, return_intermediate_steps=True, - output_parser=self.output_parser + output_parser=self.output_parser, ) input_arguments = {"agent_name": self.name} if isinstance(self.llm_chain.llm, BaseChatModel): @@ -103,8 +111,8 @@ def step(self, env_description: str="") -> Message: else: raise ValueError(f"Unsupported LLM type: {self.llm_chain.llm}") - input_arguments['chat_history'] = chat_history - input_arguments['env_description'] = env_description + input_arguments["chat_history"] = chat_history + input_arguments["env_description"] = env_description response = None for i in range(self.max_retry): @@ -119,22 +127,22 @@ def step(self, env_description: str="") -> Message: raise ValueError(f"{self.name} failed to generate valid response.") message = Message( - content=response['output'], + content=response["output"], role="assistant", sender=self.name, receiver=self.get_receiver(), - tool_response=response['intermediate_steps'] + tool_response=response["intermediate_steps"], ) return message - async def astep(self, env_description: str="") -> Message: + async def astep(self, env_description: str = "") -> Message: """Asynchronous version of step""" executor = AgentExecutor.from_agent_and_tools( agent=self, tools=self.tools, verbose=True, return_intermediate_steps=True, - output_parser=self.output_parser + output_parser=self.output_parser, ) input_arguments = {"agent_name": self.name} if isinstance(self.llm_chain.llm, BaseChatModel): @@ -146,14 +154,14 @@ async def astep(self, env_description: str="") -> Message: else: raise ValueError(f"Unsupported LLM type: {self.llm_chain.llm}") if self.tool_memory is not None: - input_arguments['tool_memory'] = self.tool_memory.buffer + input_arguments["tool_memory"] = self.tool_memory.buffer - input_arguments['chat_history'] = chat_history - input_arguments['env_description'] = env_description + input_arguments["chat_history"] = chat_history + input_arguments["env_description"] = env_description if self.tool_memory is not None: - input_arguments['tool_memory'] = self.tool_memory.buffer + input_arguments["tool_memory"] = self.tool_memory.buffer else: - input_arguments['tool_memory'] = "" + input_arguments["tool_memory"] = "" response = None for i in range(self.max_retry): @@ -168,11 +176,11 @@ async def astep(self, env_description: str="") -> Message: logging.error(f"{self.name} failed to generate valid response.") message = Message( - content="" if response is None else response['output'], + content="" if response is None else response["output"], role="assistant", sender=self.name, receiver=self.get_receiver(), - tool_response=[] if response is None else response['intermediate_steps'] + tool_response=[] if response is None else response["intermediate_steps"], ) return message @@ -206,11 +214,13 @@ def create_prompt( SystemMessage(content=prefix_prompt), SystemMessage(content=format_prompt), SystemMessage(content=suffix_prompt), - MessagesPlaceholder(variable_name="chat_history") + MessagesPlaceholder(variable_name="chat_history"), ] if len(tools) > 0: messages.append(MessagesPlaceholder(variable_name="agent_scratchpad")) - return ChatPromptTemplate(input_variables=input_variables, messages=messages) + return ChatPromptTemplate( + input_variables=input_variables, messages=messages + ) elif isinstance(llm, BaseLLM): template = f"{prefix_prompt}\n\n{format_prompt}\n\n{suffix_prompt}" return PromptTemplate(input_variables=input_variables, template=template) @@ -226,7 +236,9 @@ def _construct_scratchpad( if isinstance(self.llm_chain.llm, BaseChatModel): thoughts.append(AIMessage(content=action.log.strip())) human_message = HumanMessage( - content="Tool response:\n{observation}".format(observation=observation) + content="Tool response:\n{observation}".format( + observation=observation + ) ) thoughts.append(human_message) elif isinstance(self.llm_chain.llm, BaseLLM): @@ -245,7 +257,7 @@ def from_llm_and_tools( format_prompt: str = "", suffix_prompt: str = "", input_variables: Optional[List[str]] = None, - **kwargs + **kwargs, ) -> langchainAgent: """Construct an agent from an LLM and tools.""" cls._validate_tools(tools) @@ -272,4 +284,4 @@ def from_llm_and_tools( ) def reset(self): - self.memory.clear() \ No newline at end of file + self.memory.clear() diff --git a/agentverse/agents/base.py b/agentverse/agents/base.py new file mode 100644 index 000000000..f4940390e --- /dev/null +++ b/agentverse/agents/base.py @@ -0,0 +1,78 @@ +import logging +from abc import abstractmethod +from typing import List, Set, Union + +from pydantic import BaseModel, Field + +from agentverse.llms import BaseLLM +from agentverse.memory import BaseMemory +from agentverse.message import Message +from agentverse.parser import OutputParser + + +class BaseAgent(BaseModel): + name: str + llm: BaseLLM + output_parser: OutputParser + prompt_template: str + role_description: str = Field(default="") + memory: BaseMemory = Field(default_factory=BaseMemory) + max_retry: int = Field(default=3) + receiver: Set[str] = Field(default=set({"all"})) + async_mode: bool = Field(default=True) + + @abstractmethod + def step(self, env_description: str = "") -> Message: + """Get one step response""" + pass + + @abstractmethod + def astep(self, env_description: str = "") -> Message: + """Asynchronous version of step""" + pass + + @abstractmethod + def reset(self) -> None: + """Reset the agent""" + pass + + @abstractmethod + def add_message_to_memory(self, message: Message) -> None: + """Add a message to the memory""" + pass + + def get_receiver(self) -> List[str]: + return self.receiver + + def set_receiver(self, receiver: Union[Set[str], str]) -> None: + if isinstance(receiver, str): + self.receiver = set({receiver}) + elif isinstance(receiver, set): + self.receiver = receiver + else: + raise ValueError( + "input argument `receiver` must be a string or a set of string" + ) + + def add_receiver(self, receiver: Union[Set[str], str]) -> None: + if isinstance(receiver, str): + self.receiver.add(receiver) + elif isinstance(receiver, set): + self.receiver = self.receiver.union(receiver) + else: + raise ValueError( + "input argument `receiver` must be a string or a set of string" + ) + + def remove_receiver(self, receiver: Union[Set[str], str]) -> None: + if isinstance(receiver, str): + try: + self.receiver.remove(receiver) + except KeyError as e: + logging.warning(f"Receiver {receiver} not found.") + elif isinstance(receiver, set): + self.receiver = self.receiver.difference(receiver) + else: + raise ValueError( + "input argument `receiver` must be a string or a set of string" + ) diff --git a/agentverse/agents/conversation_agent.py b/agentverse/agents/conversation_agent.py new file mode 100644 index 000000000..0c064ae09 --- /dev/null +++ b/agentverse/agents/conversation_agent.py @@ -0,0 +1,74 @@ +import logging +from string import Template +from typing import List, NamedTuple, Optional, Union + +from agentverse.llms import BaseChatModel, BaseCompletionModel, BaseLLM +from agentverse.memory import BaseMemory +from agentverse.message import Message +from agentverse.parser import OutputParseError, OutputParser +from .base import BaseAgent + + +class AgentAction(NamedTuple): + """Agent's action to take.""" + + tool: str + tool_input: Union[str, dict] + log: str + + +class AgentFinish(NamedTuple): + """Agent's return value.""" + + return_values: dict + log: str + + +class ConversationAgent(BaseAgent): + def step(self, env_description: str = "") -> Message: + pass + + async def astep(self, env_description: str = "") -> Message: + """Asynchronous version of step""" + prompt = self._fill_prompt_template(env_description) + + parsed_response = None + for i in range(self.max_retry): + try: + response = await self.llm.agenerate_response(prompt) + parsed_response = self.output_parser.parse(response) + break + except Exception as e: + logging.error(e) + logging.warning("Retrying...") + continue + + if parsed_response is None: + logging.error(f"{self.name} failed to generate valid response.") + + message = Message( + content="" + if parsed_response is None + else parsed_response.return_values["output"], + sender=self.name, + receiver=self.get_receiver(), + ) + return message + + def _fill_prompt_template(self, env_description: str = "") -> str: + input_arguments = { + "agent_name": self.name, + "env_description": env_description, + "role_description": self.role_description, + } + chat_history = self.memory.to_string(add_sender_prefix=True) + input_arguments["chat_history"] = chat_history + return Template(self.prompt_template).safe_substitute(input_arguments) + + def add_message_to_memory(self, message: Message) -> None: + self.memory.add_message(message) + + def reset(self) -> None: + """Reset the agent""" + self.memory.reset() + # TODO: reset receiver diff --git a/agentverse/agentverse.py b/agentverse/agentverse.py index 99a7b0133..0e8d90b1a 100644 --- a/agentverse/agentverse.py +++ b/agentverse/agentverse.py @@ -2,19 +2,23 @@ import logging from typing import List -from agentverse.agents import Agent +# from agentverse.agents import Agent +from agentverse.agents.conversation_agent import BaseAgent from agentverse.environments import BaseEnvironment -from agentverse.initialization import (load_agent, load_environment, - prepare_task_config) +from agentverse.initialization import load_agent, load_environment, prepare_task_config -logging.basicConfig(format='%(asctime)s - %(levelname)s - %(name)s - %(message)s', datefmt='%m/%d/%Y %H:%M:%S', level=logging.INFO) +logging.basicConfig( + format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", + datefmt="%m/%d/%Y %H:%M:%S", + level=logging.INFO, +) openai_logger = logging.getLogger("openai") openai_logger.setLevel(logging.WARNING) -class AgentVerse(): - def __init__(self, agents: List[Agent], environment: BaseEnvironment): +class AgentVerse: + def __init__(self, agents: List[BaseAgent], environment: BaseEnvironment): self.agents = agents self.environment = environment @@ -29,13 +33,13 @@ def from_task(cls, task: str): # Build the agents agents = [] - for agent_configs in task_config['agents']: + for agent_configs in task_config["agents"]: agent = load_agent(agent_configs) agents.append(agent) # Build the environment - env_config = task_config['environment'] - env_config['agents'] = agents + env_config = task_config["environment"] + env_config["agents"] = agents environment = load_environment(env_config) return cls(agents, environment) diff --git a/agentverse/environments/__init__.py b/agentverse/environments/__init__.py index 4dc494ac7..130fbed1b 100644 --- a/agentverse/environments/__init__.py +++ b/agentverse/environments/__init__.py @@ -1,5 +1,7 @@ from typing import Dict from agentverse.registry import Registry + env_registry = Registry(name="EnvironmentRegistry") from .base import BaseEnvironment +from .basic import BasicEnvironment diff --git a/agentverse/environments/base.py b/agentverse/environments/base.py index 334eaf147..27c61b530 100644 --- a/agentverse/environments/base.py +++ b/agentverse/environments/base.py @@ -1,20 +1,21 @@ import asyncio import logging from typing import Any, Dict, List +from abc import abstractmethod from pydantic import BaseModel -from agentverse.agents.agent import Agent +# from agentverse.agents.agent import Agent +from agentverse.agents.conversation_agent import BaseAgent from agentverse.environments.rules.base import Rule from agentverse.message import Message from . import env_registry as EnvironmentRegistry -@EnvironmentRegistry.register("base") class BaseEnvironment(BaseModel): """ - A basic environment implementing the logic of conversation. + Base class for environment. Args: agents: List of agents @@ -24,61 +25,23 @@ class BaseEnvironment(BaseModel): last_messages: Messages from last turn rule_params: Variables set by the rule """ - agents: List[Agent] + + agents: List[BaseAgent] rule: Rule max_turns: int = 10 cnt_turn: int = 0 last_messages: List[Message] = [] rule_params: Dict = {} - def __init__(self, rule, **kwargs): - rule_config = rule - order_config = rule_config.get('order', {'type': 'sequential'}) - visibility_config = rule_config.get('visibility', {'type': 'all'}) - selector_config = rule_config.get('selector', {'type': 'base'}) - updater_config = rule_config.get('updater', {'type': 'base'}) - describer_config = rule_config.get('describer', {'type': 'base'}) - rule = Rule(order_config, visibility_config, selector_config, updater_config, describer_config) - super().__init__(rule=rule, **kwargs) - + @abstractmethod async def step(self) -> List[Message]: """Run one step of the environment""" + pass - # Get the next agent index - agent_ids = self.rule.get_next_agent_idx(self) - - # Generate current environment description - env_descriptions = self.rule.get_env_description(self) - - # Generate the next message - messages = await asyncio.gather(*[self.agents[i].astep(env_descriptions[i]) for i in agent_ids]) - - # Some rules will select certain messages from all the messages - selected_messages = self.rule.select_message(self, messages) - self.last_messages = selected_messages - self.print_messages(selected_messages) - - # Update the memory of the agents - self.rule.update_memory(self) - - # Update the set of visible agents for each agent - self.rule.update_visible_agents(self) - - self.cnt_turn += 1 - - return selected_messages - - def print_messages(self, messages: List[Message]) -> None: - for message in messages: - if message is not None: - logging.info(f"{message.sender}: {message.content}") - + @abstractmethod def reset(self) -> None: """Reset the environment""" - self.cnt_turn = 0 - self.rule.reset() - for agent in self.agents: - agent.reset() + pass def is_done(self) -> bool: """Check if the environment is done""" diff --git a/agentverse/environments/basic.py b/agentverse/environments/basic.py new file mode 100644 index 000000000..3dacbbf42 --- /dev/null +++ b/agentverse/environments/basic.py @@ -0,0 +1,94 @@ +import asyncio +import logging +from typing import Any, Dict, List + +# from agentverse.agents.agent import Agent +from agentverse.agents.conversation_agent import BaseAgent +from agentverse.environments.rules.base import Rule +from agentverse.message import Message + +from . import env_registry as EnvironmentRegistry +from .base import BaseEnvironment + + +@EnvironmentRegistry.register("basic") +class BasicEnvironment(BaseEnvironment): + """ + A basic environment implementing the logic of conversation. + + Args: + agents: List of agents + rule: Rule for the environment + max_turns: Maximum number of turns + cnt_turn: Current turn number + last_messages: Messages from last turn + rule_params: Variables set by the rule + """ + + agents: List[BaseAgent] + rule: Rule + max_turns: int = 10 + cnt_turn: int = 0 + last_messages: List[Message] = [] + rule_params: Dict = {} + + def __init__(self, rule, **kwargs): + rule_config = rule + order_config = rule_config.get("order", {"type": "sequential"}) + visibility_config = rule_config.get("visibility", {"type": "all"}) + selector_config = rule_config.get("selector", {"type": "base"}) + updater_config = rule_config.get("updater", {"type": "base"}) + describer_config = rule_config.get("describer", {"type": "base"}) + rule = Rule( + order_config, + visibility_config, + selector_config, + updater_config, + describer_config, + ) + super().__init__(rule=rule, **kwargs) + + async def step(self) -> List[Message]: + """Run one step of the environment""" + + # Get the next agent index + agent_ids = self.rule.get_next_agent_idx(self) + + # Generate current environment description + env_descriptions = self.rule.get_env_description(self) + + # Generate the next message + messages = await asyncio.gather( + *[self.agents[i].astep(env_descriptions[i]) for i in agent_ids] + ) + + # Some rules will select certain messages from all the messages + selected_messages = self.rule.select_message(self, messages) + self.last_messages = selected_messages + self.print_messages(selected_messages) + + # Update the memory of the agents + self.rule.update_memory(self) + + # Update the set of visible agents for each agent + self.rule.update_visible_agents(self) + + self.cnt_turn += 1 + + return selected_messages + + def print_messages(self, messages: List[Message]) -> None: + for message in messages: + if message is not None: + logging.info(f"{message.sender}: {message.content}") + + def reset(self) -> None: + """Reset the environment""" + self.cnt_turn = 0 + self.rule.reset() + for agent in self.agents: + agent.reset() + + def is_done(self) -> bool: + """Check if the environment is done""" + return self.cnt_turn >= self.max_turns diff --git a/agentverse/environments/rules/describer/__init__.py b/agentverse/environments/rules/describer/__init__.py index a275126f3..7de6fe82f 100644 --- a/agentverse/environments/rules/describer/__init__.py +++ b/agentverse/environments/rules/describer/__init__.py @@ -1,5 +1,7 @@ from agentverse.registry import Registry + describer_registry = Registry(name="DescriberRegistry") from .base import BaseDescriber +from .basic import BasicDescriber from .classroom import ClassroomDescriber diff --git a/agentverse/environments/rules/describer/base.py b/agentverse/environments/rules/describer/base.py index 5fbaa1d97..3a51e09f5 100644 --- a/agentverse/environments/rules/describer/base.py +++ b/agentverse/environments/rules/describer/base.py @@ -5,16 +5,17 @@ from pydantic import BaseModel from . import describer_registry as DescriberRegistry +from abc import abstractmethod if TYPE_CHECKING: from agentverse.environments import BaseEnvironment -@DescriberRegistry.register("base") class BaseDescriber(BaseModel): + @abstractmethod def get_env_description(self, environment: BaseEnvironment) -> List[str]: """Return the environment description for each agent""" - return ["" for _ in range(len(environment.agents))] + pass def reset(self) -> None: pass diff --git a/agentverse/environments/rules/describer/basic.py b/agentverse/environments/rules/describer/basic.py new file mode 100644 index 000000000..20f6bd4f6 --- /dev/null +++ b/agentverse/environments/rules/describer/basic.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, List + +from . import describer_registry as DescriberRegistry +from .base import BaseDescriber + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@DescriberRegistry.register("basic") +class BasicDescriber(BaseDescriber): + def get_env_description(self, environment: BaseEnvironment) -> List[str]: + """Return the environment description for each agent""" + return ["" for _ in range(len(environment.agents))] diff --git a/agentverse/environments/rules/selector/__init__.py b/agentverse/environments/rules/selector/__init__.py index 2b713a5e6..da11bda59 100644 --- a/agentverse/environments/rules/selector/__init__.py +++ b/agentverse/environments/rules/selector/__init__.py @@ -1,5 +1,7 @@ from agentverse.registry import Registry + selector_registry = Registry(name="SelectorRegistry") from .base import BaseSelector -from .classroom import ClassroomSelector \ No newline at end of file +from .basic import BasicSelector +from .classroom import ClassroomSelector diff --git a/agentverse/environments/rules/selector/base.py b/agentverse/environments/rules/selector/base.py index bc69ac9c5..cc283870f 100644 --- a/agentverse/environments/rules/selector/base.py +++ b/agentverse/environments/rules/selector/base.py @@ -7,6 +7,7 @@ from agentverse.message import Message from . import selector_registry as SelectorRegistry +from abc import abstractmethod if TYPE_CHECKING: from agentverse.environments import BaseEnvironment @@ -17,9 +18,13 @@ class BaseSelector(BaseModel): """ Base class for all selecters """ - def select_message(self, environment: BaseEnvironment, messages: List[Message]) -> List[Message]: + + @abstractmethod + def select_message( + self, environment: BaseEnvironment, messages: List[Message] + ) -> List[Message]: """Selects a set of valid messages from all messages""" - return messages + pass def reset(self) -> None: - pass \ No newline at end of file + pass diff --git a/agentverse/environments/rules/selector/basic.py b/agentverse/environments/rules/selector/basic.py new file mode 100644 index 000000000..1ebc0b48b --- /dev/null +++ b/agentverse/environments/rules/selector/basic.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List + +from agentverse.message import Message + +from . import selector_registry as SelectorRegistry +from .base import BaseSelector + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@SelectorRegistry.register("basic") +class BasicSelector(BaseSelector): + """ + Base class for all selecters + """ + + def select_message( + self, environment: BaseEnvironment, messages: List[Message] + ) -> List[Message]: + """Selects a set of valid messages from all messages""" + return messages + + def reset(self) -> None: + pass diff --git a/agentverse/environments/rules/updater/__init__.py b/agentverse/environments/rules/updater/__init__.py index d8682db8c..81c30a37f 100644 --- a/agentverse/environments/rules/updater/__init__.py +++ b/agentverse/environments/rules/updater/__init__.py @@ -1,5 +1,7 @@ from agentverse.registry import Registry + updater_registry = Registry(name="UpdaterRegistry") from .base import BaseUpdater -from .classroom import ClassroomUpdater \ No newline at end of file +from .basic import BasicUpdater +from .classroom import ClassroomUpdater diff --git a/agentverse/environments/rules/updater/base.py b/agentverse/environments/rules/updater/base.py index ff6925c08..8dc593a16 100644 --- a/agentverse/environments/rules/updater/base.py +++ b/agentverse/environments/rules/updater/base.py @@ -5,8 +5,10 @@ from langchain.schema import AgentAction from pydantic import BaseModel -from agentverse.agents import Agent +# from agentverse.agents import Agent +from agentverse.agents import BaseAgent from agentverse.message import Message +from abc import abstractmethod from . import updater_registry as UpdaterRegistry @@ -17,50 +19,12 @@ @UpdaterRegistry.register("base") class BaseUpdater(BaseModel): """ - The basic version of updater. - The messages will be seen by all the receiver specified in the message. + The base class of updater class. """ - def update_memory(self, environment: BaseEnvironment): - added = False - for message in environment.last_messages: - if len(message.tool_response) > 0: - self.add_tool_response(message.sender, environment.agents, message.tool_response) - if message.content == "": - continue - added |= self.add_message_to_all_agents(environment.agents, message) - # If no one speaks in this turn. Add an empty message to all agents - if not added: - for agent in environment.agents: - agent.memory.add_user_message("[Silence]") - def add_tool_response(self, name: str, agents: List[Agent], tool_response: List[Tuple[AgentAction, str]]): - for agent in agents: - if agent.name != name: - continue - if agent.tool_memory is not None: - agent.tool_memory.save_context(tool_response) - break + @abstractmethod + def update_memory(self, environment: BaseEnvironment): + pass - def add_message_to_all_agents(self, agents: List[Agent], message: Message) -> bool: - if "all" in message.receiver: - # If receiver is all, then add the message to all agents - for agent in agents: - if agent.name != message.sender: - agent.memory.add_user_message(f'{message.sender}: ' + message.content) - else: - agent.memory.add_ai_message(f'{message.sender}: ' + message.content) - return True - else: - # If receiver is not all, then add the message to the specified agents - receiver_set = set(message.receiver) - for agent in agents: - if agent.name in receiver_set: - agent.memory.add_user_message(f'{message.sender}: ' + message.content) - receiver_set.remove(agent.name) - if len(receiver_set) > 0: - missing_receiver = ', '.join(list(receiver_set)) - raise ValueError("Receiver {} not found. Message discarded".format(missing_receiver)) - return True - def reset(self): - pass \ No newline at end of file + pass diff --git a/agentverse/environments/rules/updater/basic.py b/agentverse/environments/rules/updater/basic.py new file mode 100644 index 000000000..ec73bec2e --- /dev/null +++ b/agentverse/environments/rules/updater/basic.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Tuple + +from . import updater_registry as UpdaterRegistry +from .base import BaseUpdater +from agentverse.agents import BaseAgent +from agentverse.message import AgentAction, Message + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@UpdaterRegistry.register("basic") +class BasicUpdater(BaseUpdater): + """ + The basic version of updater. + The messages will be seen by all the receiver specified in the message. + """ + + def update_memory(self, environment: BaseEnvironment): + added = False + for message in environment.last_messages: + if len(message.tool_response) > 0: + self.add_tool_response( + message.sender, environment.agents, message.tool_response + ) + if message.content == "": + continue + added |= self.add_message_to_all_agents(environment.agents, message) + # If no one speaks in this turn. Add an empty message to all agents + if not added: + for agent in environment.agents: + agent.memory.add_user_message("[Silence]") + + def add_tool_response( + self, + name: str, + agents: List[BaseAgent], + tool_response: List[Tuple[AgentAction, str]], + ): + for agent in agents: + if agent.name != name: + continue + if agent.tool_memory is not None: + agent.tool_memory.save_context(tool_response) + break + + def add_message_to_all_agents( + self, agents: List[BaseAgent], message: Message + ) -> bool: + if "all" in message.receiver: + # If receiver is all, then add the message to all agents + for agent in agents: + agent.add_message_to_memory(message) + return True + else: + # If receiver is not all, then add the message to the specified agents + receiver_set = message.receiver + for agent in agents: + if agent.name in receiver_set: + agent.add_message_to_memory(message) + receiver_set.remove(agent.name) + if len(receiver_set) > 0: + missing_receiver = ", ".join(list(receiver_set)) + raise ValueError( + "Receiver {} not found. Message discarded".format(missing_receiver) + ) + return True diff --git a/agentverse/initialization.py b/agentverse/initialization.py index 7b989177b..34040f8f8 100644 --- a/agentverse/initialization.py +++ b/agentverse/initialization.py @@ -4,112 +4,115 @@ import yaml from bmtools.agent.singletool import import_all_apis, load_single_tools from langchain.agents import Agent as langchainAgent -from langchain.chat_models import ChatOpenAI -from langchain.chat_models.base import BaseChatModel -from langchain.llms import OpenAI -from langchain.llms.base import BaseLLM -from langchain.memory import ChatMessageHistory + +# from langchain.chat_models import ChatOpenAI +# from langchain.chat_models.base import BaseChatModel +# from langchain.llms import OpenAI +# from langchain.llms.base import BaseLLM +from agentverse.llms import OpenAICompletion, OpenAIChat + +# from langchain.memory import ChatMessageHistory from langchain.memory.prompt import _DEFAULT_SUMMARIZER_TEMPLATE from langchain.prompts import PromptTemplate -from agentverse.agents import Agent +# from agentverse.agents import Agent +from agentverse.agents.conversation_agent import BaseAgent +from agentverse.agents.conversation_agent import ConversationAgent from agentverse.environments import BaseEnvironment, env_registry -from agentverse.memory import SummaryMemory +from agentverse.memory import ChatHistoryMemory + +# from agentverse.memory.memory import SummaryMemory from agentverse.parser import output_parser_registry def load_llm(llm_config: Dict): - llm_type = llm_config.pop('llm_type', 'text-davinci-003') - if llm_type == 'gpt-3.5-turbo': - return ChatOpenAI(**llm_config) - elif llm_type == 'text-davinci-003': - return OpenAI(**llm_config) + llm_type = llm_config.pop("llm_type", "text-davinci-003") + if llm_type == "gpt-3.5-turbo": + return OpenAIChat(**llm_config) + elif llm_type == "text-davinci-003": + return OpenAICompletion(**llm_config) else: raise NotImplementedError("LLM type {} not implemented".format(llm_type)) + def load_memory(memory_config: Dict): memory_type = memory_config.pop("memory_type", "chat_message_history") - if memory_type == "chat_message_history": - return ChatMessageHistory() - elif memory_type == 'summary': - llm = load_llm(memory_config.pop('llm', 'text-davinci-003')) - prompt = memory_config.pop('prompt', _DEFAULT_SUMMARIZER_TEMPLATE) - memory_config['prompt'] = PromptTemplate( + if memory_type == "chat_history": + return ChatHistoryMemory() + elif memory_type == "summary": + llm = load_llm(memory_config.pop("llm", "text-davinci-003")) + prompt = memory_config.pop("prompt", _DEFAULT_SUMMARIZER_TEMPLATE) + memory_config["prompt"] = PromptTemplate( input_variables=["summary", "new_lines"], template=prompt ) return SummaryMemory(llm=llm, **memory_config) else: raise NotImplementedError("Memory type {} not implemented".format(memory_type)) - + + def load_tools(tool_config: List[Dict]): if len(tool_config) == 0: return [] all_tools_list = [] for tool in tool_config: - _, config = load_single_tools(tool['tool_name'], tool['tool_url']) + _, config = load_single_tools(tool["tool_name"], tool["tool_url"]) all_tools_list += import_all_apis(config) return all_tools_list + def load_environment(env_config: Dict) -> BaseEnvironment: - env_type = env_config.pop('env_type', 'base') + env_type = env_config.pop("env_type", "base") return env_registry.build(env_type, **env_config) + def load_agent(agent_config: Dict) -> langchainAgent: - agent_type = agent_config.pop('agent_type', 'chat') - if agent_type == "chat": - agent = Agent.from_llm_and_tools(**agent_config) + agent_type = agent_config.pop("agent_type", "conversation") + if agent_type == "conversation": + # agent = Agent.from_llm_and_tools(**agent_config) + agent = ConversationAgent(**agent_config) else: raise NotImplementedError("Agent type {} not found".format(agent_type)) return agent + def prepare_task_config(task): """Read the yaml config of the given task in `tasks` directory.""" - all_task_dir = os.path.join(os.path.dirname(__file__), 'tasks') + all_task_dir = os.path.join(os.path.dirname(__file__), "tasks") task_path = os.path.join(all_task_dir, task) - config_path = os.path.join(task_path, 'config.yaml') + config_path = os.path.join(task_path, "config.yaml") if not os.path.exists(task_path): all_tasks = [] for task in os.listdir(all_task_dir): - if os.path.isdir(os.path.join(all_task_dir, task)) \ - and task != "__pycache__": + if ( + os.path.isdir(os.path.join(all_task_dir, task)) + and task != "__pycache__" + ): all_tasks.append(task) raise ValueError(f"Task {task} not found. Available tasks: {all_tasks}") if not os.path.exists(config_path): - raise ValueError("You should include the config.yaml file in the task directory") + raise ValueError( + "You should include the config.yaml file in the task directory" + ) task_config = yaml.safe_load(open(config_path)) + # Build the output parser parser = output_parser_registry.build(task) - task_config['output_parser'] = parser - - for i, agent_configs in enumerate(task_config['agents']): - agent_configs['memory'] = load_memory(agent_configs['memory']) - if agent_configs.get('tool_memory', None) is not None: - agent_configs['tool_memory'] = load_memory(agent_configs['tool_memory']) - llm = load_llm(agent_configs['llm']) - agent_configs['llm'] = llm - agent_configs['tools'] = load_tools(agent_configs.get("tools", [])) - - # BaseLLM and its subclass will use .format to format the {chat_history} and {agent_scratchpad} during prompting - # so we have to keep the bracket {{ and }} in the description of the tools (will become { and } after formatting}) - # BaseChatModel and its subclass will not use .format, so we have to replace {{ and }} with { and } in the description of the tools - if isinstance(llm, BaseLLM): - tool_strings = "\n".join( - [f"> {tool.name}: {tool.description}" for tool in agent_configs['tools']] - ) - elif isinstance(llm, BaseChatModel): - tool_strings = "\n".join( - [f"> {tool.name}: {tool.description.replace('{{', '{').replace('}}', '}')}" for tool in agent_configs['tools']] - ) - else: - raise NotImplementedError("LLM type {} not supported".format(llm.__class__.__name__)) - - tool_names = ", ".join([tool.name for tool in agent_configs['tools']]) - # Here we assume that the description for tools only appears in prefix prompt with placeholder {tool} - # and we assume that format prompt contains the placeholder {tool_names} that tells the model - # which tools is available - # TODO: Improve the flexibility - agent_configs['output_parser'] = task_config['output_parser'] - agent_configs['prefix_prompt'] = agent_configs['prefix_prompt'] + '\n' + agent_configs['role_description'] - agent_configs['format_prompt'] = agent_configs['format_prompt'].format(tool_names=tool_names, tools=tool_strings) - - return task_config \ No newline at end of file + task_config["output_parser"] = parser + + for i, agent_configs in enumerate(task_config["agents"]): + agent_configs["memory"] = load_memory(agent_configs.get("memory", {})) + if agent_configs.get("tool_memory", None) is not None: + agent_configs["tool_memory"] = load_memory(agent_configs["tool_memory"]) + llm = load_llm(agent_configs.get("llm", "text-davinci-003")) + agent_configs["llm"] = llm + agent_configs["tools"] = load_tools(agent_configs.get("tools", [])) + + # tool_strings = "\n".join( + # [f"> {tool.name}: {tool.description}" for tool in agent_configs["tools"]] + # ) + + # tool_names = ", ".join([tool.name for tool in agent_configs["tools"]]) + + agent_configs["output_parser"] = task_config["output_parser"] + + return task_config diff --git a/agentverse/llms/__init__.py b/agentverse/llms/__init__.py new file mode 100644 index 000000000..eeebbc61b --- /dev/null +++ b/agentverse/llms/__init__.py @@ -0,0 +1,2 @@ +from .base import BaseLLM, BaseChatModel, BaseCompletionModel +from .openai import OpenAIChat, OpenAICompletion diff --git a/agentverse/llms/base.py b/agentverse/llms/base.py new file mode 100644 index 000000000..91d0b9114 --- /dev/null +++ b/agentverse/llms/base.py @@ -0,0 +1,30 @@ +from pydantic import BaseModel, Field +from abc import abstractmethod +from typing import Dict + +class LLMResult(BaseModel): + content: str + send_tokens: int + recv_tokens: int + total_tokens: int + +class BaseModelArgs(BaseModel): + pass + +class BaseLLM(BaseModel): + args: BaseModelArgs = Field(default_factory=BaseModelArgs) + max_retry: int = Field(default=3) + + @abstractmethod + def generate_response(self, **kwargs) -> LLMResult: + pass + + @abstractmethod + def agenerate_response(self, **kwargs) -> LLMResult: + pass + +class BaseChatModel(BaseLLM): + pass + +class BaseCompletionModel(BaseLLM): + pass \ No newline at end of file diff --git a/agentverse/llms/openai.py b/agentverse/llms/openai.py new file mode 100644 index 000000000..b74317615 --- /dev/null +++ b/agentverse/llms/openai.py @@ -0,0 +1,115 @@ +import logging +import os +from pydantic import Field, BaseModel +from typing import List, Optional, Union, Dict + +from agentverse.llms.base import LLMResult +from .base import BaseModelArgs, BaseChatModel, BaseCompletionModel + +try: + import openai + from openai.error import OpenAIError +except ImportError: + is_openai_available = False + logging.warning("openai package is not installed") +else: + openai.api_key = os.environ.get("OPENAI_API_KEY") + openai.proxy = os.environ.get("http_proxy") + if openai.proxy is None: + openai.proxy = os.environ.get("HTTP_PROXY") + if openai.api_key is None: + logging.warning( + "OpenAI API key is not set. Please set the environment variable OPENAI_API_KEY" + ) + is_openai_available = False + else: + is_openai_available = True + + +class OpenAIChatArgs(BaseModelArgs): + model: str = Field(default="gpt-3.5-turbo") + max_tokens: int = Field(default=2048) + temperature: float = Field(default=1.0) + top_p: int = Field(default=1) + n: int = Field(default=1) + stop: Optional[Union[str, List]] = Field(default=None) + presence_penalty: int = Field(default=0) + frequency_penalty: int = Field(default=0) + + +class OpenAICompletionArgs(OpenAIChatArgs): + model: str = Field(default="text-davinci-003") + suffix: str = Field(default="") + best_of: int = Field(default=1) + + +class OpenAICompletion(BaseCompletionModel): + args: OpenAICompletionArgs = Field(default_factory=OpenAICompletionArgs) + + def __init__(self, max_retry: int = 3, **kwargs): + args = OpenAICompletionArgs() + args = args.dict() + for k, v in args.items(): + args[k] = kwargs.pop(k, v) + if len(kwargs) > 0: + logging.warning(f"Unused arguments: {kwargs}") + super().__init__(args=args, max_retry=max_retry) + + def generate_response(self, prompt: str) -> LLMResult: + response = openai.Completion.create(prompt=prompt, **self.args.dict()) + return LLMResult( + content=response["choices"][0]["text"], + send_tokens=response["usage"]["prompt_tokens"], + recv_tokens=response["usage"]["completion_tokens"], + total_tokens=response["usage"]["total_tokens"], + ) + + async def agenerate_response(self, prompt: str) -> LLMResult: + response = await openai.Completion.acreate(prompt=prompt, **self.args.dict()) + return LLMResult( + content=response["choices"][0]["text"], + send_tokens=response["usage"]["prompt_tokens"], + recv_tokens=response["usage"]["completion_tokens"], + total_tokens=response["usage"]["total_tokens"], + ) + + +class OpenAIChat(BaseChatModel): + args: OpenAIChatArgs = Field(default_factory=OpenAIChatArgs) + + def __init__(self, max_retry: int = 3, **kwargs): + args = OpenAIChatArgs() + args = args.dict() + for k, v in args.items(): + args.k = kwargs.pop(k, v) + if len(kwargs) > 0: + logging.warning(f"Unused arguments: {kwargs}") + super().__init__(args=args, max_retry=max_retry) + + def _construct_messages(self, prompt: str): + return [{"role": "user", "content": prompt}] + + def generate_response(self, prompt: str) -> LLMResult: + messages = self._construct_messages(prompt) + response = openai.Completion.create(messages=messages, **self.args.dict()) + return LLMResult( + content=response["choices"][0]["message"]["content"], + send_tokens=response["usage"]["prompt_tokens"], + recv_tokens=response["usage"]["completion_tokens"], + total_tokens=response["usage"]["total_tokens"], + ) + + async def agenerate_response(self, prompt: str) -> LLMResult: + messages = self._construct_messages(prompt) + try: + response = await openai.Completion.acreate( + messages=messages, **self.args.dict() + ) + except OpenAIError as error: + raise + return LLMResult( + content=response["choices"][0]["message"]["content"], + send_tokens=response["usage"]["prompt_tokens"], + recv_tokens=response["usage"]["completion_tokens"], + total_tokens=response["usage"]["total_tokens"], + ) diff --git a/agentverse/memory.py b/agentverse/memory.py deleted file mode 100644 index f4969e853..000000000 --- a/agentverse/memory.py +++ /dev/null @@ -1,87 +0,0 @@ -# Modified from langchain.memory.summary.py - -from typing import Any, Dict, List, Tuple, Type, Union - -from langchain.base_language import BaseLanguageModel -from langchain.chains.llm import LLMChain -from langchain.memory.chat_memory import BaseChatMemory -from langchain.memory.prompt import SUMMARY_PROMPT -from langchain.prompts.base import BasePromptTemplate -from langchain.schema import (AgentAction, AIMessage, BaseMessage, ChatMessage, - SystemMessage, get_buffer_string) -from pydantic import BaseModel, root_validator - -from agentverse.message import Message - - -class SummarizerMixin(BaseModel): - llm: BaseLanguageModel - prompt: BasePromptTemplate = SUMMARY_PROMPT - summary_message_cls: Type[BaseMessage] = AIMessage - - def predict_new_summary( - self, messages: List[ChatMessage], existing_summary: str - ) -> str: - lines = [] - for message in messages: - if message.role == "": - # no role. it's tool responses - lines.append(message.content) - else: - lines.append(f"{message.role}: {message.content}") - new_lines = "\n".join(lines) - - chain = LLMChain(llm=self.llm, prompt=self.prompt) - return chain.predict(summary=existing_summary, new_lines=new_lines) - - -class SummaryMemory(BaseChatMemory, SummarizerMixin): - """Conversation summarizer to memory.""" - - buffer: str = "" - memory_key: str = "history" #: :meta private: - - @property - def memory_variables(self) -> List[str]: - """Will always return list of memory variables. - - :meta private: - """ - return [self.memory_key] - - def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]: - """Return history buffer.""" - if self.return_messages: - buffer: Any = [self.summary_message_cls(content=self.buffer)] - else: - buffer = self.buffer - return {self.memory_key: buffer} - - @root_validator() - def validate_prompt_input_variables(cls, values: Dict) -> Dict: - """Validate that prompt input variables are consistent.""" - prompt_variables = values["prompt"].input_variables - expected_keys = {"summary", "new_lines"} - if expected_keys != set(prompt_variables): - raise ValueError( - "Got unexpected prompt input variables. The prompt expects " - f"{prompt_variables}, but it should have {expected_keys}." - ) - return values - - def save_context(self, contexts: Union[List[Tuple[AgentAction, str]], List[Message]]) -> None: - """Save context from this conversation to buffer.""" - for context in contexts: - if isinstance(context, Message): - self.chat_memory.messages.append(ChatMessage(content=context.content, role=context.sender)) - elif isinstance(context, tuple) and len(context) == 2 and \ - isinstance(context[0], AgentAction) and isinstance(context[1], str): - self.chat_memory.messages.append(ChatMessage(content=context[0].log.strip() + '\nObservation:' + context[1], role="")) - self.buffer = self.predict_new_summary( - self.chat_memory.messages[-len(contexts):], self.buffer - ) - - def clear(self) -> None: - """Clear memory contents.""" - super().clear() - self.buffer = "" diff --git a/agentverse/memory/__init__.py b/agentverse/memory/__init__.py new file mode 100644 index 000000000..3b8736868 --- /dev/null +++ b/agentverse/memory/__init__.py @@ -0,0 +1,2 @@ +from .base import BaseMemory +from .chat_history_message import ChatHistoryMemory diff --git a/agentverse/memory/base.py b/agentverse/memory/base.py new file mode 100644 index 000000000..5e86bad7f --- /dev/null +++ b/agentverse/memory/base.py @@ -0,0 +1,18 @@ +from pydantic import BaseModel, Field +from typing import List, Dict +from agentverse.message import Message +from abc import abstractmethod + + +class BaseMemory(BaseModel): + @abstractmethod + def add_message(self, message: Message) -> None: + pass + + @abstractmethod + def to_string(self) -> str: + pass + + @abstractmethod + def reset(self) -> None: + pass diff --git a/agentverse/memory/chat_history_message.py b/agentverse/memory/chat_history_message.py new file mode 100644 index 000000000..ac9bf9b9e --- /dev/null +++ b/agentverse/memory/chat_history_message.py @@ -0,0 +1,22 @@ +from .base import BaseMemory +from typing import List +from pydantic import Field +from agentverse.message import Message + + +class ChatHistoryMemory(BaseMemory): + messages: List[Message] = Field(default=[]) + + def add_message(self, message: Message) -> None: + self.messages.append(message) + + def to_string(self, add_sender_prefix: bool = False) -> str: + if add_sender_prefix: + return "\n".join( + [f"[{message.sender}]: {message.content}" for message in self.messages] + ) + else: + return "\n".join([message.content for message in self.messages]) + + def reset(self) -> None: + self.messages = [] diff --git a/agentverse/message.py b/agentverse/message.py index 2c4f3d273..1c92ec292 100644 --- a/agentverse/message.py +++ b/agentverse/message.py @@ -1,9 +1,11 @@ -from typing import List, Tuple +from pydantic import BaseModel, Field +from typing import List, Tuple, Set from langchain.schema import AgentAction, ChatMessage -class Message(ChatMessage): - sender: str - receiver: List[str] - tool_response: List[Tuple[AgentAction, str]] +class Message(BaseModel): + content: str = Field(default="") + sender: str = Field(default="") + receiver: Set[str] = Field(default=set({"all"})) + tool_response: List[Tuple[AgentAction, str]] = Field(default=[]) diff --git a/agentverse/parser.py b/agentverse/parser.py index 96b97757c..a06e8fc64 100644 --- a/agentverse/parser.py +++ b/agentverse/parser.py @@ -1,11 +1,25 @@ from agentverse.registry import Registry +from typing import NamedTuple +from abc import abstractmethod +from agentverse.llms.base import LLMResult +from pydantic import BaseModel output_parser_registry = Registry(name="OutputParserRegistry") + class OutputParseError(BaseException): """Exception raised when parsing output from a command fails.""" + def __init__(self, message): self.message = message def __str__(self): return "Failed to parse output of the model:%s\n " % self.message + + +class OutputParser(BaseModel): + """Base class for output parsers.""" + + @abstractmethod + def parse(self, output: LLMResult) -> NamedTuple: + pass diff --git a/agentverse/tasks/__init__.py b/agentverse/tasks/__init__.py index 59d68c992..4806ccea3 100644 --- a/agentverse/tasks/__init__.py +++ b/agentverse/tasks/__init__.py @@ -4,5 +4,8 @@ from .math_problem_2players_tools.output_parser import MathProblem2PlayersToolsParser from .nlp_classroom_3players.output_parser import NlpClassroom3PlayersParser from .nlp_classroom_9players.output_parser import NlpClassroom9PlayersParser -from .nlp_classroom_3players_withtool.output_parser import NlpClassroom3PlayersWithtoolParser +from .nlp_classroom_3players_withtool.output_parser import ( + NlpClassroom3PlayersWithtoolParser, +) from .nlp_classroom_9players_group.output_parser import NlpClassroom9PlayersGroupParser +from .nlp_classroom_3players_nolc.output_parser import NlpClassroom3PlayersNolcParser diff --git a/agentverse/tasks/nlp_classroom_3players_nolc/config.yaml b/agentverse/tasks/nlp_classroom_3players_nolc/config.yaml new file mode 100644 index 000000000..027d5ae79 --- /dev/null +++ b/agentverse/tasks/nlp_classroom_3players_nolc/config.yaml @@ -0,0 +1,63 @@ +prompts: + prompt: &prompt |- + Assume that you are in a university classroom and it is Natural Language Processing module. You start by introducing themselves. Below is the description of your role. ${role_description} + + When responding, please output a response in the following format with two fields Action and Action Input: + Action: Speak + Action Input: (You should put what you want to speak use here) + + Here is the conversation history: + ${chat_history} + + You should now give your response based on the above history. Remember to give your response STRICTLY in the above response format. Do not add any additional field or line break to your response! + +name: NLP Classroom 3 Players + +environment: + env_type: basic + max_turns: 10 + rule: + order: + type: sequential + visibility: + type: all + selector: + type: basic + updater: + type: basic + describer: + type: basic + +agents: + - agent_type: conversation + name: Professor Micheal + role_description: You are Prof. Micheal, a knowledgeable professor in NLP. Your answer will concise and accurate. The answers should be less than 100 words. + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + llm_type: text-davinci-003 + temperature: 0.7 + max_tokens: 250 + - agent_type: conversation + name: Student Beta + role_description: You are Beta, a student curious about Natural Language Processing and you want to learn some basic concepts of NLP. You know nothing about the area so you will ask lots of questions. + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + llm_type: text-davinci-003 + temperature: 0.7 + max_tokens: 100 + - agent_type: conversation + name: Teaching Assistant Gamma + role_description: You are Gamma, a teaching assistant of the Natural Language Processing module. You mostly help with logistics and marking, but occasionally handles questions. Your answer should be less than 100 words. + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + llm_type: text-davinci-003 + temperature: 0.7 + max_tokens: 100 + +tools: diff --git a/agentverse/tasks/nlp_classroom_3players_nolc/output_parser.py b/agentverse/tasks/nlp_classroom_3players_nolc/output_parser.py new file mode 100644 index 000000000..9ffd2f435 --- /dev/null +++ b/agentverse/tasks/nlp_classroom_3players_nolc/output_parser.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import re +from typing import Union + +# from langchain.agents import AgentOutputParser +from agentverse.parser import OutputParser, LLMResult +from langchain.schema import AgentAction, AgentFinish + +from agentverse.parser import OutputParseError, output_parser_registry + + +@output_parser_registry.register("nlp_classroom_3players_nolc") +class NlpClassroom3PlayersNolcParser(OutputParser): + def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]: + text = output.content + cleaned_output = text.strip() + cleaned_output = re.sub(r"\n+", "\n", cleaned_output) + cleaned_output = cleaned_output.split("\n") + if not ( + len(cleaned_output) == 2 + and cleaned_output[0].startswith("Action:") + and cleaned_output[1].startswith("Action Input:") + ): + raise OutputParseError(text) + action = cleaned_output[0][len("Action:") :].strip() + action_input = cleaned_output[1][len("Action Input:") :].strip() + if action == "Speak": + return AgentFinish({"output": action_input}, text) + else: + raise OutputParseError(text) From fbe1a2377e9baeeb9a1e7e377a1a595b690273ad Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Tue, 16 May 2023 23:13:10 +0800 Subject: [PATCH 02/50] fix bugs in initializing OpenAIChat model --- agentverse/llms/openai.py | 6 +++--- agentverse/tasks/nlp_classroom_3players_nolc/config.yaml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/agentverse/llms/openai.py b/agentverse/llms/openai.py index b74317615..4159e5bc1 100644 --- a/agentverse/llms/openai.py +++ b/agentverse/llms/openai.py @@ -81,7 +81,7 @@ def __init__(self, max_retry: int = 3, **kwargs): args = OpenAIChatArgs() args = args.dict() for k, v in args.items(): - args.k = kwargs.pop(k, v) + args[k] = kwargs.pop(k, v) if len(kwargs) > 0: logging.warning(f"Unused arguments: {kwargs}") super().__init__(args=args, max_retry=max_retry) @@ -91,7 +91,7 @@ def _construct_messages(self, prompt: str): def generate_response(self, prompt: str) -> LLMResult: messages = self._construct_messages(prompt) - response = openai.Completion.create(messages=messages, **self.args.dict()) + response = openai.ChatCompletion.create(messages=messages, **self.args.dict()) return LLMResult( content=response["choices"][0]["message"]["content"], send_tokens=response["usage"]["prompt_tokens"], @@ -102,7 +102,7 @@ def generate_response(self, prompt: str) -> LLMResult: async def agenerate_response(self, prompt: str) -> LLMResult: messages = self._construct_messages(prompt) try: - response = await openai.Completion.acreate( + response = await openai.ChatCompletion.acreate( messages=messages, **self.args.dict() ) except OpenAIError as error: diff --git a/agentverse/tasks/nlp_classroom_3players_nolc/config.yaml b/agentverse/tasks/nlp_classroom_3players_nolc/config.yaml index 027d5ae79..cf3a98f22 100644 --- a/agentverse/tasks/nlp_classroom_3players_nolc/config.yaml +++ b/agentverse/tasks/nlp_classroom_3players_nolc/config.yaml @@ -36,7 +36,7 @@ agents: memory_type: chat_history prompt_template: *prompt llm: - llm_type: text-davinci-003 + llm_type: gpt-3.5-turbo temperature: 0.7 max_tokens: 250 - agent_type: conversation @@ -46,7 +46,7 @@ agents: memory_type: chat_history prompt_template: *prompt llm: - llm_type: text-davinci-003 + llm_type: gpt-3.5-turbo temperature: 0.7 max_tokens: 100 - agent_type: conversation @@ -56,7 +56,7 @@ agents: memory_type: chat_history prompt_template: *prompt llm: - llm_type: text-davinci-003 + llm_type: gpt-3.5-turbo temperature: 0.7 max_tokens: 100 From ed47ff72e67f4a7bb9762bbbc9e41341bab63de6 Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Tue, 16 May 2023 23:23:44 +0800 Subject: [PATCH 03/50] add some comment --- agentverse/agents/base.py | 17 +++++++++- agentverse/agents/conversation_agent.py | 45 ++++++++++++++++--------- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/agentverse/agents/base.py b/agentverse/agents/base.py index f4940390e..e9dc83449 100644 --- a/agentverse/agents/base.py +++ b/agentverse/agents/base.py @@ -1,6 +1,6 @@ import logging from abc import abstractmethod -from typing import List, Set, Union +from typing import List, Set, Union, NamedTuple from pydantic import BaseModel, Field @@ -10,6 +10,21 @@ from agentverse.parser import OutputParser +class AgentAction(NamedTuple): + """Agent's action to take.""" + + tool: str + tool_input: Union[str, dict] + log: str + + +class AgentFinish(NamedTuple): + """Agent's return value.""" + + return_values: dict + log: str + + class BaseAgent(BaseModel): name: str llm: BaseLLM diff --git a/agentverse/agents/conversation_agent.py b/agentverse/agents/conversation_agent.py index 0c064ae09..d492fc543 100644 --- a/agentverse/agents/conversation_agent.py +++ b/agentverse/agents/conversation_agent.py @@ -9,24 +9,32 @@ from .base import BaseAgent -class AgentAction(NamedTuple): - """Agent's action to take.""" - - tool: str - tool_input: Union[str, dict] - log: str - - -class AgentFinish(NamedTuple): - """Agent's return value.""" +class ConversationAgent(BaseAgent): + def step(self, env_description: str = "") -> Message: + prompt = self._fill_prompt_template(env_description) - return_values: dict - log: str + parsed_response = None + for i in range(self.max_retry): + try: + response = self.llm.generate_response(prompt) + parsed_response = self.output_parser.parse(response) + break + except Exception as e: + logging.error(e) + logging.warning("Retrying...") + continue + if parsed_response is None: + logging.error(f"{self.name} failed to generate valid response.") -class ConversationAgent(BaseAgent): - def step(self, env_description: str = "") -> Message: - pass + message = Message( + content="" + if parsed_response is None + else parsed_response.return_values["output"], + sender=self.name, + receiver=self.get_receiver(), + ) + return message async def astep(self, env_description: str = "") -> Message: """Asynchronous version of step""" @@ -56,6 +64,13 @@ async def astep(self, env_description: str = "") -> Message: return message def _fill_prompt_template(self, env_description: str = "") -> str: + """Fill the placeholders in the prompt template + + In the conversation agent, three placeholders are supported: + - ${agent_name}: the name of the agent + - ${env_description}: the description of the environment + - ${role_description}: the description of the role of the agent + """ input_arguments = { "agent_name": self.name, "env_description": env_description, From f9e6c77c55d6917d2ee43258d3c98c79b9665277 Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Wed, 17 May 2023 16:11:33 +0800 Subject: [PATCH 04/50] add tool agent --- agentverse/agents/__init__.py | 8 +- agentverse/agents/agent.py | 6 +- agentverse/agents/base.py | 2 +- agentverse/agents/conversation_agent.py | 8 +- agentverse/agents/tool_agent.py | 147 ++++++++++++++++++ .../environments/rules/updater/basic.py | 2 +- agentverse/initialization.py | 11 +- agentverse/llms/openai.py | 2 +- agentverse/memory/chat_history_message.py | 7 +- agentverse/parser.py | 2 +- agentverse/tasks/__init__.py | 3 + .../output_parser.py | 24 +-- .../config.yaml | 72 +++++++++ .../output_parser.py | 36 +++++ .../nlp_classroom_3players/output_parser.py | 21 +-- .../output_parser.py | 6 +- .../output_parser.py | 23 +-- .../nlp_classroom_9players/output_parser.py | 21 +-- .../output_parser.py | 21 +-- 19 files changed, 347 insertions(+), 75 deletions(-) create mode 100644 agentverse/agents/tool_agent.py create mode 100644 agentverse/tasks/math_problem_2players_tools_nolc/config.yaml create mode 100644 agentverse/tasks/math_problem_2players_tools_nolc/output_parser.py diff --git a/agentverse/agents/__init__.py b/agentverse/agents/__init__.py index 1192972cf..c5965b14e 100644 --- a/agentverse/agents/__init__.py +++ b/agentverse/agents/__init__.py @@ -1,2 +1,8 @@ # from .agent import Agent -from .conversation_agent import BaseAgent +from agentverse.registry import Registry + +agent_registry = Registry(name="AgentRegistry") + +from .base import BaseAgent +from .conversation_agent import ConversationAgent +from .tool_agent import ToolAgent diff --git a/agentverse/agents/agent.py b/agentverse/agents/agent.py index f68210063..da8f8fa7a 100644 --- a/agentverse/agents/agent.py +++ b/agentverse/agents/agent.py @@ -25,7 +25,7 @@ # from agentverse.memory.memory import SummaryMemory from agentverse.message import Message -from agentverse.parser import OutputParseError +from agentverse.parser import OutputParserError try: import openai @@ -119,7 +119,7 @@ def step(self, env_description: str = "") -> Message: try: response = executor(input_arguments) break - except OutputParseError as e: + except OutputParserError as e: print(e) print("Retrying...") continue @@ -168,7 +168,7 @@ async def astep(self, env_description: str = "") -> Message: try: response = await executor.acall(input_arguments) break - except OutputParseError as e: + except OutputParserError as e: print(e) print("Retrying...") continue diff --git a/agentverse/agents/base.py b/agentverse/agents/base.py index e9dc83449..be7761148 100644 --- a/agentverse/agents/base.py +++ b/agentverse/agents/base.py @@ -56,7 +56,7 @@ def add_message_to_memory(self, message: Message) -> None: """Add a message to the memory""" pass - def get_receiver(self) -> List[str]: + def get_receiver(self) -> Set[str]: return self.receiver def set_receiver(self, receiver: Union[Set[str], str]) -> None: diff --git a/agentverse/agents/conversation_agent.py b/agentverse/agents/conversation_agent.py index d492fc543..ca9608ec1 100644 --- a/agentverse/agents/conversation_agent.py +++ b/agentverse/agents/conversation_agent.py @@ -5,10 +5,12 @@ from agentverse.llms import BaseChatModel, BaseCompletionModel, BaseLLM from agentverse.memory import BaseMemory from agentverse.message import Message -from agentverse.parser import OutputParseError, OutputParser +from agentverse.parser import OutputParserError, OutputParser from .base import BaseAgent +from . import agent_registry +@agent_registry.register("conversation") class ConversationAgent(BaseAgent): def step(self, env_description: str = "") -> Message: prompt = self._fill_prompt_template(env_description) @@ -70,14 +72,14 @@ def _fill_prompt_template(self, env_description: str = "") -> str: - ${agent_name}: the name of the agent - ${env_description}: the description of the environment - ${role_description}: the description of the role of the agent + - ${chat_history}: the chat history of the agent """ input_arguments = { "agent_name": self.name, "env_description": env_description, "role_description": self.role_description, + "chat_history": self.memory.to_string(add_sender_prefix=True), } - chat_history = self.memory.to_string(add_sender_prefix=True) - input_arguments["chat_history"] = chat_history return Template(self.prompt_template).safe_substitute(input_arguments) def add_message_to_memory(self, message: Message) -> None: diff --git a/agentverse/agents/tool_agent.py b/agentverse/agents/tool_agent.py new file mode 100644 index 000000000..13907999c --- /dev/null +++ b/agentverse/agents/tool_agent.py @@ -0,0 +1,147 @@ +import logging +from pydantic import Field +from string import Template +from typing import List, NamedTuple, Optional, Union + +from agentverse.llms import BaseChatModel, BaseCompletionModel, BaseLLM +from agentverse.memory import BaseMemory, ChatHistoryMemory +from agentverse.message import Message +from agentverse.parser import OutputParserError, OutputParser +from .base import BaseAgent, AgentAction, AgentFinish +from langchain.tools import BaseTool +from . import agent_registry + + +class ToolNotExistError(BaseException): + """Exception raised when parsing output from a command fails.""" + + def __init__(self, tool_name=""): + self.tool_name = tool_name + + def __str__(self): + return f"Tool {self.tool_name} does not exist." + + +@agent_registry.register("tool") +class ToolAgent(BaseAgent): + tools: List[BaseTool] = Field(default=[]) + tool_memory: BaseMemory = Field(default_factory=ChatHistoryMemory) + verbose: bool = Field(default=False) + + def step(self, env_description: str = "") -> Message: + prompt = self._fill_prompt_template(env_description) + parsed_response = None + for i in range(self.max_retry): + try: + response = self.llm.generate_response(prompt) + parsed_response = self.output_parser.parse(response) + break + except Exception as e: + logging.error(e) + logging.warning("Retrying...") + continue + + if parsed_response is None: + logging.error(f"{self.name} failed to generate valid response.") + + message = Message( + content="" + if parsed_response is None + else parsed_response.return_values["output"], + sender=self.name, + receiver=self.get_receiver(), + ) + return message + + async def astep(self, env_description: str = "") -> Message: + """Asynchronous version of step""" + parsed_response = None + tool_observation = [] + while True: + prompt = self._fill_prompt_template(env_description, tool_observation) + + for i in range(self.max_retry): + try: + response = await self.llm.agenerate_response(prompt) + import pdb + + pdb.set_trace() + parsed_response = self.output_parser.parse(response) + break + except Exception as e: + logging.error(e) + logging.warning("Retrying...") + continue + if parsed_response is None: + break + if isinstance(parsed_response, AgentAction): + observation = await self._acall_tool(parsed_response) + tool_observation.append( + parsed_response.log.strip() + + f"\nObservation: {observation.strip()}" + ) + elif isinstance(parsed_response, AgentFinish): + break + + if parsed_response is None: + logging.error(f"{self.name} failed to generate valid response.") + + message = Message( + content="" + if parsed_response is None + else parsed_response.return_values["output"], + sender=self.name, + receiver=self.get_receiver(), + ) + return message + + def _call_tool(self, response: NamedTuple) -> str: + """Call a tool and return the output""" + name_to_tool = {tool.name: tool for tool in self.tools} + tool = name_to_tool[response.tool] + return tool.run(response.tool_input, verbose=self.verbose) + + async def _acall_tool(self, response: NamedTuple) -> str: + """Call a tool and return the output""" + name_to_tool = {tool.name: tool for tool in self.tools} + if response.tool not in name_to_tool: + raise ToolNotExistError(response.tool) + tool = name_to_tool[response.tool] + observation = await tool.arun(response.tool_input, verbose=self.verbose) + return observation + + def _fill_prompt_template( + self, env_description: str = "", tool_observation: List[str] = [] + ) -> str: + """Fill the placeholders in the prompt template + + In the tool agent, these placeholders are supported: + - ${agent_name}: the name of the agent + - ${env_description}: the description of the environment + - ${role_description}: the description of the role of the agent + - ${chat_history}: the chat history of the agent + - ${tools}: the list of tools and their usage + - ${tool_names}: the list of tool names + - ${tool_observations}: the observation of the tool in this turn + """ + tools = "\n".join([f"> {tool.name}: {tool.description}" for tool in self.tools]) + tools = tools.replace("{{", "{").replace("}}", "}") + tool_names = ", ".join([tool.name for tool in self.tools]) + input_arguments = { + "agent_name": self.name, + "env_description": env_description, + "role_description": self.role_description, + "chat_history": self.memory.to_string(add_sender_prefix=True), + "tools": tools, + "tool_names": tool_names, + "tool_observation": "\n".join(tool_observation), + } + return Template(self.prompt_template).safe_substitute(input_arguments) + + def add_message_to_memory(self, message: Message) -> None: + self.memory.add_message(message) + + def reset(self) -> None: + """Reset the agent""" + self.memory.reset() + # TODO: reset receiver diff --git a/agentverse/environments/rules/updater/basic.py b/agentverse/environments/rules/updater/basic.py index ec73bec2e..0604a8b30 100644 --- a/agentverse/environments/rules/updater/basic.py +++ b/agentverse/environments/rules/updater/basic.py @@ -31,7 +31,7 @@ def update_memory(self, environment: BaseEnvironment): # If no one speaks in this turn. Add an empty message to all agents if not added: for agent in environment.agents: - agent.memory.add_user_message("[Silence]") + agent.memory.add_message(Message(content="[Silence]")) def add_tool_response( self, diff --git a/agentverse/initialization.py b/agentverse/initialization.py index 34040f8f8..158e20b19 100644 --- a/agentverse/initialization.py +++ b/agentverse/initialization.py @@ -16,8 +16,7 @@ from langchain.prompts import PromptTemplate # from agentverse.agents import Agent -from agentverse.agents.conversation_agent import BaseAgent -from agentverse.agents.conversation_agent import ConversationAgent +from agentverse.agents import agent_registry from agentverse.environments import BaseEnvironment, env_registry from agentverse.memory import ChatHistoryMemory @@ -61,17 +60,13 @@ def load_tools(tool_config: List[Dict]): def load_environment(env_config: Dict) -> BaseEnvironment: - env_type = env_config.pop("env_type", "base") + env_type = env_config.pop("env_type", "basic") return env_registry.build(env_type, **env_config) def load_agent(agent_config: Dict) -> langchainAgent: agent_type = agent_config.pop("agent_type", "conversation") - if agent_type == "conversation": - # agent = Agent.from_llm_and_tools(**agent_config) - agent = ConversationAgent(**agent_config) - else: - raise NotImplementedError("Agent type {} not found".format(agent_type)) + agent = agent_registry.build(agent_type, **agent_config) return agent diff --git a/agentverse/llms/openai.py b/agentverse/llms/openai.py index 4159e5bc1..d6394cf21 100644 --- a/agentverse/llms/openai.py +++ b/agentverse/llms/openai.py @@ -32,7 +32,7 @@ class OpenAIChatArgs(BaseModelArgs): temperature: float = Field(default=1.0) top_p: int = Field(default=1) n: int = Field(default=1) - stop: Optional[Union[str, List]] = Field(default=None) + stop: Optional[Union[str, List]] = Field(default=["\nObservation:"]) presence_penalty: int = Field(default=0) frequency_penalty: int = Field(default=0) diff --git a/agentverse/memory/chat_history_message.py b/agentverse/memory/chat_history_message.py index ac9bf9b9e..a9ac6482b 100644 --- a/agentverse/memory/chat_history_message.py +++ b/agentverse/memory/chat_history_message.py @@ -13,7 +13,12 @@ def add_message(self, message: Message) -> None: def to_string(self, add_sender_prefix: bool = False) -> str: if add_sender_prefix: return "\n".join( - [f"[{message.sender}]: {message.content}" for message in self.messages] + [ + f"[{message.sender}]: {message.content}" + if message.sender != "" + else message.content + for message in self.messages + ] ) else: return "\n".join([message.content for message in self.messages]) diff --git a/agentverse/parser.py b/agentverse/parser.py index a06e8fc64..6fcef5394 100644 --- a/agentverse/parser.py +++ b/agentverse/parser.py @@ -7,7 +7,7 @@ output_parser_registry = Registry(name="OutputParserRegistry") -class OutputParseError(BaseException): +class OutputParserError(BaseException): """Exception raised when parsing output from a command fails.""" def __init__(self, message): diff --git a/agentverse/tasks/__init__.py b/agentverse/tasks/__init__.py index 4806ccea3..5f09f6073 100644 --- a/agentverse/tasks/__init__.py +++ b/agentverse/tasks/__init__.py @@ -9,3 +9,6 @@ ) from .nlp_classroom_9players_group.output_parser import NlpClassroom9PlayersGroupParser from .nlp_classroom_3players_nolc.output_parser import NlpClassroom3PlayersNolcParser +from .math_problem_2players_tools_nolc.output_parser import ( + MathProblem2PlayersToolsNolcParser, +) diff --git a/agentverse/tasks/math_problem_2players_tools/output_parser.py b/agentverse/tasks/math_problem_2players_tools/output_parser.py index 35916b53c..94a31045d 100644 --- a/agentverse/tasks/math_problem_2players_tools/output_parser.py +++ b/agentverse/tasks/math_problem_2players_tools/output_parser.py @@ -6,24 +6,26 @@ from langchain.agents import AgentOutputParser from langchain.schema import AgentAction, AgentFinish -from agentverse.parser import OutputParseError, output_parser_registry +from agentverse.parser import OutputParserError, output_parser_registry @output_parser_registry.register("math_problem_2players_tools") class MathProblem2PlayersToolsParser(AgentOutputParser): def parse(self, text: str) -> Union[AgentAction, AgentFinish]: - cleaned_output = text.strip() - cleaned_output = re.sub(r'\n+', '\n', cleaned_output) - cleaned_output = cleaned_output.split('\n') - if not (len(cleaned_output) == 2 and - # cleaned_output[0].startswith("THOUGHT:") and - cleaned_output[0].startswith("ACTION:") and - cleaned_output[1].startswith("ACTION INPUT:")): + cleaned_output = re.sub(r"\n+", "\n", cleaned_output) + cleaned_output = cleaned_output.split("\n") + if not ( + len(cleaned_output) == 2 + and + # cleaned_output[0].startswith("THOUGHT:") and + cleaned_output[0].startswith("ACTION:") + and cleaned_output[1].startswith("ACTION INPUT:") + ): print(text) - raise OutputParseError("Output Format Error") - action = cleaned_output[0][len("ACTION:"):].strip() - action_input = cleaned_output[1][len("ACTION INPUT:"):].strip() + raise OutputParserError("Output Format Error") + action = cleaned_output[0][len("ACTION:") :].strip() + action_input = cleaned_output[1][len("ACTION INPUT:") :].strip() if action in ["Speak"]: return AgentFinish({"output": action_input}, text) else: diff --git a/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml b/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml new file mode 100644 index 000000000..c68d9a2e0 --- /dev/null +++ b/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml @@ -0,0 +1,72 @@ +prompts: + prompt: &prompt |- + You are participating in a math enthusiast event where you will compete in a turn-based arithmetic challenge with another player. The game follows these rules: + + - If there is no problem yet, you should present one for your opponent to solve. + - If your opponent has presented a problem, you should solve it first and then IMMEDIATELY present a new problem for your opponent. + - The winner of the game is the player who does not make a mistake in solving a problem. Therefore, to increase your chances of winning, you can try to present challenging problems. + + During the game, you can use the following tools when necessary: + ${tools} + + When responding, please use the following two-line format: + + [Option 1]: When you need to use a tool, output in the following format (omit the "[]" bracket when responding) + ACTION: (a tool name, it can be one of [${tool_names}]) + ACTION INPUT: (input arguments for the tool) + + [Option 2]: When you watn to speak, you can use the following format: + ACTION: Speak + ACTION INPUT: (what you want to say in a single line) + + Here is the conversation history + ${chat_history} + + Here is the observations from tool execution: + ${tool_observation} + + Now the game starts! ${role_description} You should give your action based on the above history. Remember, you should ALWAYS give your response STRICTLY in the above response format with the TWO lines start with "ACTION:" and "ACTION INPUT:" respectively! + +tools: &tools + - tool_name: "wolframalpha" + tool_url: "http://127.0.0.1:8079/tools/wolframalpha/" + +environment: + env_type: basic + max_turns: 10 + rule: + order: + type: sequential + visibility: + type: all + selector: + type: basic + updater: + type: basic + describer: + type: basic + +agents: + - agent_type: tool + name: Alice + role_description: "You are Alice." + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + llm_type: gpt-3.5-turbo + temperature: 0.7 + max_tokens: 250 + tools: *tools + + - agent_type: tool + name: Bob + role_description: "You are Bob." + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + llm_type: gpt-3.5-turbo + temperature: 0.7 + max_tokens: 250 + tools: *tools diff --git a/agentverse/tasks/math_problem_2players_tools_nolc/output_parser.py b/agentverse/tasks/math_problem_2players_tools_nolc/output_parser.py new file mode 100644 index 000000000..d6940581e --- /dev/null +++ b/agentverse/tasks/math_problem_2players_tools_nolc/output_parser.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +import re +from typing import Union + +from langchain.agents import AgentOutputParser + +# from langchain.schema import AgentAction, AgentFinish + +from agentverse.parser import OutputParserError, output_parser_registry, OutputParser +from agentverse.llms.base import LLMResult +from agentverse.agents.base import AgentAction, AgentFinish + + +@output_parser_registry.register("math_problem_2players_tools_nolc") +class MathProblem2PlayersToolsNolcParser(OutputParser): + def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]: + text = output.content + cleaned_output = text.strip() + cleaned_output = re.sub(r"\n+", "\n", cleaned_output) + cleaned_output = cleaned_output.split("\n") + if not ( + len(cleaned_output) == 2 + and + # cleaned_output[0].startswith("THOUGHT:") and + cleaned_output[0].startswith("ACTION:") + and cleaned_output[1].startswith("ACTION INPUT:") + ): + print(text) + raise OutputParserError("Output Format Error") + action = cleaned_output[0][len("ACTION:") :].strip() + action_input = cleaned_output[1][len("ACTION INPUT:") :].strip() + if action == "Speak": + return AgentFinish({"output": action_input}, text) + else: + return AgentAction(action, action_input, text) diff --git a/agentverse/tasks/nlp_classroom_3players/output_parser.py b/agentverse/tasks/nlp_classroom_3players/output_parser.py index dbe090368..bff74231c 100644 --- a/agentverse/tasks/nlp_classroom_3players/output_parser.py +++ b/agentverse/tasks/nlp_classroom_3players/output_parser.py @@ -6,22 +6,23 @@ from langchain.agents import AgentOutputParser from langchain.schema import AgentAction, AgentFinish -from agentverse.parser import OutputParseError, output_parser_registry +from agentverse.parser import OutputParserError, output_parser_registry @output_parser_registry.register("nlp_classroom_3players") class NlpClassroom3PlayersParser(AgentOutputParser): def parse(self, text: str) -> Union[AgentAction, AgentFinish]: - cleaned_output = text.strip() - cleaned_output = re.sub(r'\n+', '\n', cleaned_output) - cleaned_output = cleaned_output.split('\n') - if not (len(cleaned_output) == 2 and - cleaned_output[0].startswith("Action:") and - cleaned_output[1].startswith("Action Input:")): - raise OutputParseError("Output Format Error") - action = cleaned_output[0][len("Action:"):].strip() - action_input = cleaned_output[1][len("Action Input:"):].strip() + cleaned_output = re.sub(r"\n+", "\n", cleaned_output) + cleaned_output = cleaned_output.split("\n") + if not ( + len(cleaned_output) == 2 + and cleaned_output[0].startswith("Action:") + and cleaned_output[1].startswith("Action Input:") + ): + raise OutputParserError("Output Format Error") + action = cleaned_output[0][len("Action:") :].strip() + action_input = cleaned_output[1][len("Action Input:") :].strip() if action in ["Speak", "Listen"]: return AgentFinish({"output": action_input}, text) else: diff --git a/agentverse/tasks/nlp_classroom_3players_nolc/output_parser.py b/agentverse/tasks/nlp_classroom_3players_nolc/output_parser.py index 9ffd2f435..6c086f977 100644 --- a/agentverse/tasks/nlp_classroom_3players_nolc/output_parser.py +++ b/agentverse/tasks/nlp_classroom_3players_nolc/output_parser.py @@ -7,7 +7,7 @@ from agentverse.parser import OutputParser, LLMResult from langchain.schema import AgentAction, AgentFinish -from agentverse.parser import OutputParseError, output_parser_registry +from agentverse.parser import OutputParserError, output_parser_registry @output_parser_registry.register("nlp_classroom_3players_nolc") @@ -22,10 +22,10 @@ def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]: and cleaned_output[0].startswith("Action:") and cleaned_output[1].startswith("Action Input:") ): - raise OutputParseError(text) + raise OutputParserError(text) action = cleaned_output[0][len("Action:") :].strip() action_input = cleaned_output[1][len("Action Input:") :].strip() if action == "Speak": return AgentFinish({"output": action_input}, text) else: - raise OutputParseError(text) + raise OutputParserError(text) diff --git a/agentverse/tasks/nlp_classroom_3players_withtool/output_parser.py b/agentverse/tasks/nlp_classroom_3players_withtool/output_parser.py index f38075f2f..fbdddd0dd 100644 --- a/agentverse/tasks/nlp_classroom_3players_withtool/output_parser.py +++ b/agentverse/tasks/nlp_classroom_3players_withtool/output_parser.py @@ -6,23 +6,24 @@ from langchain.agents import AgentOutputParser from langchain.schema import AgentAction, AgentFinish -from agentverse.parser import OutputParseError, output_parser_registry +from agentverse.parser import OutputParserError, output_parser_registry @output_parser_registry.register("nlp_classroom_3players_withtool") class NlpClassroom3PlayersWithtoolParser(AgentOutputParser): def parse(self, text: str) -> Union[AgentAction, AgentFinish]: - cleaned_output = text.strip() - cleaned_output = re.sub(r'\n+', '\n', cleaned_output) - cleaned_output = cleaned_output.split('\n') - if not (len(cleaned_output) == 3 and - cleaned_output[0].startswith("Thought:") and - cleaned_output[1].startswith("Action:") and - cleaned_output[2].startswith("Action Input:")): - raise OutputParseError(text) - action = cleaned_output[1][len("Action:"):].strip() - action_input = cleaned_output[2][len("Action Input:"):].strip() + cleaned_output = re.sub(r"\n+", "\n", cleaned_output) + cleaned_output = cleaned_output.split("\n") + if not ( + len(cleaned_output) == 3 + and cleaned_output[0].startswith("Thought:") + and cleaned_output[1].startswith("Action:") + and cleaned_output[2].startswith("Action Input:") + ): + raise OutputParserError(text) + action = cleaned_output[1][len("Action:") :].strip() + action_input = cleaned_output[2][len("Action Input:") :].strip() if action in ["Speak"]: return AgentFinish({"output": action_input}, text) elif action == "CallOn": diff --git a/agentverse/tasks/nlp_classroom_9players/output_parser.py b/agentverse/tasks/nlp_classroom_9players/output_parser.py index c2f14c71d..59d0ad781 100644 --- a/agentverse/tasks/nlp_classroom_9players/output_parser.py +++ b/agentverse/tasks/nlp_classroom_9players/output_parser.py @@ -6,22 +6,23 @@ from langchain.agents import AgentOutputParser from langchain.schema import AgentAction, AgentFinish -from agentverse.parser import OutputParseError, output_parser_registry +from agentverse.parser import OutputParserError, output_parser_registry @output_parser_registry.register("nlp_classroom_9players") class NlpClassroom9PlayersParser(AgentOutputParser): def parse(self, text: str) -> Union[AgentAction, AgentFinish]: - cleaned_output = text.strip() - cleaned_output = re.sub(r'\n+', '\n', cleaned_output) - cleaned_output = cleaned_output.split('\n') - if not (len(cleaned_output) == 2 and - cleaned_output[0].startswith("Action:") and - cleaned_output[1].startswith("Action Input:")): - raise OutputParseError(text) - action = cleaned_output[0][len("Action:"):].strip() - action_input = cleaned_output[1][len("Action Input:"):].strip() + cleaned_output = re.sub(r"\n+", "\n", cleaned_output) + cleaned_output = cleaned_output.split("\n") + if not ( + len(cleaned_output) == 2 + and cleaned_output[0].startswith("Action:") + and cleaned_output[1].startswith("Action Input:") + ): + raise OutputParserError(text) + action = cleaned_output[0][len("Action:") :].strip() + action_input = cleaned_output[1][len("Action Input:") :].strip() if action in ["Speak"]: return AgentFinish({"output": action_input}, text) elif action == "CallOn": diff --git a/agentverse/tasks/nlp_classroom_9players_group/output_parser.py b/agentverse/tasks/nlp_classroom_9players_group/output_parser.py index 4ae555ef1..fb103b49c 100644 --- a/agentverse/tasks/nlp_classroom_9players_group/output_parser.py +++ b/agentverse/tasks/nlp_classroom_9players_group/output_parser.py @@ -6,22 +6,23 @@ from langchain.agents import AgentOutputParser from langchain.schema import AgentAction, AgentFinish -from agentverse.parser import OutputParseError, output_parser_registry +from agentverse.parser import OutputParserError, output_parser_registry @output_parser_registry.register("nlp_classroom_9players_group") class NlpClassroom9PlayersGroupParser(AgentOutputParser): def parse(self, text: str) -> Union[AgentAction, AgentFinish]: - cleaned_output = text.strip() - cleaned_output = re.sub(r'\n+', '\n', cleaned_output) - cleaned_output = cleaned_output.split('\n') - if not (len(cleaned_output) == 2 and - cleaned_output[0].startswith("Action:") and - cleaned_output[1].startswith("Action Input:")): - raise OutputParseError(text) - action = cleaned_output[0][len("Action:"):].strip() - action_input = cleaned_output[1][len("Action Input:"):].strip() + cleaned_output = re.sub(r"\n+", "\n", cleaned_output) + cleaned_output = cleaned_output.split("\n") + if not ( + len(cleaned_output) == 2 + and cleaned_output[0].startswith("Action:") + and cleaned_output[1].startswith("Action Input:") + ): + raise OutputParserError(text) + action = cleaned_output[0][len("Action:") :].strip() + action_input = cleaned_output[1][len("Action Input:") :].strip() if action == "Speak": return AgentFinish({"output": action_input}, text) elif action in ["CallOn", "RaiseHand", "GroupDiscuss"]: From 0cd7da1b7760789ea28c6db2e18fcae1c1632cef Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Wed, 17 May 2023 16:57:28 +0800 Subject: [PATCH 05/50] fix bugs in tool agent --- agentverse/agents/tool_agent.py | 66 +++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/agentverse/agents/tool_agent.py b/agentverse/agents/tool_agent.py index 13907999c..c4100b6de 100644 --- a/agentverse/agents/tool_agent.py +++ b/agentverse/agents/tool_agent.py @@ -29,21 +29,34 @@ class ToolAgent(BaseAgent): verbose: bool = Field(default=False) def step(self, env_description: str = "") -> Message: - prompt = self._fill_prompt_template(env_description) parsed_response = None - for i in range(self.max_retry): - try: - response = self.llm.generate_response(prompt) - parsed_response = self.output_parser.parse(response) + tool_observation = [self.tool_memory.to_string()] + while True: + prompt = self._fill_prompt_template(env_description, tool_observation) + + for i in range(self.max_retry): + try: + response = self.llm.generate_response(prompt) + parsed_response = self.output_parser.parse(response) + if isinstance(parsed_response, AgentAction): + observation = self._call_tool(parsed_response) + tool_observation.append( + parsed_response.log.strip() + + f"\nObservation: {observation.strip()}" + ) + break + except BaseException as e: + logging.error(e) + logging.warning("Retrying...") + continue + if parsed_response is None or isinstance(parsed_response, AgentFinish): break - except Exception as e: - logging.error(e) - logging.warning("Retrying...") - continue if parsed_response is None: logging.error(f"{self.name} failed to generate valid response.") + self._update_tool_memory(tool_observation) + message = Message( content="" if parsed_response is None @@ -56,36 +69,33 @@ def step(self, env_description: str = "") -> Message: async def astep(self, env_description: str = "") -> Message: """Asynchronous version of step""" parsed_response = None - tool_observation = [] + tool_observation = [self.tool_memory.to_string()] while True: prompt = self._fill_prompt_template(env_description, tool_observation) for i in range(self.max_retry): try: response = await self.llm.agenerate_response(prompt) - import pdb - - pdb.set_trace() parsed_response = self.output_parser.parse(response) + if isinstance(parsed_response, AgentAction): + observation = await self._acall_tool(parsed_response) + tool_observation.append( + parsed_response.log.strip() + + f"\nObservation: {observation.strip()}" + ) break - except Exception as e: + except BaseException as e: logging.error(e) logging.warning("Retrying...") continue - if parsed_response is None: - break - if isinstance(parsed_response, AgentAction): - observation = await self._acall_tool(parsed_response) - tool_observation.append( - parsed_response.log.strip() - + f"\nObservation: {observation.strip()}" - ) - elif isinstance(parsed_response, AgentFinish): + if parsed_response is None or isinstance(parsed_response, AgentFinish): break if parsed_response is None: logging.error(f"{self.name} failed to generate valid response.") + self._update_tool_memory(tool_observation) + message = Message( content="" if parsed_response is None @@ -98,8 +108,11 @@ async def astep(self, env_description: str = "") -> Message: def _call_tool(self, response: NamedTuple) -> str: """Call a tool and return the output""" name_to_tool = {tool.name: tool for tool in self.tools} + if response.tool not in name_to_tool: + raise ToolNotExistError(response.tool) tool = name_to_tool[response.tool] - return tool.run(response.tool_input, verbose=self.verbose) + observation = tool.arun(response.tool_input, verbose=self.verbose) + return observation async def _acall_tool(self, response: NamedTuple) -> str: """Call a tool and return the output""" @@ -110,6 +123,11 @@ async def _acall_tool(self, response: NamedTuple) -> str: observation = await tool.arun(response.tool_input, verbose=self.verbose) return observation + def _update_tool_memory(self, tool_observation: List[str]): + """Update the memory of the tool""" + for observation in tool_observation[1:]: + self.tool_memory.add_message(Message(content=observation)) + def _fill_prompt_template( self, env_description: str = "", tool_observation: List[str] = [] ) -> str: From aa4666e86c49f461a972803fb18bc47b4eb9925c Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Wed, 17 May 2023 17:44:30 +0800 Subject: [PATCH 06/50] add registry for memory and agent --- agentverse/agents/tool_agent.py | 6 ++++ agentverse/initialization.py | 31 +++++++------------ agentverse/llms/__init__.py | 4 +++ agentverse/llms/openai.py | 13 ++++++-- agentverse/memory/__init__.py | 4 +++ agentverse/memory/chat_history_message.py | 2 ++ .../config.yaml | 10 ++++-- 7 files changed, 45 insertions(+), 25 deletions(-) diff --git a/agentverse/agents/tool_agent.py b/agentverse/agents/tool_agent.py index c4100b6de..1a4158586 100644 --- a/agentverse/agents/tool_agent.py +++ b/agentverse/agents/tool_agent.py @@ -75,6 +75,9 @@ async def astep(self, env_description: str = "") -> Message: for i in range(self.max_retry): try: + import pdb + + pdb.set_trace() response = await self.llm.agenerate_response(prompt) parsed_response = self.output_parser.parse(response) if isinstance(parsed_response, AgentAction): @@ -112,6 +115,9 @@ def _call_tool(self, response: NamedTuple) -> str: raise ToolNotExistError(response.tool) tool = name_to_tool[response.tool] observation = tool.arun(response.tool_input, verbose=self.verbose) + import pdb + + pdb.set_trace() return observation async def _acall_tool(self, response: NamedTuple) -> str: diff --git a/agentverse/initialization.py b/agentverse/initialization.py index 158e20b19..c30740fdc 100644 --- a/agentverse/initialization.py +++ b/agentverse/initialization.py @@ -9,7 +9,7 @@ # from langchain.chat_models.base import BaseChatModel # from langchain.llms import OpenAI # from langchain.llms.base import BaseLLM -from agentverse.llms import OpenAICompletion, OpenAIChat +from agentverse.llms import OpenAICompletion, OpenAIChat, llm_registry # from langchain.memory import ChatMessageHistory from langchain.memory.prompt import _DEFAULT_SUMMARIZER_TEMPLATE @@ -18,7 +18,7 @@ # from agentverse.agents import Agent from agentverse.agents import agent_registry from agentverse.environments import BaseEnvironment, env_registry -from agentverse.memory import ChatHistoryMemory +from agentverse.memory import memory_registry # from agentverse.memory.memory import SummaryMemory from agentverse.parser import output_parser_registry @@ -26,27 +26,18 @@ def load_llm(llm_config: Dict): llm_type = llm_config.pop("llm_type", "text-davinci-003") - if llm_type == "gpt-3.5-turbo": - return OpenAIChat(**llm_config) - elif llm_type == "text-davinci-003": - return OpenAICompletion(**llm_config) - else: - raise NotImplementedError("LLM type {} not implemented".format(llm_type)) + # if llm_type == "gpt-3.5-turbo": + # return OpenAIChat(**llm_config) + # elif llm_type == "text-davinci-003": + # return OpenAICompletion(**llm_config) + # else: + # raise NotImplementedError("LLM type {} not implemented".format(llm_type)) + return llm_registry.build(llm_type, **llm_config) def load_memory(memory_config: Dict): - memory_type = memory_config.pop("memory_type", "chat_message_history") - if memory_type == "chat_history": - return ChatHistoryMemory() - elif memory_type == "summary": - llm = load_llm(memory_config.pop("llm", "text-davinci-003")) - prompt = memory_config.pop("prompt", _DEFAULT_SUMMARIZER_TEMPLATE) - memory_config["prompt"] = PromptTemplate( - input_variables=["summary", "new_lines"], template=prompt - ) - return SummaryMemory(llm=llm, **memory_config) - else: - raise NotImplementedError("Memory type {} not implemented".format(memory_type)) + memory_type = memory_config.pop("memory_type", "chat_history") + return memory_registry.build(memory_type, **memory_config) def load_tools(tool_config: List[Dict]): diff --git a/agentverse/llms/__init__.py b/agentverse/llms/__init__.py index eeebbc61b..c7a2b244c 100644 --- a/agentverse/llms/__init__.py +++ b/agentverse/llms/__init__.py @@ -1,2 +1,6 @@ +from agentverse.registry import Registry + +llm_registry = Registry(name="LLMRegistry") + from .base import BaseLLM, BaseChatModel, BaseCompletionModel from .openai import OpenAIChat, OpenAICompletion diff --git a/agentverse/llms/openai.py b/agentverse/llms/openai.py index d6394cf21..af2e666d2 100644 --- a/agentverse/llms/openai.py +++ b/agentverse/llms/openai.py @@ -5,6 +5,7 @@ from agentverse.llms.base import LLMResult from .base import BaseModelArgs, BaseChatModel, BaseCompletionModel +from . import llm_registry try: import openai @@ -32,7 +33,7 @@ class OpenAIChatArgs(BaseModelArgs): temperature: float = Field(default=1.0) top_p: int = Field(default=1) n: int = Field(default=1) - stop: Optional[Union[str, List]] = Field(default=["\nObservation:"]) + stop: Optional[Union[str, List]] = Field(default=None) presence_penalty: int = Field(default=0) frequency_penalty: int = Field(default=0) @@ -43,6 +44,7 @@ class OpenAICompletionArgs(OpenAIChatArgs): best_of: int = Field(default=1) +@llm_registry.register("text-davinci-003") class OpenAICompletion(BaseCompletionModel): args: OpenAICompletionArgs = Field(default_factory=OpenAICompletionArgs) @@ -74,6 +76,8 @@ async def agenerate_response(self, prompt: str) -> LLMResult: ) +@llm_registry.register("gpt-3.5-turbo") +@llm_registry.register("gpt-4") class OpenAIChat(BaseChatModel): args: OpenAIChatArgs = Field(default_factory=OpenAIChatArgs) @@ -91,7 +95,12 @@ def _construct_messages(self, prompt: str): def generate_response(self, prompt: str) -> LLMResult: messages = self._construct_messages(prompt) - response = openai.ChatCompletion.create(messages=messages, **self.args.dict()) + try: + response = openai.ChatCompletion.create( + messages=messages, **self.args.dict() + ) + except OpenAIError as error: + raise return LLMResult( content=response["choices"][0]["message"]["content"], send_tokens=response["usage"]["prompt_tokens"], diff --git a/agentverse/memory/__init__.py b/agentverse/memory/__init__.py index 3b8736868..8befbb77e 100644 --- a/agentverse/memory/__init__.py +++ b/agentverse/memory/__init__.py @@ -1,2 +1,6 @@ +from agentverse.registry import Registry + +memory_registry = Registry(name="MemoryRegistry") + from .base import BaseMemory from .chat_history_message import ChatHistoryMemory diff --git a/agentverse/memory/chat_history_message.py b/agentverse/memory/chat_history_message.py index a9ac6482b..40fec91dc 100644 --- a/agentverse/memory/chat_history_message.py +++ b/agentverse/memory/chat_history_message.py @@ -2,8 +2,10 @@ from typing import List from pydantic import Field from agentverse.message import Message +from . import memory_registry +@memory_registry.register("chat_history") class ChatHistoryMemory(BaseMemory): messages: List[Message] = Field(default=[]) diff --git a/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml b/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml index c68d9a2e0..d684fb2e2 100644 --- a/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml +++ b/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml @@ -15,7 +15,7 @@ prompts: ACTION: (a tool name, it can be one of [${tool_names}]) ACTION INPUT: (input arguments for the tool) - [Option 2]: When you watn to speak, you can use the following format: + [Option 2]: When you want to speak, you can use the following format: ACTION: Speak ACTION INPUT: (what you want to say in a single line) @@ -53,8 +53,10 @@ agents: memory: memory_type: chat_history prompt_template: *prompt + verbose: true llm: - llm_type: gpt-3.5-turbo + llm_type: text-davinci-003 + model: text-davinci-003 temperature: 0.7 max_tokens: 250 tools: *tools @@ -65,8 +67,10 @@ agents: memory: memory_type: chat_history prompt_template: *prompt + verbose: true llm: - llm_type: gpt-3.5-turbo + llm_type: text-davinci-003 + model: text-davinci-003 temperature: 0.7 max_tokens: 250 tools: *tools From 87d07188900ba1d488a161904197c7c20fc2fae6 Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Wed, 17 May 2023 21:08:05 +0800 Subject: [PATCH 07/50] add summary memory --- agentverse/agents/base.py | 17 +- agentverse/agents/conversation_agent.py | 4 +- agentverse/agents/tool_agent.py | 20 +- agentverse/environments/base.py | 11 +- agentverse/environments/rules/updater/base.py | 3 - .../environments/rules/updater/basic.py | 14 +- agentverse/initialization.py | 12 -- agentverse/memory/__init__.py | 3 +- agentverse/memory/base.py | 2 +- ...hat_history_message.py => chat_history.py} | 5 +- agentverse/memory/summary.py | 90 ++++++++ agentverse/message.py | 3 +- agentverse/tasks/__init__.py | 3 + .../output_parser.py | 4 +- .../config.yaml | 53 +++++ .../output_parser.py | 2 +- .../nlp_classroom_3players/output_parser.py | 4 +- .../output_parser.py | 4 +- .../output_parser.py | 4 +- .../config.yaml | 197 ++++++++++++++++++ .../output_parser.py | 40 ++++ .../nlp_classroom_9players/output_parser.py | 4 +- .../output_parser.py | 4 +- agentverse/utils.py | 16 ++ 24 files changed, 453 insertions(+), 66 deletions(-) rename agentverse/memory/{chat_history_message.py => chat_history.py} (85%) create mode 100644 agentverse/memory/summary.py create mode 100644 agentverse/tasks/nlp_classroom_3players_withtool_nolc/config.yaml create mode 100644 agentverse/tasks/nlp_classroom_3players_withtool_nolc/output_parser.py create mode 100644 agentverse/utils.py diff --git a/agentverse/agents/base.py b/agentverse/agents/base.py index be7761148..18663a47d 100644 --- a/agentverse/agents/base.py +++ b/agentverse/agents/base.py @@ -10,21 +10,6 @@ from agentverse.parser import OutputParser -class AgentAction(NamedTuple): - """Agent's action to take.""" - - tool: str - tool_input: Union[str, dict] - log: str - - -class AgentFinish(NamedTuple): - """Agent's return value.""" - - return_values: dict - log: str - - class BaseAgent(BaseModel): name: str llm: BaseLLM @@ -52,7 +37,7 @@ def reset(self) -> None: pass @abstractmethod - def add_message_to_memory(self, message: Message) -> None: + def add_message_to_memory(self, messages: List[Message]) -> None: """Add a message to the memory""" pass diff --git a/agentverse/agents/conversation_agent.py b/agentverse/agents/conversation_agent.py index ca9608ec1..045d96230 100644 --- a/agentverse/agents/conversation_agent.py +++ b/agentverse/agents/conversation_agent.py @@ -82,8 +82,8 @@ def _fill_prompt_template(self, env_description: str = "") -> str: } return Template(self.prompt_template).safe_substitute(input_arguments) - def add_message_to_memory(self, message: Message) -> None: - self.memory.add_message(message) + def add_message_to_memory(self, messages: List[Message]) -> None: + self.memory.add_message(messages) def reset(self) -> None: """Reset the agent""" diff --git a/agentverse/agents/tool_agent.py b/agentverse/agents/tool_agent.py index 1a4158586..efb89b4f8 100644 --- a/agentverse/agents/tool_agent.py +++ b/agentverse/agents/tool_agent.py @@ -7,8 +7,9 @@ from agentverse.memory import BaseMemory, ChatHistoryMemory from agentverse.message import Message from agentverse.parser import OutputParserError, OutputParser -from .base import BaseAgent, AgentAction, AgentFinish +from .base import BaseAgent from langchain.tools import BaseTool +from agentverse.utils import AgentAction, AgentFinish from . import agent_registry @@ -114,10 +115,7 @@ def _call_tool(self, response: NamedTuple) -> str: if response.tool not in name_to_tool: raise ToolNotExistError(response.tool) tool = name_to_tool[response.tool] - observation = tool.arun(response.tool_input, verbose=self.verbose) - import pdb - - pdb.set_trace() + observation = tool.run(response.tool_input, verbose=self.verbose) return observation async def _acall_tool(self, response: NamedTuple) -> str: @@ -131,8 +129,12 @@ async def _acall_tool(self, response: NamedTuple) -> str: def _update_tool_memory(self, tool_observation: List[str]): """Update the memory of the tool""" - for observation in tool_observation[1:]: - self.tool_memory.add_message(Message(content=observation)) + if len(tool_observation) == 1: + return + messages = [ + Message(content=observation) for observation in tool_observation[1:] + ] + self.tool_memory.add_message(messages) def _fill_prompt_template( self, env_description: str = "", tool_observation: List[str] = [] @@ -162,8 +164,8 @@ def _fill_prompt_template( } return Template(self.prompt_template).safe_substitute(input_arguments) - def add_message_to_memory(self, message: Message) -> None: - self.memory.add_message(message) + def add_message_to_memory(self, messages: List[Message]) -> None: + self.memory.add_message(messages) def reset(self) -> None: """Reset the agent""" diff --git a/agentverse/environments/base.py b/agentverse/environments/base.py index 27c61b530..ea471ee09 100644 --- a/agentverse/environments/base.py +++ b/agentverse/environments/base.py @@ -1,16 +1,17 @@ +from __future__ import annotations import asyncio import logging -from typing import Any, Dict, List +from typing import Any, Dict, List, TYPE_CHECKING from abc import abstractmethod from pydantic import BaseModel # from agentverse.agents.agent import Agent -from agentverse.agents.conversation_agent import BaseAgent -from agentverse.environments.rules.base import Rule -from agentverse.message import Message -from . import env_registry as EnvironmentRegistry +if TYPE_CHECKING: + from agentverse.environments.rules.base import Rule + from agentverse.message import Message + from agentverse.agents.base import BaseAgent class BaseEnvironment(BaseModel): diff --git a/agentverse/environments/rules/updater/base.py b/agentverse/environments/rules/updater/base.py index 8dc593a16..abd212875 100644 --- a/agentverse/environments/rules/updater/base.py +++ b/agentverse/environments/rules/updater/base.py @@ -2,12 +2,9 @@ from typing import TYPE_CHECKING, List, Tuple -from langchain.schema import AgentAction from pydantic import BaseModel # from agentverse.agents import Agent -from agentverse.agents import BaseAgent -from agentverse.message import Message from abc import abstractmethod from . import updater_registry as UpdaterRegistry diff --git a/agentverse/environments/rules/updater/basic.py b/agentverse/environments/rules/updater/basic.py index 0604a8b30..36c59a3ec 100644 --- a/agentverse/environments/rules/updater/basic.py +++ b/agentverse/environments/rules/updater/basic.py @@ -4,11 +4,11 @@ from . import updater_registry as UpdaterRegistry from .base import BaseUpdater -from agentverse.agents import BaseAgent -from agentverse.message import AgentAction, Message +from agentverse.message import Message if TYPE_CHECKING: from agentverse.environments import BaseEnvironment + from agentverse.agents import BaseAgent @UpdaterRegistry.register("basic") @@ -31,19 +31,19 @@ def update_memory(self, environment: BaseEnvironment): # If no one speaks in this turn. Add an empty message to all agents if not added: for agent in environment.agents: - agent.memory.add_message(Message(content="[Silence]")) + agent.add_message_to_memory([Message(content="[Silence]")]) def add_tool_response( self, name: str, agents: List[BaseAgent], - tool_response: List[Tuple[AgentAction, str]], + tool_response: List[str], ): for agent in agents: if agent.name != name: continue if agent.tool_memory is not None: - agent.tool_memory.save_context(tool_response) + agent.tool_memory.add_message(tool_response) break def add_message_to_all_agents( @@ -52,14 +52,14 @@ def add_message_to_all_agents( if "all" in message.receiver: # If receiver is all, then add the message to all agents for agent in agents: - agent.add_message_to_memory(message) + agent.add_message_to_memory([message]) return True else: # If receiver is not all, then add the message to the specified agents receiver_set = message.receiver for agent in agents: if agent.name in receiver_set: - agent.add_message_to_memory(message) + agent.add_message_to_memory([message]) receiver_set.remove(agent.name) if len(receiver_set) > 0: missing_receiver = ", ".join(list(receiver_set)) diff --git a/agentverse/initialization.py b/agentverse/initialization.py index c30740fdc..62118b4e7 100644 --- a/agentverse/initialization.py +++ b/agentverse/initialization.py @@ -26,12 +26,6 @@ def load_llm(llm_config: Dict): llm_type = llm_config.pop("llm_type", "text-davinci-003") - # if llm_type == "gpt-3.5-turbo": - # return OpenAIChat(**llm_config) - # elif llm_type == "text-davinci-003": - # return OpenAICompletion(**llm_config) - # else: - # raise NotImplementedError("LLM type {} not implemented".format(llm_type)) return llm_registry.build(llm_type, **llm_config) @@ -93,12 +87,6 @@ def prepare_task_config(task): agent_configs["llm"] = llm agent_configs["tools"] = load_tools(agent_configs.get("tools", [])) - # tool_strings = "\n".join( - # [f"> {tool.name}: {tool.description}" for tool in agent_configs["tools"]] - # ) - - # tool_names = ", ".join([tool.name for tool in agent_configs["tools"]]) - agent_configs["output_parser"] = task_config["output_parser"] return task_config diff --git a/agentverse/memory/__init__.py b/agentverse/memory/__init__.py index 8befbb77e..2ced03877 100644 --- a/agentverse/memory/__init__.py +++ b/agentverse/memory/__init__.py @@ -3,4 +3,5 @@ memory_registry = Registry(name="MemoryRegistry") from .base import BaseMemory -from .chat_history_message import ChatHistoryMemory +from .chat_history import ChatHistoryMemory +from .summary import SummaryMemory diff --git a/agentverse/memory/base.py b/agentverse/memory/base.py index 5e86bad7f..be83ad94a 100644 --- a/agentverse/memory/base.py +++ b/agentverse/memory/base.py @@ -6,7 +6,7 @@ class BaseMemory(BaseModel): @abstractmethod - def add_message(self, message: Message) -> None: + def add_message(self, messages: List[Message]) -> None: pass @abstractmethod diff --git a/agentverse/memory/chat_history_message.py b/agentverse/memory/chat_history.py similarity index 85% rename from agentverse/memory/chat_history_message.py rename to agentverse/memory/chat_history.py index 40fec91dc..d60b1dc98 100644 --- a/agentverse/memory/chat_history_message.py +++ b/agentverse/memory/chat_history.py @@ -9,8 +9,9 @@ class ChatHistoryMemory(BaseMemory): messages: List[Message] = Field(default=[]) - def add_message(self, message: Message) -> None: - self.messages.append(message) + def add_message(self, messages: List[Message]) -> None: + for message in messages: + self.messages.append(message) def to_string(self, add_sender_prefix: bool = False) -> str: if add_sender_prefix: diff --git a/agentverse/memory/summary.py b/agentverse/memory/summary.py new file mode 100644 index 000000000..04ac09fe7 --- /dev/null +++ b/agentverse/memory/summary.py @@ -0,0 +1,90 @@ +from string import Template +from typing import List +import re + +from pydantic import Field, validator + +from agentverse.llms.base import BaseLLM +from agentverse.message import Message +from agentverse.initialization import load_llm + +from . import memory_registry +from .base import BaseMemory + + +@memory_registry.register("summary") +class SummaryMemory(BaseMemory): + llm: BaseLLM + messages: List[Message] = Field(default=[]) + buffer: str = Field(default="") + recursive: bool = Field(default=False) + prompt_template: str = Field(default="") + + def __init__(self, *args, **kwargs): + llm_config = kwargs.pop("llm") + llm = load_llm(llm_config) + super().__init__(llm=llm, *args, **kwargs) + + @validator("prompt_template") + def check_prompt_template(cls, v, values): + """Check if the prompt template is valid. + When recursive is True, the prompt template should contain the following arguments: + - $summary: The summary so far. + - $new_lines: The new lines to be added to the summary. + + Otherwise, the prompt template should only contain $new_lines + """ + recursive = values.get("recursive") + summary_pat = re.compile(r"\$\{?summary\}?") + new_lines_pat = re.compile(r"\$\{?new_lines\}?") + if recursive: + if not summary_pat.search(v): + raise ValueError( + "When recursive is True, the prompt template should contain $summary." + ) + if not new_lines_pat.search(v): + raise ValueError( + "When recursive is True, the prompt template should contain $new_lines." + ) + else: + if summary_pat.search(v): + raise ValueError( + "When recursive is False, the prompt template should not contain $summary." + ) + if not new_lines_pat.search(v): + raise ValueError( + "When recursive is False, the prompt template should contain $new_lines." + ) + return v + + def add_message(self, messages: List[Message]) -> None: + import pdb + + pdb.set_trace() + new_lines = "\n".join([message.content for message in messages]) + self.update_buffer(new_lines) + + def update_buffer(self, new_message: str): + prompt = self._fill_in_prompt_template(new_message) + response = self.llm.generate_response(prompt) + if self.recursive: + self.buffer = response.content + else: + self.buffer = "\n" + response.content + + def _fill_in_prompt_template(self, new_lines: str) -> str: + """Fill in the prompt template with the given arguments. + + SummaryMemory supports the following arguments: + - summary: The summary so far. + - new_lines: The new lines to be added to the summary. + """ + input_arguments = {"summary": self.buffer, "new_lines": new_lines} + return Template(self.prompt_template).safe_substitute(input_arguments) + + def to_string(self, *args, **kwargs) -> str: + return self.buffer + + def reset(self) -> None: + self.messages = [] + self.buffer = "" diff --git a/agentverse/message.py b/agentverse/message.py index 1c92ec292..f1de1889b 100644 --- a/agentverse/message.py +++ b/agentverse/message.py @@ -1,7 +1,8 @@ from pydantic import BaseModel, Field from typing import List, Tuple, Set -from langchain.schema import AgentAction, ChatMessage +# from langchain.schema import AgentAction, ChatMessage +from agentverse.utils import AgentAction class Message(BaseModel): diff --git a/agentverse/tasks/__init__.py b/agentverse/tasks/__init__.py index 5f09f6073..c40ecc64c 100644 --- a/agentverse/tasks/__init__.py +++ b/agentverse/tasks/__init__.py @@ -12,3 +12,6 @@ from .math_problem_2players_tools_nolc.output_parser import ( MathProblem2PlayersToolsNolcParser, ) +from .nlp_classroom_3players_withtool_nolc.output_parser import ( + NlpClassroom3PlayersWithtoolNolcParser, +) diff --git a/agentverse/tasks/math_problem_2players_tools/output_parser.py b/agentverse/tasks/math_problem_2players_tools/output_parser.py index 94a31045d..6c5289693 100644 --- a/agentverse/tasks/math_problem_2players_tools/output_parser.py +++ b/agentverse/tasks/math_problem_2players_tools/output_parser.py @@ -4,7 +4,9 @@ from typing import Union from langchain.agents import AgentOutputParser -from langchain.schema import AgentAction, AgentFinish + +# from langchain.schema import AgentAction, AgentFinish +from agentverse.utils import AgentAction, AgentFinish from agentverse.parser import OutputParserError, output_parser_registry diff --git a/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml b/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml index d684fb2e2..d83b40ea1 100644 --- a/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml +++ b/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml @@ -27,6 +27,45 @@ prompts: Now the game starts! ${role_description} You should give your action based on the above history. Remember, you should ALWAYS give your response STRICTLY in the above response format with the TWO lines start with "ACTION:" and "ACTION INPUT:" respectively! + summary_prompt: &summary_prompt | + Progressively summarize the lines of a record that you uses tools, which contains inputs for certain tools and the results returned by these tools. Based on the current summary, you need to summarize from the record the goals that the you intended to solve with each call to the tool, add it onto the previous summary, and eventually return a new summary. + + EXAMPLE 1 + Current summary: + + New lines: + Action: getWolframAlphaResults + Action Input: {"input": "what is 5 x 7"} + Observation: "..." + + New summary: + - I search for 5 x 7, and I now know that the result is ... + END OF EXAMPLE 1 + + EXAMPLE 2 + Current summary: + - I search for 5 x 7, and I now know that the result is ... + + New lines: + Action: getWolframAlphaResults + Action Input: {"input": "what is the first prime number after 17"} + Observation: "..." + + New summary: + - I search for 5 x 7, and I now know that the result is ... + - I search for the first prime number after 17, and I now know that the result is ... + END OF EXAMPLE 2 + + Now, try to summarize the following record. + + Current summary: + ${summary} + + New lines: + ${new_lines} + + New summary: + tools: &tools - tool_name: "wolframalpha" tool_url: "http://127.0.0.1:8079/tools/wolframalpha/" @@ -52,6 +91,13 @@ agents: role_description: "You are Alice." memory: memory_type: chat_history + tool_memory: + memory_type: summary + llm: + llm_type: gpt-3.5-turbo + temperature: 0.7 + prompt_template: *summary_prompt + recursive: true prompt_template: *prompt verbose: true llm: @@ -66,6 +112,13 @@ agents: role_description: "You are Bob." memory: memory_type: chat_history + tool_memory: + memory_type: summary + llm: + llm_type: gpt-3.5-turbo + temperature: 0.7 + prompt_template: *summary_prompt + recursive: true prompt_template: *prompt verbose: true llm: diff --git a/agentverse/tasks/math_problem_2players_tools_nolc/output_parser.py b/agentverse/tasks/math_problem_2players_tools_nolc/output_parser.py index d6940581e..3679332ff 100644 --- a/agentverse/tasks/math_problem_2players_tools_nolc/output_parser.py +++ b/agentverse/tasks/math_problem_2players_tools_nolc/output_parser.py @@ -9,7 +9,7 @@ from agentverse.parser import OutputParserError, output_parser_registry, OutputParser from agentverse.llms.base import LLMResult -from agentverse.agents.base import AgentAction, AgentFinish +from agentverse.utils import AgentAction, AgentFinish @output_parser_registry.register("math_problem_2players_tools_nolc") diff --git a/agentverse/tasks/nlp_classroom_3players/output_parser.py b/agentverse/tasks/nlp_classroom_3players/output_parser.py index bff74231c..1b8724f79 100644 --- a/agentverse/tasks/nlp_classroom_3players/output_parser.py +++ b/agentverse/tasks/nlp_classroom_3players/output_parser.py @@ -4,7 +4,9 @@ from typing import Union from langchain.agents import AgentOutputParser -from langchain.schema import AgentAction, AgentFinish + +# from langchain.schema import AgentAction, AgentFinish +from agentverse.utils import AgentAction, AgentFinish from agentverse.parser import OutputParserError, output_parser_registry diff --git a/agentverse/tasks/nlp_classroom_3players_nolc/output_parser.py b/agentverse/tasks/nlp_classroom_3players_nolc/output_parser.py index 6c086f977..245dc3769 100644 --- a/agentverse/tasks/nlp_classroom_3players_nolc/output_parser.py +++ b/agentverse/tasks/nlp_classroom_3players_nolc/output_parser.py @@ -5,7 +5,9 @@ # from langchain.agents import AgentOutputParser from agentverse.parser import OutputParser, LLMResult -from langchain.schema import AgentAction, AgentFinish + +# from langchain.schema import AgentAction, AgentFinish +from agentverse.utils import AgentAction, AgentFinish from agentverse.parser import OutputParserError, output_parser_registry diff --git a/agentverse/tasks/nlp_classroom_3players_withtool/output_parser.py b/agentverse/tasks/nlp_classroom_3players_withtool/output_parser.py index fbdddd0dd..2f4632d6d 100644 --- a/agentverse/tasks/nlp_classroom_3players_withtool/output_parser.py +++ b/agentverse/tasks/nlp_classroom_3players_withtool/output_parser.py @@ -4,7 +4,9 @@ from typing import Union from langchain.agents import AgentOutputParser -from langchain.schema import AgentAction, AgentFinish + +# from langchain.schema import AgentAction, AgentFinish +from agentverse.utils import AgentAction, AgentFinish from agentverse.parser import OutputParserError, output_parser_registry diff --git a/agentverse/tasks/nlp_classroom_3players_withtool_nolc/config.yaml b/agentverse/tasks/nlp_classroom_3players_withtool_nolc/config.yaml new file mode 100644 index 000000000..cab430034 --- /dev/null +++ b/agentverse/tasks/nlp_classroom_3players_withtool_nolc/config.yaml @@ -0,0 +1,197 @@ +prompts: + professor_prompt: &professor_prompt |- + You are in a university classroom and it is a lecture on the Transformer architecture of neural networks. ${role_description} + + # Rules and Format Instructions for Response + + - When you are speaking, you must use the following format: + Thought: (your thought) + Action: Speak + Action Input: (what you want to say) + + - When several students raise their hands, you can choose to call on ONE (and only one) of them using the following format: + Thought: (your thought) + Action: CallOn + Action Input: Yes, (one student's name) + + - Once you have called on a student and they have asked their question, it is your responsibility to provide an answer. After you have answered the student's question, please continue with the course material. + + - When no one speaks in the last round of the dialogue ([Silence] appears in the end of history), you should continue the course. + + - You should not answer the questions that have been already answered. + + - You must follow the following format with two fields "Action" and "Action Input" for your response in ANY case + Thought: (your thought) + Action: (an action name, it can be one of [Speak, Listen, CallOn]) + Action Input: (argument for the action) + + Here is the conversation history + ${chat_history} + + Remember to pay attention to the response format instructions, and strictly follow the rules specified above! + Based on the above history, what will you, ${agent_name}, do next? + + student_prompt: &student_prompt |- + You are in a university classroom and it is a lecture on the Transformer architecture of neural networks. ${role_description} + + # Rules and Format Instructions for Response + + - During class, you can listen to the professor by responding: + Thought: (your thought) + Action: Listen + Action Input: None + + - During class, you have access to the following tools: + ${tools} + + - You can respond as follows to use tool: + Thought: (your thought) + Action: (tool name) + Action Input: (input arguments for the tool) + + - Do not repeatly search for the same question. To obtain more information, you should try different tools or different input arguments when using tool. + + - If you have a question that cannot be resolved by the tools, you should first raise your hand using the following format to let the professor notice you: + Thought: (your thought) + Action: RaiseHand + Action Input: None + + if the professor does call on your name, you MUST speak or ask a question, and use the following format: + Thought: (your thought) + Action: Speak + Action Input: (what you want to ask or speak) + + If you raised your hand but are not called on, you should keep listening, or use a tool to solve the question yourself, or raise your hand again and wait for the professor to call on you. You are NOT allowed to speak if the professor does not call on you. Respect the discipline of the class!! + + - Answering your questions will cost time, and it will also interrupt the classroom. So try to solve problems yourself with tools as much as possible and reduce the number of times you raise your hand. + + - [IMPORTANT!] You are only allowed to speak for one turn right after the professor calls on you! You MUST NOT speak in any other cases! + + - Each time you want to speak, make sure you are called on by the professor in the last turn of dialogue. Otherwise you are not allowed to speak! Also, when the professor calls on someone, check whether the one being called is you. If so, you must speak. + + - You should respond in the following format: + Thought: (your thought) + Action: (an action name, it can be one of [${tool_names}, RaiseHand, Listen, Speak], pay attention to the capitalization) + Action Input: (argument for the action) + + Here is the execution log of tools + ${tool_observation} + + Here is the conversation history + ${chat_history} + + Remember to pay attention to the response format instructions, and strictly follow the rules specified above! + Based on the above history, what will you, ${agent_name}, do next? + + summary_prompt: &summary_prompt | + Progressively summarize the lines of a record that you uses tools, which contains inputs for certain tools and the results returned by these tools. Based on the current summary, you need to summarize from the record the goals that the you intended to solve with each call to the tool, add it onto the previous summary, and eventually return a new summary. + + EXAMPLE + Current summary: + + New lines: + Thought: I want to understand aaa + Action: search_top3 + Action Input: {"key_words": "aaa"} + Observation: "page: 1\ntitle: ...\n" + + New summary: + - I search for aaa, and I now know that bbb. + + Current summary: + - I search for aaa, and I now know that bbb. + + New lines: + Thought: I want to understand xxx + Action: search_top3 + Action Input: {"key_words": "xxx"} + Observation: "page: 1\ntitle: ...\n" + + New summary: + - I search for aaa, and I now know that bbb. + - I searched for xxx, and I now know that yyy. + END OF EXAMPLE + + Now, try to summarize the following record. + + Current summary: + ${summary} + + New lines: + ${new_lines} + + New summary: + +tools: &tools + - tool_name: bing_search, + tool_url: http://127.0.0.1:8079/tools/bing_search/ + +name: NLP Classroom 3 Players with Tools + +environment: + env_type: basic + max_turns: 30 + rule: + order: + type: classroom + visibility: + type: all + selector: + type: classroom + updater: + type: basic + describer: + type: basic + +agents: + - agent_type: conversation + name: Professor Micheal + role_description: |- + You are Professor Micheal, a knowledgeable professor in NLP. Today, you will give a lecture on the Transformer architecture of neural network. Here is the outline for today's course: + 1. Greet the students and introduce yourself to the students. + 2. Explain the disadvantages of RNN models. + 3. Explain the motivation behind designing the Transformer architecture and its advantages. + 4. Introduce pre-trained language models and why they are important. + 5. Provide an envision towards the future development of neural networks. + Your goal is to ensure that the students understand the material, so it's important to speak slowly and clearly. You don't necessarily have to strictly follow the course outline when teaching, you can also talk about some other relevant topics. Remember, in each round of conversation, your response should only address one topic at most. Please take your time and don't rush through the content. + prompt_template: *professor_prompt + llm: + llm_type: gpt-3.5-turbo + temperature: 0.7 + max_tokens: 250 + memory: + memory_type: chat_history + - agent_type: tool + name: Student Oliver + role_description: You are Oliver, a student curious about Natural Language Processing and you want to learn some basic concepts of NLP. You only have a very basic idea of what NLP is. + prompt_template: *student_prompt + memory: + memory_type: chat_history + tool_memory: + memory_type: summary + llm: + llm_type: gpt-3.5-turbo + temperature: 0.7 + prompt: *summary_prompt + llm: + llm_type: gpt-3.5-turbo + temperature: 0.7 + max_tokens: 100 + tools: *tools + - agent_type: tool + name: Student Amelia + role_description: You are Amelia, a shy student who struggles to keep up with the pace of the class. You have some background in computer science but find the concepts being taught in this class challenging. + prompt_template: *student_prompt + memory: + memory_type: chat_history + tool_memory: + memory_type: summary + llm: + llm_type: gpt-3.5-turbo + temperature: 0.7 + prompt: *summary_prompt + llm: + llm_type: gpt-3.5-turbo + temperature: 0.7 + max_tokens: 100 + tools: *tools diff --git a/agentverse/tasks/nlp_classroom_3players_withtool_nolc/output_parser.py b/agentverse/tasks/nlp_classroom_3players_withtool_nolc/output_parser.py new file mode 100644 index 000000000..df99e7b10 --- /dev/null +++ b/agentverse/tasks/nlp_classroom_3players_withtool_nolc/output_parser.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +import re +from typing import Union + + +# from langchain.schema import AgentAction, AgentFinish +from agentverse.utils import AgentAction, AgentFinish + +from agentverse.parser import OutputParserError, output_parser_registry +from agentverse.parser import OutputParser +from agentverse.llms.base import LLMResult + + +@output_parser_registry.register("nlp_classroom_3players_withtool_nolc") +class NlpClassroom3PlayersWithtoolNolcParser(OutputParser): + def parse(self, output: LLMResult) -> Union[AgentAction, AgentFinish]: + text = output.content + cleaned_output = text.strip() + cleaned_output = re.sub(r"\n+", "\n", cleaned_output) + cleaned_output = cleaned_output.split("\n") + if not ( + len(cleaned_output) == 3 + and cleaned_output[0].startswith("Thought:") + and cleaned_output[1].startswith("Action:") + and cleaned_output[2].startswith("Action Input:") + ): + raise OutputParserError(text) + action = cleaned_output[1][len("Action:") :].strip() + action_input = cleaned_output[2][len("Action Input:") :].strip() + if action in ["Speak"]: + return AgentFinish({"output": action_input}, text) + elif action == "CallOn": + return AgentFinish({"output": "[CallOn] " + action_input}, text) + elif action == "RaiseHand": + return AgentFinish({"output": "[RaiseHand] " + action_input}, text) + elif action == "Listen": + return AgentFinish({"output": ""}, text) + else: + return AgentAction(action.lower(), action_input, text) diff --git a/agentverse/tasks/nlp_classroom_9players/output_parser.py b/agentverse/tasks/nlp_classroom_9players/output_parser.py index 59d0ad781..1ec1e2223 100644 --- a/agentverse/tasks/nlp_classroom_9players/output_parser.py +++ b/agentverse/tasks/nlp_classroom_9players/output_parser.py @@ -4,7 +4,9 @@ from typing import Union from langchain.agents import AgentOutputParser -from langchain.schema import AgentAction, AgentFinish + +# from langchain.schema import AgentAction, AgentFinish +from agentverse.utils import AgentAction, AgentFinish from agentverse.parser import OutputParserError, output_parser_registry diff --git a/agentverse/tasks/nlp_classroom_9players_group/output_parser.py b/agentverse/tasks/nlp_classroom_9players_group/output_parser.py index fb103b49c..19284776a 100644 --- a/agentverse/tasks/nlp_classroom_9players_group/output_parser.py +++ b/agentverse/tasks/nlp_classroom_9players_group/output_parser.py @@ -4,7 +4,9 @@ from typing import Union from langchain.agents import AgentOutputParser -from langchain.schema import AgentAction, AgentFinish + +# from langchain.schema import AgentAction, AgentFinish +from agentverse.utils import AgentAction, AgentFinish from agentverse.parser import OutputParserError, output_parser_registry diff --git a/agentverse/utils.py b/agentverse/utils.py new file mode 100644 index 000000000..e172f22e7 --- /dev/null +++ b/agentverse/utils.py @@ -0,0 +1,16 @@ +from typing import NamedTuple, Union + + +class AgentAction(NamedTuple): + """Agent's action to take.""" + + tool: str + tool_input: Union[str, dict] + log: str + + +class AgentFinish(NamedTuple): + """Agent's return value.""" + + return_values: dict + log: str From 4534e8371032be797086c44931d77d2c9f276f55 Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Wed, 17 May 2023 21:17:25 +0800 Subject: [PATCH 08/50] remove unnecessary codees --- agentverse/agents/tool_agent.py | 5 +---- agentverse/memory/summary.py | 3 --- .../math_problem_2players_tools_nolc/config.yaml | 14 -------------- 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/agentverse/agents/tool_agent.py b/agentverse/agents/tool_agent.py index efb89b4f8..a224ee3c0 100644 --- a/agentverse/agents/tool_agent.py +++ b/agentverse/agents/tool_agent.py @@ -76,9 +76,6 @@ async def astep(self, env_description: str = "") -> Message: for i in range(self.max_retry): try: - import pdb - - pdb.set_trace() response = await self.llm.agenerate_response(prompt) parsed_response = self.output_parser.parse(response) if isinstance(parsed_response, AgentAction): @@ -115,7 +112,7 @@ def _call_tool(self, response: NamedTuple) -> str: if response.tool not in name_to_tool: raise ToolNotExistError(response.tool) tool = name_to_tool[response.tool] - observation = tool.run(response.tool_input, verbose=self.verbose) + observation = tool.arun(response.tool_input, verbose=self.verbose) return observation async def _acall_tool(self, response: NamedTuple) -> str: diff --git a/agentverse/memory/summary.py b/agentverse/memory/summary.py index 04ac09fe7..604b39738 100644 --- a/agentverse/memory/summary.py +++ b/agentverse/memory/summary.py @@ -58,9 +58,6 @@ def check_prompt_template(cls, v, values): return v def add_message(self, messages: List[Message]) -> None: - import pdb - - pdb.set_trace() new_lines = "\n".join([message.content for message in messages]) self.update_buffer(new_lines) diff --git a/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml b/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml index d83b40ea1..23de64436 100644 --- a/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml +++ b/agentverse/tasks/math_problem_2players_tools_nolc/config.yaml @@ -91,13 +91,6 @@ agents: role_description: "You are Alice." memory: memory_type: chat_history - tool_memory: - memory_type: summary - llm: - llm_type: gpt-3.5-turbo - temperature: 0.7 - prompt_template: *summary_prompt - recursive: true prompt_template: *prompt verbose: true llm: @@ -112,13 +105,6 @@ agents: role_description: "You are Bob." memory: memory_type: chat_history - tool_memory: - memory_type: summary - llm: - llm_type: gpt-3.5-turbo - temperature: 0.7 - prompt_template: *summary_prompt - recursive: true prompt_template: *prompt verbose: true llm: From 4df7d14d7d1858e0d8a9af8e52d3c4ef3f742e69 Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Wed, 17 May 2023 22:21:00 +0800 Subject: [PATCH 09/50] some code cleaning --- agentverse/agents/agent.py | 287 ------------------------ agentverse/agents/base.py | 6 +- agentverse/agents/conversation_agent.py | 8 +- agentverse/agents/tool_agent.py | 16 +- agentverse/environments/base.py | 7 +- agentverse/llms/base.py | 10 +- agentverse/llms/openai.py | 8 +- agentverse/memory/base.py | 6 +- agentverse/memory/chat_history.py | 5 +- agentverse/memory/summary.py | 4 +- 10 files changed, 42 insertions(+), 315 deletions(-) delete mode 100644 agentverse/agents/agent.py diff --git a/agentverse/agents/agent.py b/agentverse/agents/agent.py deleted file mode 100644 index da8f8fa7a..000000000 --- a/agentverse/agents/agent.py +++ /dev/null @@ -1,287 +0,0 @@ -import logging -import os -from typing import Any, List, Optional, Sequence, Tuple, Union - -from langchain.agents import AgentExecutor, AgentOutputParser -from langchain.agents.agent import Agent as langchainAgent -from langchain.agents.agent import AgentOutputParser -from langchain.agents.conversational_chat.output_parser import ConvoOutputParser -from langchain.base_language import BaseLanguageModel -from langchain.callbacks.base import BaseCallbackManager -from langchain.chains import LLMChain -from langchain.chat_models.base import BaseChatModel -from langchain.llms.base import BaseLLM -from langchain.memory import ChatMessageHistory -from langchain.prompts import BasePromptTemplate, PromptTemplate -from langchain.prompts.chat import ChatPromptTemplate, MessagesPlaceholder -from langchain.schema import ( - AgentAction, - AIMessage, - BaseMessage, - HumanMessage, - SystemMessage, -) -from langchain.tools.base import BaseTool - -# from agentverse.memory.memory import SummaryMemory -from agentverse.message import Message -from agentverse.parser import OutputParserError - -try: - import openai -except ImportError: - is_openai_available = False - logging.warning("openai package is not installed") -else: - openai.api_key = os.environ.get("OPENAI_API_KEY") - openai.proxy = os.environ.get("http_proxy") - if openai.proxy is None: - openai.proxy = os.environ.get("HTTP_PROXY") - if openai.api_key is None: - logging.warning( - "OpenAI API key is not set. Please set the environment variable OPENAI_API_KEY" - ) - is_openai_available = False - else: - is_openai_available = True - - -class Agent(langchainAgent): - """Action unit in the environment. - - Args: - name: Name of the agent - role_description: Description of the role of the agent - memory: Memory of the agent. This is used to store the chat history. - max_retry: Maximum number of retries when encountering an error during generation - tool_memory: Memory of the tools. This is used to store the observations from executing tools by this agent. - tools: List of tools that the agent can use - receiver: List of receivers of the messages generated by this agent - """ - - name: str - role_description: str - memory: ChatMessageHistory - max_retry: int = 3 - tool_memory: Optional[SummaryMemory] = None - tools: List[BaseTool] = [] - receiver: List[str] = ["all"] - input_variables: List[str] = ["chat_history", "agent_scratchpad"] - - @classmethod - def _get_default_output_parser(cls, **kwargs: Any) -> AgentOutputParser: - return ConvoOutputParser() - - @property - def _agent_type(self) -> str: - raise NotImplementedError - - @property - def observation_prefix(self) -> str: - """Prefix to append the observation with.""" - return "Observation: " - - @property - def llm_prefix(self) -> str: - """Prefix to append the llm call with.""" - return "" - - def get_receiver(self) -> List[str]: - return self.receiver - - def set_receiver(self, receiver: List[str]) -> None: - self.receiver = receiver - - def step(self, env_description: str = "") -> Message: - """Generate the next message""" - executor = AgentExecutor.from_agent_and_tools( - agent=self, - tools=self.tools, - verbose=True, - return_intermediate_steps=True, - output_parser=self.output_parser, - ) - input_arguments = {"agent_name": self.name} - if isinstance(self.llm_chain.llm, BaseChatModel): - chat_history = self.memory.messages - elif isinstance(self.llm_chain.llm, BaseLLM): - messages = self.memory.messages - chat_history = "\n".join([message.content for message in messages]) - chat_history += f"\n{self.name}: " - else: - raise ValueError(f"Unsupported LLM type: {self.llm_chain.llm}") - - input_arguments["chat_history"] = chat_history - input_arguments["env_description"] = env_description - - response = None - for i in range(self.max_retry): - try: - response = executor(input_arguments) - break - except OutputParserError as e: - print(e) - print("Retrying...") - continue - if response is None: - raise ValueError(f"{self.name} failed to generate valid response.") - - message = Message( - content=response["output"], - role="assistant", - sender=self.name, - receiver=self.get_receiver(), - tool_response=response["intermediate_steps"], - ) - return message - - async def astep(self, env_description: str = "") -> Message: - """Asynchronous version of step""" - executor = AgentExecutor.from_agent_and_tools( - agent=self, - tools=self.tools, - verbose=True, - return_intermediate_steps=True, - output_parser=self.output_parser, - ) - input_arguments = {"agent_name": self.name} - if isinstance(self.llm_chain.llm, BaseChatModel): - chat_history = self.memory.messages - elif isinstance(self.llm_chain.llm, BaseLLM): - messages = self.memory.messages - chat_history = "\n".join([message.content for message in messages]) - chat_history += f"\n{self.name}: " - else: - raise ValueError(f"Unsupported LLM type: {self.llm_chain.llm}") - if self.tool_memory is not None: - input_arguments["tool_memory"] = self.tool_memory.buffer - - input_arguments["chat_history"] = chat_history - input_arguments["env_description"] = env_description - if self.tool_memory is not None: - input_arguments["tool_memory"] = self.tool_memory.buffer - else: - input_arguments["tool_memory"] = "" - - response = None - for i in range(self.max_retry): - try: - response = await executor.acall(input_arguments) - break - except OutputParserError as e: - print(e) - print("Retrying...") - continue - if response is None: - logging.error(f"{self.name} failed to generate valid response.") - - message = Message( - content="" if response is None else response["output"], - role="assistant", - sender=self.name, - receiver=self.get_receiver(), - tool_response=[] if response is None else response["intermediate_steps"], - ) - return message - - @classmethod - def create_prompt( - cls, - llm: BaseLanguageModel, - tools: List[BaseTool], - prefix_prompt: str = "", - format_prompt: str = "", - suffix_prompt: str = "", - input_variables: Optional[List[str]] = None, - ) -> BasePromptTemplate: - """Create the prompt for the agent. - - Args: - llm: Language model to use. Following the interface of langchain.llms - tools: List of tools to use. Following the interface of langchain.tools - prefix_prompt: Introduce the scene - format_prompt: Specifying the format of the output and the rule of the environment - suffix_prompt: Telling the agent to response - input_variables: List of placeholder variable names in your prompt - """ - if input_variables is None: - if len(tools) > 0: - input_variables = ["chat_history", "agent_scratchpad"] - else: - input_variables = ["chat_history"] - if isinstance(llm, BaseChatModel): - messages = [ - SystemMessage(content=prefix_prompt), - SystemMessage(content=format_prompt), - SystemMessage(content=suffix_prompt), - MessagesPlaceholder(variable_name="chat_history"), - ] - if len(tools) > 0: - messages.append(MessagesPlaceholder(variable_name="agent_scratchpad")) - return ChatPromptTemplate( - input_variables=input_variables, messages=messages - ) - elif isinstance(llm, BaseLLM): - template = f"{prefix_prompt}\n\n{format_prompt}\n\n{suffix_prompt}" - return PromptTemplate(input_variables=input_variables, template=template) - else: - raise ValueError(f"Unsupported LLM type: {llm}") - - def _construct_scratchpad( - self, intermediate_steps: List[Tuple[AgentAction, str]] - ) -> Union[List[BaseMessage], str]: - """Construct the scratchpad that lets the agent continue its thought process.""" - thoughts = [] if isinstance(self.llm_chain.llm, BaseChatModel) else "" - for action, observation in intermediate_steps: - if isinstance(self.llm_chain.llm, BaseChatModel): - thoughts.append(AIMessage(content=action.log.strip())) - human_message = HumanMessage( - content="Tool response:\n{observation}".format( - observation=observation - ) - ) - thoughts.append(human_message) - elif isinstance(self.llm_chain.llm, BaseLLM): - thoughts += action.log.strip() - thoughts += "\n" + "Observation: " + observation - return thoughts - - @classmethod - def from_llm_and_tools( - cls, - llm: BaseLanguageModel, - tools: Sequence[BaseTool], - callback_manager: Optional[BaseCallbackManager] = None, - output_parser: Optional[AgentOutputParser] = None, - prefix_prompt: str = "", - format_prompt: str = "", - suffix_prompt: str = "", - input_variables: Optional[List[str]] = None, - **kwargs, - ) -> langchainAgent: - """Construct an agent from an LLM and tools.""" - cls._validate_tools(tools) - prompt = cls.create_prompt( - llm=llm, - tools=tools, - prefix_prompt=prefix_prompt, - format_prompt=format_prompt, - suffix_prompt=suffix_prompt, - input_variables=input_variables, - ) - llm_chain = LLMChain( - llm=llm, - prompt=prompt, - callback_manager=callback_manager, - ) - tool_names = [tool.name for tool in tools] - return cls( - llm_chain=llm_chain, - tools=tools, - allowed_tools=tool_names, - output_parser=output_parser, - **kwargs, - ) - - def reset(self): - self.memory.clear() diff --git a/agentverse/agents/base.py b/agentverse/agents/base.py index 18663a47d..13db010b7 100644 --- a/agentverse/agents/base.py +++ b/agentverse/agents/base.py @@ -1,11 +1,11 @@ import logging from abc import abstractmethod -from typing import List, Set, Union, NamedTuple +from typing import List, NamedTuple, Set, Union from pydantic import BaseModel, Field from agentverse.llms import BaseLLM -from agentverse.memory import BaseMemory +from agentverse.memory import BaseMemory, ChatHistoryMemory from agentverse.message import Message from agentverse.parser import OutputParser @@ -16,7 +16,7 @@ class BaseAgent(BaseModel): output_parser: OutputParser prompt_template: str role_description: str = Field(default="") - memory: BaseMemory = Field(default_factory=BaseMemory) + memory: BaseMemory = Field(default_factory=ChatHistoryMemory) max_retry: int = Field(default=3) receiver: Set[str] = Field(default=set({"all"})) async_mode: bool = Field(default=True) diff --git a/agentverse/agents/conversation_agent.py b/agentverse/agents/conversation_agent.py index 045d96230..4f7c927d6 100644 --- a/agentverse/agents/conversation_agent.py +++ b/agentverse/agents/conversation_agent.py @@ -1,13 +1,11 @@ import logging from string import Template -from typing import List, NamedTuple, Optional, Union +from typing import List -from agentverse.llms import BaseChatModel, BaseCompletionModel, BaseLLM -from agentverse.memory import BaseMemory from agentverse.message import Message -from agentverse.parser import OutputParserError, OutputParser -from .base import BaseAgent + from . import agent_registry +from .base import BaseAgent @agent_registry.register("conversation") diff --git a/agentverse/agents/tool_agent.py b/agentverse/agents/tool_agent.py index a224ee3c0..a95b8ce2d 100644 --- a/agentverse/agents/tool_agent.py +++ b/agentverse/agents/tool_agent.py @@ -1,16 +1,16 @@ import logging -from pydantic import Field from string import Template from typing import List, NamedTuple, Optional, Union -from agentverse.llms import BaseChatModel, BaseCompletionModel, BaseLLM +from langchain.tools import BaseTool +from pydantic import Field + from agentverse.memory import BaseMemory, ChatHistoryMemory from agentverse.message import Message -from agentverse.parser import OutputParserError, OutputParser -from .base import BaseAgent -from langchain.tools import BaseTool from agentverse.utils import AgentAction, AgentFinish + from . import agent_registry +from .base import BaseAgent class ToolNotExistError(BaseException): @@ -70,6 +70,7 @@ def step(self, env_description: str = "") -> Message: async def astep(self, env_description: str = "") -> Message: """Asynchronous version of step""" parsed_response = None + # Initialize the tool_observation with tool_memory tool_observation = [self.tool_memory.to_string()] while True: prompt = self._fill_prompt_template(env_description, tool_observation) @@ -79,6 +80,8 @@ async def astep(self, env_description: str = "") -> Message: response = await self.llm.agenerate_response(prompt) parsed_response = self.output_parser.parse(response) if isinstance(parsed_response, AgentAction): + # If the response is an action, call the tool + # and append the observation to tool_observation observation = await self._acall_tool(parsed_response) tool_observation.append( parsed_response.log.strip() @@ -112,7 +115,7 @@ def _call_tool(self, response: NamedTuple) -> str: if response.tool not in name_to_tool: raise ToolNotExistError(response.tool) tool = name_to_tool[response.tool] - observation = tool.arun(response.tool_input, verbose=self.verbose) + observation = tool.run(response.tool_input, verbose=self.verbose) return observation async def _acall_tool(self, response: NamedTuple) -> str: @@ -127,6 +130,7 @@ async def _acall_tool(self, response: NamedTuple) -> str: def _update_tool_memory(self, tool_observation: List[str]): """Update the memory of the tool""" if len(tool_observation) == 1: + # If no tool is called this turn, do nothing return messages = [ Message(content=observation) for observation in tool_observation[1:] diff --git a/agentverse/environments/base.py b/agentverse/environments/base.py index ea471ee09..e537a51b3 100644 --- a/agentverse/environments/base.py +++ b/agentverse/environments/base.py @@ -1,17 +1,16 @@ from __future__ import annotations -import asyncio -import logging -from typing import Any, Dict, List, TYPE_CHECKING + from abc import abstractmethod +from typing import TYPE_CHECKING, Any, Dict, List from pydantic import BaseModel # from agentverse.agents.agent import Agent if TYPE_CHECKING: + from agentverse.agents.base import BaseAgent from agentverse.environments.rules.base import Rule from agentverse.message import Message - from agentverse.agents.base import BaseAgent class BaseEnvironment(BaseModel): diff --git a/agentverse/llms/base.py b/agentverse/llms/base.py index 91d0b9114..644246cd3 100644 --- a/agentverse/llms/base.py +++ b/agentverse/llms/base.py @@ -1,16 +1,20 @@ -from pydantic import BaseModel, Field from abc import abstractmethod from typing import Dict +from pydantic import BaseModel, Field + + class LLMResult(BaseModel): content: str send_tokens: int recv_tokens: int total_tokens: int + class BaseModelArgs(BaseModel): pass + class BaseLLM(BaseModel): args: BaseModelArgs = Field(default_factory=BaseModelArgs) max_retry: int = Field(default=3) @@ -23,8 +27,10 @@ def generate_response(self, **kwargs) -> LLMResult: def agenerate_response(self, **kwargs) -> LLMResult: pass + class BaseChatModel(BaseLLM): pass + class BaseCompletionModel(BaseLLM): - pass \ No newline at end of file + pass diff --git a/agentverse/llms/openai.py b/agentverse/llms/openai.py index af2e666d2..e79b3c633 100644 --- a/agentverse/llms/openai.py +++ b/agentverse/llms/openai.py @@ -1,11 +1,13 @@ import logging import os -from pydantic import Field, BaseModel -from typing import List, Optional, Union, Dict +from typing import Dict, List, Optional, Union + +from pydantic import BaseModel, Field from agentverse.llms.base import LLMResult -from .base import BaseModelArgs, BaseChatModel, BaseCompletionModel + from . import llm_registry +from .base import BaseChatModel, BaseCompletionModel, BaseModelArgs try: import openai diff --git a/agentverse/memory/base.py b/agentverse/memory/base.py index be83ad94a..cec06e8f7 100644 --- a/agentverse/memory/base.py +++ b/agentverse/memory/base.py @@ -1,7 +1,9 @@ +from abc import abstractmethod +from typing import Dict, List + from pydantic import BaseModel, Field -from typing import List, Dict + from agentverse.message import Message -from abc import abstractmethod class BaseMemory(BaseModel): diff --git a/agentverse/memory/chat_history.py b/agentverse/memory/chat_history.py index d60b1dc98..aae5c9cb6 100644 --- a/agentverse/memory/chat_history.py +++ b/agentverse/memory/chat_history.py @@ -1,8 +1,11 @@ -from .base import BaseMemory from typing import List + from pydantic import Field + from agentverse.message import Message + from . import memory_registry +from .base import BaseMemory @memory_registry.register("chat_history") diff --git a/agentverse/memory/summary.py b/agentverse/memory/summary.py index 604b39738..84bd98393 100644 --- a/agentverse/memory/summary.py +++ b/agentverse/memory/summary.py @@ -1,12 +1,12 @@ +import re from string import Template from typing import List -import re from pydantic import Field, validator +from agentverse.initialization import load_llm from agentverse.llms.base import BaseLLM from agentverse.message import Message -from agentverse.initialization import load_llm from . import memory_registry from .base import BaseMemory From a742d5010becad91cd2854cf8f9c4d5a76b82e64 Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Wed, 17 May 2023 22:48:03 +0800 Subject: [PATCH 10/50] fix a bug in classroom_withtool example --- .../config.yaml | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/agentverse/tasks/nlp_classroom_3players_withtool_nolc/config.yaml b/agentverse/tasks/nlp_classroom_3players_withtool_nolc/config.yaml index cab430034..bee7f9c0a 100644 --- a/agentverse/tasks/nlp_classroom_3players_withtool_nolc/config.yaml +++ b/agentverse/tasks/nlp_classroom_3players_withtool_nolc/config.yaml @@ -7,7 +7,7 @@ prompts: - When you are speaking, you must use the following format: Thought: (your thought) Action: Speak - Action Input: (what you want to say) + Action Input: (what you want to say in without line break) - When several students raise their hands, you can choose to call on ONE (and only one) of them using the following format: Thought: (your thought) @@ -39,7 +39,7 @@ prompts: - During class, you can listen to the professor by responding: Thought: (your thought) Action: Listen - Action Input: None + Action Input: "None" - During class, you have access to the following tools: ${tools} @@ -54,7 +54,7 @@ prompts: - If you have a question that cannot be resolved by the tools, you should first raise your hand using the following format to let the professor notice you: Thought: (your thought) Action: RaiseHand - Action Input: None + Action Input: "None" if the professor does call on your name, you MUST speak or ask a question, and use the following format: Thought: (your thought) @@ -156,11 +156,12 @@ agents: Your goal is to ensure that the students understand the material, so it's important to speak slowly and clearly. You don't necessarily have to strictly follow the course outline when teaching, you can also talk about some other relevant topics. Remember, in each round of conversation, your response should only address one topic at most. Please take your time and don't rush through the content. prompt_template: *professor_prompt llm: - llm_type: gpt-3.5-turbo + llm_type: text-davinci-003 temperature: 0.7 max_tokens: 250 memory: memory_type: chat_history + verbose: true - agent_type: tool name: Student Oliver role_description: You are Oliver, a student curious about Natural Language Processing and you want to learn some basic concepts of NLP. You only have a very basic idea of what NLP is. @@ -172,12 +173,14 @@ agents: llm: llm_type: gpt-3.5-turbo temperature: 0.7 - prompt: *summary_prompt + prompt_template: *summary_prompt + recursive: true llm: - llm_type: gpt-3.5-turbo + llm_type: text-davinci-003 temperature: 0.7 max_tokens: 100 tools: *tools + verbose: true - agent_type: tool name: Student Amelia role_description: You are Amelia, a shy student who struggles to keep up with the pace of the class. You have some background in computer science but find the concepts being taught in this class challenging. @@ -189,9 +192,11 @@ agents: llm: llm_type: gpt-3.5-turbo temperature: 0.7 - prompt: *summary_prompt + prompt_template: *summary_prompt + recursive: true llm: - llm_type: gpt-3.5-turbo + llm_type: text-davinci-003 temperature: 0.7 max_tokens: 100 tools: *tools + verbose: true From 240a2db4d8f951fbf3b8b2239c28bba518b2c9fc Mon Sep 17 00:00:00 2001 From: Yusheng Su Date: Thu, 18 May 2023 10:10:37 +0800 Subject: [PATCH 11/50] add ui --- ui/.github/CODE_OF_CONDUCT.md | 84 + ui/.github/CONTRIBUTING.md | 80 + ui/.github/FUNDING.yml | 5 + ui/.github/no-response.yml | 14 + ui/.gitignore | 13 + ui/LICENSE | 21 + ui/README.md | 49 + ui/package-lock.json | 2652 ++ ui/package.json | 34 + ui/raw/map.tmx | 40124 +++++++++++++++++++++++++++++ ui/raw/npc1.psd | Bin 0 -> 57032 bytes ui/rollup.config.dev.mjs | 75 + ui/rollup.config.dist.mjs | 65 + ui/run.sh | 4 + ui/screenshot.png | Bin 0 -> 191593 bytes ui/src/classes/actor.ts | 41 + ui/src/classes/message.ts | 31 + ui/src/classes/npc.ts | 45 + ui/src/classes/player.ts | 87 + ui/src/classes/text.ts | 18 + ui/src/conversation.ts | 3 + ui/src/index.ts | 60 + ui/src/scenes/index.ts | 3 + ui/src/scenes/loading/loading.ts | 45 + ui/src/scenes/message/message.ts | 14 + ui/src/scenes/town/town.ts | 85 + ui/tsconfig.json | 9 + 27 files changed, 43661 insertions(+) create mode 100644 ui/.github/CODE_OF_CONDUCT.md create mode 100644 ui/.github/CONTRIBUTING.md create mode 100644 ui/.github/FUNDING.yml create mode 100644 ui/.github/no-response.yml create mode 100644 ui/.gitignore create mode 100644 ui/LICENSE create mode 100644 ui/README.md create mode 100644 ui/package-lock.json create mode 100644 ui/package.json create mode 100644 ui/raw/map.tmx create mode 100644 ui/raw/npc1.psd create mode 100644 ui/rollup.config.dev.mjs create mode 100644 ui/rollup.config.dist.mjs create mode 100644 ui/run.sh create mode 100644 ui/screenshot.png create mode 100644 ui/src/classes/actor.ts create mode 100644 ui/src/classes/message.ts create mode 100644 ui/src/classes/npc.ts create mode 100644 ui/src/classes/player.ts create mode 100644 ui/src/classes/text.ts create mode 100644 ui/src/conversation.ts create mode 100644 ui/src/index.ts create mode 100644 ui/src/scenes/index.ts create mode 100644 ui/src/scenes/loading/loading.ts create mode 100644 ui/src/scenes/message/message.ts create mode 100644 ui/src/scenes/town/town.ts create mode 100644 ui/tsconfig.json diff --git a/ui/.github/CODE_OF_CONDUCT.md b/ui/.github/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..8fafdb309 --- /dev/null +++ b/ui/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,84 @@ +# Code of Conduct + +## 1. Purpose + +A primary goal of Phaser is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). + +This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. + +We invite all those who participate in Phaser to help us create safe and positive experiences for everyone. + +## 2. Open Source Citizenship + +A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. + +Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. + +If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. + +## 3. Expected Behavior + +The following behaviors are expected and requested of all community members: + +* Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. +* Exercise consideration and respect in your speech and actions. +* Attempt collaboration before conflict. +* Refrain from demeaning, discriminatory, or harassing behavior and speech. +* Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. +* Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. + +## 4. Unacceptable Behavior + +The following behaviors are considered harassment and are unacceptable within our community: + +* Violence, threats of violence or violent language directed against another person. +* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. +* Posting or displaying sexually explicit or violent material. +* Posting or threatening to post other people’s personally identifying information ("doxing"). +* Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. +* Inappropriate photography or recording. +* Inappropriate physical contact. You should have someone’s consent before touching them. +* Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. +* Deliberate intimidation, stalking or following (online or in person). +* Advocating for, or encouraging, any of the above behavior. +* Sustained disruption of community events, including talks and presentations. + +## 5. Consequences of Unacceptable Behavior + +Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. + +Anyone asked to stop unacceptable behavior is expected to comply immediately. + +If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event). + +## 6. Reporting Guidelines + +If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. support@phaser.io. + + + +Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. + +## 7. Addressing Grievances + +If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify Photon Storm Ltd with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. + + + +## 8. Scope + +We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business. + +This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. + +## 9. Contact info + +support@phaser.io + +## 10. License and attribution + +This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). + +Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). + +Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/) diff --git a/ui/.github/CONTRIBUTING.md b/ui/.github/CONTRIBUTING.md new file mode 100644 index 000000000..74ce28264 --- /dev/null +++ b/ui/.github/CONTRIBUTING.md @@ -0,0 +1,80 @@ +# How to contribute + +It's important to us that you feel you can contribute towards the evolution of Phaser. This can take many forms: from helping to fix bugs or improve the docs, to adding in new features to the source. This guide should help you in making that process as smooth as possible. + +Before contributing, please read the [code of conduct](https://github.com/photonstorm/phaser/blob/master/.github/CODE_OF_CONDUCT.md). + +## Reporting issues + +[GitHub Issues][0] is the place to report bugs you may have found. When submitting a bug please do the following: + +**1. Search for existing issues.** Your bug may have already been fixed, or cannot, or will not, be fixed. So be sure to search the issues first before putting in a duplicate issue. + +**2. Not sure if it's a bug?.** Please ask on the [forum][4]. If something is blatantly wrong then post it to GitHub. But if you feel it might just be because you're not sure of expected behavior, then it might save us time, and get you a response faster, if you post it to the Phaser forum instead. + +**3. Create an isolated and reproducible test case.** If you are reporting a bug, make sure you also have a minimal, runnable, code example that reproduces the problem you have. + +**4. Include a live example.** After narrowing your code down to only the problem areas, make use of [jsFiddle][1], [jsBin][2], [CodePen][5], or a link to your live site so that we can view a live example of the problem. + +**5. Share as much information as possible.** Include browser version affected, your OS, version of the library, steps to reproduce, etc. "X isn't working!!!1!" will probably just be closed. + +## Support Forum + +We have a very active [Phaser Support Forum][4]. If you need general support, or are struggling to understand how to do something or need your code checked over, then we would urge you to post it to our forum. There are a lot of friendly devs in there who can help, as well as the core Phaser team, so it's a great place to get support. You're welcome to report bugs directly on GitHub, but for general support we'd always recommend using the forum first. + +## Making Changes + +I'm assuming you already have a recent version of [Node](https://nodejs.org) installed locally and can run `npm`. This guide is tested and works on both Windows 10 and OS X. + +### 1. Checkout the repos + +Check-out both the [Phaser repo](https://github.com/photonstorm/phaser) and the [Phaser 3 Examples Repo](https://github.com/photonstorm/phaser3-examples). Make sure the Phaser 3 Examples repo is saved locally in a folder called `phaser3-examples`, which will be the default for most Git clients. + +### 2. Matching Directory Levels + +Ensure that both repos live at the same depth in your directory structure. For example: `/usr/home/web/phaser` and `/usr/home/web/phaser3-examples`. This is so the dev build scripts in the Phaser repo can safely copy files to `../phaser3-examples` and have them end up in the correct place. + +### 3. Install dependencies + +Using your console, run `npm install` or `yarn install` as we've configs for both. This process will install a local copy of webpack and a handful of small support scripts. Note that Yarn on Windows seems to have issues making some packages global, so stick with npm if this is the case. + +### 4. Webpack + +Making sure you've got both repos checked out, and at the same directory level in your filesystem, issue the command `webpack`. If you can't issue the command then webpack may need [installing globally](https://webpack.js.org/guides/installation/). Webpack will build Phaser and if there are any path errors in the code they'll be flagged during the build process. + +What you need is the ability to issue the command `webpack` within the v3 folder and have it work. + +### 5. ESLint + +There is an ESLint configuration and an Editor Configuration in the v3 folder. **Please adhere to them!** Although not enforced in the build process yet, I will be adding that at a later point. There are lots of tools you can install so your editor of choice will check the ESLint config during development. + +To test if your code passes our lint config issue the command `npm run lint`. + +## Coding style preferences are not contributions + +If your PR is doing little more than changing the Phaser source code into a format / coding style that you prefer then we will automatically close it. All PRs must adhere to the coding style already set-out across the thousands of lines of code in Phaser. Your personal preferences for how things should "look" or be structured do not apply here, sorry. PRs should fix bugs, fix documentation or add features. No changes for the sake of change. + +## I don't really like git / node.js, but I can fix this bug + +That is fine too. While Pull Requests are the best thing in the world for us, they are not the only way to help. You're welcome to post fixes to our forum or even just email them to us. All we ask is that you still adhere to the guidelines presented here re: ESLint, etc. + +## Code Style Guide + +We provide an .editorconfig and eslint config for you to use, but generally: + +- Use 4 spaces for tabs, never tab characters. + +- No trailing whitespace, blank lines should have no whitespace. + +- Always favor strict equals `===` unless you *need* to use type coercion. + +- Follow conventions already in the code, and listen to eslint. Our config is set-up for a reason. + +Thanks to Chad for creating the original Pixi.js Contributing file which we adapted for Phaser. + +[0]: https://github.com/photonstorm/phaser/issues +[1]: http://jsfiddle.net +[2]: http://jsbin.com/ +[3]: http://nodejs.org +[4]: https://phaser.discourse.group/ +[5]: https://codepen.io/pen?template=YeEWom "Phaser 3 game template" diff --git a/ui/.github/FUNDING.yml b/ui/.github/FUNDING.yml new file mode 100644 index 000000000..61f2a6424 --- /dev/null +++ b/ui/.github/FUNDING.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms + +github: photonstorm +patreon: photonstorm +custom: https://phaser.io/community/donate diff --git a/ui/.github/no-response.yml b/ui/.github/no-response.yml new file mode 100644 index 000000000..cfbe92445 --- /dev/null +++ b/ui/.github/no-response.yml @@ -0,0 +1,14 @@ +# Configuration for no-response - https://github.com/probot/no-response + +# Number of days of inactivity before an Issue is closed for lack of response +daysUntilClose: 30 +# Label requiring a response +# TODO: also close `needs-reproduction` issues (blocked by https://github.com/probot/no-response/issues/11) +responseRequiredLabel: 🤷‍♂️ More info needed +# Comment to post when closing an issue due to lack of response. +closeComment: > +Thank you for taking time to open this issue. + +We haven’t gotten a response to our questions above. With the details currently given in the issue we don’t have enough information to take action. + +So we’re going to close this issue. We can re-open it if you find the time to provide the information we need. diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 000000000..6e46e45de --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,13 @@ +# System and IDE files +Thumbs.db +.DS_Store +.idea +*.suo +*.sublime-project +*.sublime-workspace + +# Vendors +node_modules/ + +# Build +/npm-debug.log diff --git a/ui/LICENSE b/ui/LICENSE new file mode 100644 index 000000000..3efb55611 --- /dev/null +++ b/ui/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Richard Davey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 000000000..1044cec84 --- /dev/null +++ b/ui/README.md @@ -0,0 +1,49 @@ +# Phaser 3 TypeScript Project Template + +This quick-start project template combines Phaser 3.60 with [TypeScript 5](https://www.typescriptlang.org/) and uses [Rollup](https://rollupjs.org) for bundling. + +## Requirements + +[Node.js](https://nodejs.org) is required to install dependencies and run scripts via `npm`. + +## Available Commands + +| Command | Description | +|---------|-------------| +| `npm install` | Install project dependencies | +| `npm run watch` | Build project and open web server running project, watching for changes | +| `npm run dev` | Builds project and open web server, but do not watch for changes | +| `npm run build` | Builds code bundle with production settings (minification, no source maps, etc..) | + +## Writing Code + +After cloning the repo, run `npm install` from your project directory. Then, you can start the local development +server by running `npm run watch`. The first time you run this you should see the following demo run: + +![Screenshot](screenshot.png "Phaser 3 Example") + +After starting the development server with `npm run watch`, you can edit any files in the `src` folder +and Rollup will automatically recompile and reload your server (available at `http://localhost:10001` +by default). + +## Configuring Rollup + +* Edit the file `rollup.config.dev.js` to edit the development build. +* Edit the file `rollup.config.dist.js` to edit the distribution build. + +You will find lots of comments inside the rollup config files to help you do this. + +Note that due to the build process involved, it can take around 20 seconds to build the initial bundle. Times will vary based on CPU and local drive speeds. The development config does not minify the code in order to save build time, but it does generate source maps. If you do not require these, disable them in the config to speed it up further. + +## Versions Used + +* Phaser 3.60 +* TypeScript 5.0.3 +* Rollup 3.20.2 +* Rollup Plugins: + * @rollup/plugin-commonjs 24.0.1 + * @rollup/plugin-node-resolve 15.0.2 + * @rollup/plugin-replace 5.0.2 + * @rollup/plugin-terser 0.4.0 + * @rollup/plugin-typescript 11.1.0 + * rollup-plugin-serve 2.0.2 diff --git a/ui/package-lock.json b/ui/package-lock.json new file mode 100644 index 000000000..fe673e9d9 --- /dev/null +++ b/ui/package-lock.json @@ -0,0 +1,2652 @@ +{ + "name": "phaser3-typescript-project-template", + "version": "1.2.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "phaser3-typescript-project-template", + "version": "1.2.0", + "license": "MIT", + "devDependencies": { + "@rollup/plugin-commonjs": "^24.0.1", + "@rollup/plugin-node-resolve": "^15.0.2", + "@rollup/plugin-replace": "^5.0.2", + "@rollup/plugin-terser": "^0.4.0", + "@rollup/plugin-typescript": "^11.1.0", + "phaser": "^3.60.0", + "rollup": "^3.20.2", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-serve": "^2.0.2", + "typescript": "^5.0.3", + "vite-plugin-static-copy": "^0.15.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz", + "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@rollup/plugin-commonjs/node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.2.tgz", + "integrity": "sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve/node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/@rollup/plugin-replace": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz", + "integrity": "sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace/node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.0.tgz", + "integrity": "sha512-Ipcf3LPNerey1q9ZMjiaWHlNPEHNU/B5/uh9zXLltfEQ1lVSLLeZSgAtTPWGyw8Ip1guOeq+mDtdOlEj/wNxQw==", + "dev": true, + "dependencies": { + "serialize-javascript": "^6.0.0", + "smob": "^0.0.6", + "terser": "^5.15.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.x || ^3.x" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.0.tgz", + "integrity": "sha512-86flrfE+bSHB69znnTV6kVjkncs2LBMhcTCyxWgRxLyfXfQrxg4UwlAqENnjrrxnSNS/XKCDJCl8EkdFJVHOxw==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "node_modules/eventemitter3": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.0.tgz", + "integrity": "sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz", + "integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/phaser": { + "version": "3.60.0", + "resolved": "https://registry.npmjs.org/phaser/-/phaser-3.60.0.tgz", + "integrity": "sha512-IKUy35EnoEVcl2EmJ8WOyK4X8OoxHYdlhZLgRGpNrvD1fEagYffhVmwHcapE/tGiLgyrnezmXIo5RrH2NcrTHw==", + "dev": true, + "dependencies": { + "eventemitter3": "^5.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", + "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "3.21.7", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.7.tgz", + "integrity": "sha512-KXPaEuR8FfUoK2uHwNjxTmJ18ApyvD6zJpYv9FOJSqLStmt6xOY84l1IjK2dSolQmoXknrhEFRaPRgOPdqCT5w==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-commonjs": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", + "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-commonjs.", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0", + "rollup-pluginutils": "^2.8.1" + }, + "peerDependencies": { + "rollup": ">=1.12.0" + } + }, + "node_modules/rollup-plugin-serve": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-serve/-/rollup-plugin-serve-2.0.2.tgz", + "integrity": "sha512-ALqyTbPhlf7FZ5RzlbDvMYvbKuCHWginJkTo6dMsbgji/a78IbsXox+pC83HENdkTRz8OXrTj+aShp3+3ratpg==", + "dev": true, + "dependencies": { + "mime": ">=2.4.6", + "opener": "1" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/smob": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/smob/-/smob-0.0.6.tgz", + "integrity": "sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/terser": { + "version": "5.16.8", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.8.tgz", + "integrity": "sha512-QI5g1E/ef7d+PsDifb+a6nnVgC4F22Bg6T0xrBrz6iloVB4PUkkunp6V8nzoOOZJIzjWVdAGqCdlKlhLq/TbIA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/typescript": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.3.tgz", + "integrity": "sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/vite": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.5.tgz", + "integrity": "sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==", + "dev": true, + "peer": true, + "dependencies": { + "esbuild": "^0.17.5", + "postcss": "^8.4.23", + "rollup": "^3.21.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-static-copy": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-0.15.0.tgz", + "integrity": "sha512-Ww+/Ug9guV45oIfIi/lA2z8v3K+lLHV9zCJqTVO4FTdqrJoZBj68VgGBSH1fi0N4q/EHW32RsL3ympi4Wlsq5w==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.3", + "fast-glob": "^3.2.11", + "fs-extra": "^11.1.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "dev": true, + "optional": true, + "peer": true + }, + "@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "dev": true, + "optional": true, + "peer": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@rollup/plugin-commonjs": { + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz", + "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.27.0" + }, + "dependencies": { + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.13" + } + } + } + }, + "@rollup/plugin-node-resolve": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.2.tgz", + "integrity": "sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "dependencies": { + "@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + } + } + }, + "@rollup/plugin-replace": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz", + "integrity": "sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.27.0" + }, + "dependencies": { + "magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.13" + } + } + } + }, + "@rollup/plugin-terser": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.0.tgz", + "integrity": "sha512-Ipcf3LPNerey1q9ZMjiaWHlNPEHNU/B5/uh9zXLltfEQ1lVSLLeZSgAtTPWGyw8Ip1guOeq+mDtdOlEj/wNxQw==", + "dev": true, + "requires": { + "serialize-javascript": "^6.0.0", + "smob": "^0.0.6", + "terser": "^5.15.1" + } + }, + "@rollup/plugin-typescript": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.0.tgz", + "integrity": "sha512-86flrfE+bSHB69znnTV6kVjkncs2LBMhcTCyxWgRxLyfXfQrxg4UwlAqENnjrrxnSNS/XKCDJCl8EkdFJVHOxw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "resolve": "^1.22.1" + } + }, + "@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "dependencies": { + "@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + } + } + }, + "@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "peer": true, + "requires": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "eventemitter3": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.0.tgz", + "integrity": "sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "requires": { + "builtin-modules": "^3.3.0" + } + }, + "is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz", + "integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==", + "dev": true + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "peer": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "phaser": { + "version": "3.60.0", + "resolved": "https://registry.npmjs.org/phaser/-/phaser-3.60.0.tgz", + "integrity": "sha512-IKUy35EnoEVcl2EmJ8WOyK4X8OoxHYdlhZLgRGpNrvD1fEagYffhVmwHcapE/tGiLgyrnezmXIo5RrH2NcrTHw==", + "dev": true, + "requires": { + "eventemitter3": "^5.0.0" + } + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "postcss": { + "version": "8.4.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", + "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "dev": true, + "peer": true, + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rollup": { + "version": "3.21.7", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.7.tgz", + "integrity": "sha512-KXPaEuR8FfUoK2uHwNjxTmJ18ApyvD6zJpYv9FOJSqLStmt6xOY84l1IjK2dSolQmoXknrhEFRaPRgOPdqCT5w==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "rollup-plugin-commonjs": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", + "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0", + "rollup-pluginutils": "^2.8.1" + } + }, + "rollup-plugin-serve": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-serve/-/rollup-plugin-serve-2.0.2.tgz", + "integrity": "sha512-ALqyTbPhlf7FZ5RzlbDvMYvbKuCHWginJkTo6dMsbgji/a78IbsXox+pC83HENdkTRz8OXrTj+aShp3+3ratpg==", + "dev": true, + "requires": { + "mime": ">=2.4.6", + "opener": "1" + } + }, + "rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "requires": { + "estree-walker": "^0.6.1" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "smob": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/smob/-/smob-0.0.6.tgz", + "integrity": "sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "peer": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "terser": { + "version": "5.16.8", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.8.tgz", + "integrity": "sha512-QI5g1E/ef7d+PsDifb+a6nnVgC4F22Bg6T0xrBrz6iloVB4PUkkunp6V8nzoOOZJIzjWVdAGqCdlKlhLq/TbIA==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "optional": true, + "peer": true + }, + "typescript": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.3.tgz", + "integrity": "sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==", + "dev": true + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "vite": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.5.tgz", + "integrity": "sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==", + "dev": true, + "peer": true, + "requires": { + "esbuild": "^0.17.5", + "fsevents": "~2.3.2", + "postcss": "^8.4.23", + "rollup": "^3.21.0" + } + }, + "vite-plugin-static-copy": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-0.15.0.tgz", + "integrity": "sha512-Ww+/Ug9guV45oIfIi/lA2z8v3K+lLHV9zCJqTVO4FTdqrJoZBj68VgGBSH1fi0N4q/EHW32RsL3ympi4Wlsq5w==", + "dev": true, + "requires": { + "chokidar": "^3.5.3", + "fast-glob": "^3.2.11", + "fs-extra": "^11.1.0", + "picocolors": "^1.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + } +} diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 000000000..e8b71d071 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,34 @@ +{ + "name": "phaser3-typescript-project-template", + "version": "1.2.0", + "description": "A Phaser 3 Project Template using Rollup and TypeScript", + "main": "src/index.ts", + "scripts": { + "dev": "rollup --config rollup.config.dev.mjs", + "build": "rollup --config rollup.config.dist.mjs", + "watch": "rollup --watch --config rollup.config.dev.mjs" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/photonstorm/phaser3-typescript-project-template.git" + }, + "author": "Richard Davey (http://www.photonstorm.com)", + "license": "MIT", + "bugs": { + "url": "https://github.com/photonstorm/phaser3-typescript-project-template/issues" + }, + "homepage": "https://github.com/photonstorm/phaser3-typescript-project-template#readme", + "devDependencies": { + "@rollup/plugin-commonjs": "^24.0.1", + "@rollup/plugin-node-resolve": "^15.0.2", + "@rollup/plugin-replace": "^5.0.2", + "@rollup/plugin-terser": "^0.4.0", + "@rollup/plugin-typescript": "^11.1.0", + "phaser": "^3.60.0", + "rollup": "^3.20.2", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-serve": "^2.0.2", + "typescript": "^5.0.3", + "vite-plugin-static-copy": "^0.15.0" + } +} diff --git a/ui/raw/map.tmx b/ui/raw/map.tmx new file mode 100644 index 000000000..1acc1112a --- /dev/null +++ b/ui/raw/map.tmx @@ -0,0 +1,40124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +4,1,1,4,3,2,2,1,3,4,2,2,1,4,2,1,3,3,4,2,2,3,4,2,4,3,1,2,2,2,4,1,3,2,3,1,4,3,2,3,3,2,2,3,3,1,1,1, +3,1,4,2,2,1,3,4,4,3,1,2,4,3,1,3,4,4,4,4,3,2,25,26,27,3,3,3,2,2,1,1,1,4,2,3,4,3,3,3,2,3,3,1,1,3,2,2, +1,2,3,4,1,4,2,3,3,4,4,1,3,2,4,3,2,4,3,4,4,2,25,26,27,3,3,2,3,2,17,18,18,18,18,18,18,19,1,4,4,1,3,2,1,2,3,3, +4,3,1,4,4,4,2,1,3,4,4,3,4,1,2,4,3,2,2,3,1,2,25,26,27,1,2,3,3,2,25,26,26,26,26,26,26,27,3,3,2,2,3,2,2,4,4,3, +2,3,4,1,1,2,2,4,1,1,17,18,19,3,1,3,2,1,1,4,4,3,25,26,27,4,3,4,1,3,25,26,45,34,34,44,26,27,4,3,1,4,4,2,1,3,4,4, +4,1,4,4,2,2,1,1,3,3,25,26,27,2,4,4,2,3,4,4,2,2,25,26,27,4,2,3,2,4,25,26,27,1,3,25,26,27,3,4,4,2,4,4,3,2,3,2, +2,4,4,3,3,2,4,1,3,3,4,1,3,2,1,1,3,4,4,4,3,1,25,26,27,3,4,2,1,1,2,3,4,1,2,25,26,27,2,4,4,4,1,1,4,3,1,4, +3,2,1,2,2,4,4,2,4,4,2,4,4,2,4,4,4,1,4,4,3,3,25,26,27,4,4,1,4,2,2,1,2,2,4,25,26,27,2,3,1,1,3,2,4,2,1,2, +4,3,1,1,4,1,3,3,1,4,3,2,1,1,1,1,4,2,3,2,4,2,25,26,27,1,3,3,2,2,1,4,1,3,1,25,26,27,2,1,4,2,3,4,1,3,1,2, +3,3,1,2,1,4,1,1,17,18,18,18,18,18,18,18,18,18,18,18,18,18,36,26,37,18,18,18,18,18,18,18,18,19,2,25,26,27,4,3,3,3,4,2,2,1,4,2, +4,3,2,2,2,2,3,1,25,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,27,2,25,26,27,1,2,2,4,1,4,4,1,4,4, +3,2,4,4,2,4,3,2,25,26,45,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,35,4,25,26,27,4,3,2,1,2,3,1,4,3,3, +4,3,3,2,3,2,1,2,25,26,27,1,2,3,3,3,3,2,2,4,4,2,2,2,3,2,4,4,1,2,2,2,2,3,4,25,26,27,3,2,1,4,3,2,4,1,3,2, +4,4,1,3,3,4,4,2,25,26,27,4,2,3,3,4,1,3,2,1,2,3,4,4,4,2,2,1,1,3,1,1,2,2,3,25,26,27,1,3,1,4,4,2,3,1,3,3, +3,2,2,3,3,4,3,2,25,26,27,1,1,4,4,2,4,4,2,4,1,4,3,1,3,2,4,1,3,1,4,3,4,2,1,25,26,27,2,4,2,4,1,4,1,4,1,4, +2,4,4,2,2,1,3,2,25,26,37,18,18,18,18,18,18,18,18,19,2,1,3,3,1,3,1,3,2,1,2,3,1,3,3,25,26,27,3,4,4,2,4,4,1,2,4,2, +3,2,2,4,4,1,1,4,25,26,26,26,26,26,26,26,26,26,26,27,2,1,3,2,4,4,3,1,1,4,3,3,4,2,1,25,26,27,4,4,2,3,3,3,2,1,3,4, +18,18,18,18,18,18,18,18,36,26,45,34,34,34,34,34,34,44,26,27,3,2,3,1,1,2,2,1,2,4,4,2,3,3,4,25,26,27,2,2,3,4,3,1,1,2,4,4, +26,26,26,26,26,26,26,26,26,26,27,2,3,1,1,3,2,25,26,27,1,2,1,4,2,2,3,4,3,1,1,4,2,2,2,25,26,27,1,1,2,4,3,1,4,2,1,3, +34,34,34,34,34,34,34,34,44,26,27,1,3,3,3,4,4,25,26,37,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,36,26,27,3,3,1,2,3,4,2,4,1,2, +1,1,2,2,1,2,1,4,25,26,27,1,2,3,4,3,4,25,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,27,2,2,1,4,1,2,3,2,4,1, +2,4,1,1,2,1,3,2,25,26,27,3,3,3,1,3,3,25,26,45,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,35,3,3,4,3,1,1,3,4,4,1, +1,4,1,4,1,4,1,2,25,26,27,1,1,3,1,3,1,25,26,27,3,4,3,1,3,4,1,4,3,2,4,3,2,2,2,3,3,2,2,1,2,4,2,4,3,3,1,4, +1,1,1,3,1,2,3,4,25,26,27,1,3,4,2,2,2,25,26,27,4,4,1,3,2,1,4,3,2,4,2,4,4,1,2,3,2,3,2,2,1,3,2,2,3,3,3,3, +1,4,3,4,2,1,1,1,25,26,27,4,2,3,2,1,1,25,26,27,2,2,3,3,4,4,2,4,1,1,3,1,4,2,3,4,1,3,1,1,2,1,1,3,2,4,1,3, +1,4,3,3,3,4,2,2,25,26,27,1,2,3,4,2,3,25,26,27,4,1,3,3,2,3,1,1,2,1,4,4,3,4,4,1,4,4,4,1,4,1,1,2,1,4,4,1, +2,1,1,2,4,4,1,4,25,26,37,18,18,18,18,18,18,36,26,27,2,1,3,2,4,1,2,3,4,2,3,2,3,3,2,4,2,2,4,1,1,1,4,2,4,3,2,3, +1,3,2,4,4,1,2,3,25,26,26,26,26,26,26,26,26,26,26,27,4,4,1,1,4,1,3,4,4,4,3,1,2,1,2,2,3,4,4,1,2,3,4,1,1,1,3,2, +3,2,1,3,4,3,4,3,33,34,34,34,34,34,34,34,34,34,34,35,4,4,4,3,3,4,3,2,4,1,3,4,4,3,1,4,1,2,1,2,2,3,2,3,4,2,1,4, +4,1,1,3,2,1,2,3,1,3,3,3,4,1,2,3,2,2,3,3,2,4,2,1,1,4,4,3,3,2,4,2,4,2,3,4,1,2,3,1,4,2,1,3,4,4,3,4, +1,2,4,3,2,3,1,2,1,1,3,3,2,4,2,2,1,1,4,3,4,3,3,3,4,1,4,2,4,2,2,4,2,1,3,2,1,4,1,1,3,1,3,3,2,2,2,2, +3,2,1,4,1,3,2,3,1,3,1,1,4,4,4,4,1,1,4,4,4,1,2,3,3,2,1,3,3,3,4,4,4,4,4,2,1,1,2,3,3,3,3,2,2,1,4,4, +4,2,2,2,3,3,2,2,1,4,1,1,1,4,1,2,4,1,3,4,4,4,1,3,3,2,3,1,3,1,1,1,4,4,1,4,1,2,4,4,1,3,3,4,1,4,3,2, +2,2,4,3,2,3,1,4,1,3,4,3,2,3,1,3,2,2,2,3,4,2,1,1,1,2,3,2,2,2,2,2,4,3,4,4,3,4,1,3,2,3,4,3,3,4,1,2, +3,4,3,2,3,2,3,2,4,3,1,3,2,3,3,4,4,1,4,4,3,1,3,2,2,1,1,1,4,1,1,1,4,4,4,2,1,4,3,2,3,3,1,1,3,4,3,3 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,219,213,213,213,213,213,213,213,213,213,213,213,213,213,213,0,0,0,213,213,213,213,201,213,213,213,213,213,213,213,213,213,220,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,211,0,0,0,213,213,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,220,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,211,213,213,213,213,213,213, +0,0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +221,221,221,221,221,221,221,212,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,204,0,0,203,213,213,213,213,213,213, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,210,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,210,0,0,209,0,0,0,0,0,0, +221,221,221,221,221,221,204,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,210,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,210,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,210,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,210,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,210,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,210,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,210,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,210,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,210,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,213,213,29,213,213,213,213,213,213,0,0,0,0,0,0,0,0,0,0,0,0,212,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,210,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,209,0,0,0,0,0,0, +0,0,0,0,0,0,218,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,213,213,213,213,213,213,213,213,213,213,217,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,8,7,8,7,8,7,8, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,16,15,16,15,16,15,16, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,6,5,6,5,6,5,6, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,14,13,14,13,14,13,14, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,22,21,22,5,6,5,6, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,14,13,14, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,6,5,6, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,14,13,14, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,8,5,6,5,6, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,15,16,13,14,13,14, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,5,6,5,6,5,6, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,13,14,13,14,13,14, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,21,22,21,22,21,22, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,8,7,8,7,8, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,16,15,16,15,16, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,6,5,6,5,6, +7,8,7,8,7,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,14,13,14,13,14, +15,16,15,16,15,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,6,5,6,5,6, +5,6,5,6,5,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,14,13,14,13,14, +13,14,13,14,13,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,6,5,6,5,6, +5,6,5,6,5,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,14,13,14,13,14, +13,14,13,14,13,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,6,5,6,5,6, +5,6,5,6,5,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,14,13,14,13,14, +13,14,13,14,13,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,6,5,6,5,6, +5,6,5,6,5,6,0,166,167,167,167,167,167,167,167,167,168,0,0,0,0,0,0,0,0,0,0,166,167,167,167,167,167,167,167,167,167,168,0,0,0,0,13,14,13,14,13,14, +13,14,13,14,13,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,6,5,6,5,6, +5,6,5,6,5,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,14,13,14,13,14, +13,14,13,14,13,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,6,5,6,5,6, +21,22,21,22,21,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,14,13,14,13,14, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,22,21,22,21,22 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1339,1340,1340,1340,1340,1344,0,0,0,0, +0,0,0,0,0,0,0,0,1339,1340,1341,1342,1343,1340,1344,1339,1340,1340,1340,1340,1344,0,0,0,0,0,0,0,1339,1340,1341,1342,1343,1340,1344,0,0,0,1347,1348,1348,1348,1348,1352,0,0,0,0, +0,0,0,0,0,0,0,0,1347,1348,1349,1350,1351,1348,1352,1347,1348,1349,1350,1351,1352,0,0,0,0,0,0,0,1347,1348,1349,1350,1351,1348,1352,0,0,0,1355,1359,1359,1359,1359,1360,0,0,0,0, +0,0,0,0,0,0,0,0,1355,1356,1357,1358,1359,1359,1360,1355,1356,1357,1358,1359,1360,0,0,0,0,0,0,0,1355,1356,1357,1358,1359,1359,1360,0,0,0,1326,1327,1310,1312,1335,1336,0,0,0,0, +0,0,0,0,0,0,0,0,1326,1318,1319,1311,1327,1310,1336,1326,1318,1319,1320,1335,1336,0,0,0,0,0,0,0,1326,1318,1319,1311,1327,1310,1336,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,1339,1340,1340,1340,1340,1340,1344,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,1347,1348,1349,1350,1351,1348,1352,361,362,363,364,365,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,1355,1356,1357,1358,1359,1359,1360,369,370,371,372,373,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,1326,1318,1319,1311,1327,1310,1336,377,378,379,380,381,0,0,0,325,326,327,328,366,367,368,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,385,386,387,388,389,0,0,0,333,334,335,336,374,375,376,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,393,394,395,396,397,0,0,0,341,342,343,344,382,383,384,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,270,349,350,351,352,390,391,392,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,357,358,359,360,398,399,400,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,1193,1194,1195,1196,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,1201,1202,1203,1204,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1247,1248,1201,1202,1203,1204,0,0,0,0,32,1339,1340,1340,1340,1340,1344,329,330,331,332,1339,1340,1340,1340,1344,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1247,1248,1209,1210,1211,1212,0,0,0,0,0,1347,1348,1349,1350,1351,1352,337,338,339,340,1347,1348,1348,1348,1352,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,1247,1248,1217,1218,1219,1220,0,0,0,0,0,1355,1356,1357,1358,1359,1360,345,346,347,348,1355,1359,1359,1359,1360,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,32,1225,1226,1227,1228,0,0,0,0,0,1326,1318,1319,1320,1335,1336,353,354,355,356,1326,1327,1334,1335,1336,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,1233,0,0,1233,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4338,0,4338,0,4338,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4338,0,4338,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,4338,0,4338,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,4338,0,4338,0,4338,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + diff --git a/ui/raw/npc1.psd b/ui/raw/npc1.psd new file mode 100644 index 0000000000000000000000000000000000000000..f8dfeece1d4e13048321838058954ec6e78f5138 GIT binary patch literal 57032 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 literal 0 HcmV?d00001 diff --git a/ui/rollup.config.dev.mjs b/ui/rollup.config.dev.mjs new file mode 100644 index 000000000..2efdc261c --- /dev/null +++ b/ui/rollup.config.dev.mjs @@ -0,0 +1,75 @@ +import commonjs from '@rollup/plugin-commonjs'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import replace from '@rollup/plugin-replace'; +import serve from 'rollup-plugin-serve'; +import typescript from '@rollup/plugin-typescript'; +import { viteStaticCopy } from 'vite-plugin-static-copy' + + +export default { + + // Our game entry point (edit as required) + input: [ + './src/index.ts' + ], + + // Where the build file is to be generated. + // Most games being built for distribution can use iife as the module type. + // You can also use 'umd' if you need to ingest your game into another system. + // If using Phaser 3.21 or **below**, add: `intro: 'var global = window;'` to the output object. + output: { + file: './dist/bundle.js', + name: 'MyGame', + format: 'iife', + sourcemap: true + }, + + plugins: [ + + // Toggle the booleans here to enable / disable Phaser 3 features: + replace({ + preventAssignment: true, + 'typeof CANVAS_RENDERER': JSON.stringify(true), + 'typeof WEBGL_RENDERER': JSON.stringify(true), + 'typeof WEBGL_DEBUG': JSON.stringify(true), + 'typeof EXPERIMENTAL': JSON.stringify(true), + 'typeof PLUGIN_CAMERA3D': JSON.stringify(false), + 'typeof PLUGIN_FBINSTANT': JSON.stringify(false), + 'typeof FEATURE_SOUND': JSON.stringify(true) + }), + + // Parse our .ts source files + nodeResolve({ + extensions: [ '.ts', '.tsx' ] + }), + + // We need to convert the Phaser 3 CJS modules into a format Rollup can use: + commonjs({ + include: [ + 'node_modules/eventemitter3/**', + 'node_modules/phaser/**' + ], + exclude: [ + 'node_modules/phaser/src/polyfills/requestAnimationFrame.js', + 'node_modules/phaser/src/phaser-esm.js' + ], + sourceMap: true, + ignoreGlobal: true + }), + + // See https://github.com/rollup/plugins/tree/master/packages/typescript for config options + typescript(), + + // See https://www.npmjs.com/package/rollup-plugin-serve for config options + serve({ + open: true, + contentBase: 'dist', + host: '127.0.0.1', + port: 10001, + headers: { + 'Access-Control-Allow-Origin': '*' + } + }) + + ] +}; \ No newline at end of file diff --git a/ui/rollup.config.dist.mjs b/ui/rollup.config.dist.mjs new file mode 100644 index 000000000..39ac16327 --- /dev/null +++ b/ui/rollup.config.dist.mjs @@ -0,0 +1,65 @@ +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import replace from '@rollup/plugin-replace'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; + +export default { + + // Our games entry point (edit as required) + input: [ + './src/index.ts' + ], + + // Where the build file is to be generated. + // Most games being built for distribution can use iife as the module type. + // You can also use 'umd' if you need to ingest your game into another system. + // If using Phaser 3.21 or **below**, add: `intro: 'var global = window;'` to the output object. + output: { + file: './dist/index.js', + name: 'MyGame', + format: 'iife', + sourcemap: false + }, + + plugins: [ + + // Toggle the booleans here to enable / disable Phaser 3 features: + replace({ + preventAssignment: true, + 'typeof CANVAS_RENDERER': JSON.stringify(true), + 'typeof WEBGL_RENDERER': JSON.stringify(true), + 'typeof WEBGL_DEBUG': JSON.stringify(false), + 'typeof EXPERIMENTAL': JSON.stringify(true), + 'typeof PLUGIN_CAMERA3D': JSON.stringify(false), + 'typeof PLUGIN_FBINSTANT': JSON.stringify(false), + 'typeof FEATURE_SOUND': JSON.stringify(true) + }), + + // Parse our .ts source files + nodeResolve({ + extensions: [ '.ts', '.tsx' ] + }), + + // We need to convert the Phaser 3 CJS modules into a format Rollup can use: + commonjs({ + include: [ + 'node_modules/eventemitter3/**', + 'node_modules/phaser/**' + ], + exclude: [ + 'node_modules/phaser/src/polyfills/requestAnimationFrame.js', + 'node_modules/phaser/src/phaser-esm.js' + ], + sourceMap: false, + ignoreGlobal: true + }), + + // See https://github.com/rollup/plugins/tree/master/packages/typescript for config options + typescript(), + + // See https://github.com/rollup/plugins/tree/master/packages/terser for config options + terser() + + ] +}; \ No newline at end of file diff --git a/ui/run.sh b/ui/run.sh new file mode 100644 index 000000000..2bb43d082 --- /dev/null +++ b/ui/run.sh @@ -0,0 +1,4 @@ +#npm install +npm run watch +#npm run dev +#npm run build diff --git a/ui/screenshot.png b/ui/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..bbe056d6083e8060d74115fc282740ce58e32261 GIT binary patch literal 191593 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

!UYu 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

wwT$;fou^z@zY88NyB(8GZ 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 DlBlP-L3Jv2Bv4z2u8_sU@1vj=N?)+@EW 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 ubuj { + if (value) { + this.hp = this.hp - value; + } + }, + onComplete: () => { + this.setAlpha(1); + }, + }); + } + public getHPValue(): number { + return this.hp; + } + protected checkFlip(): void { + if (this.body.velocity.x < 0) { + this.scaleX = -1; + } else { + this.scaleX = 1; + } + } + protected getBody(): Physics.Arcade.Body { + return this.body as Physics.Arcade.Body; + } +} + diff --git a/ui/src/classes/message.ts b/ui/src/classes/message.ts new file mode 100644 index 000000000..5662b10af --- /dev/null +++ b/ui/src/classes/message.ts @@ -0,0 +1,31 @@ +import { Text } from './text'; +export enum Agents { + PLAYER_speak, + NPC_speak, +} +export class Message extends Text { + private message: string; + constructor(scene: Phaser.Scene, x: number, y: number, initMessage = "") { + super(scene, x, y, `Message: ${initMessage}`); + scene.add.existing(this); + this.message = initMessage; + } + + // Require to Fix the following code. + public updateMessage(operation: Agents, message_player: string, message_npc: string): void { + switch (operation) { + case Agents.PLAYER_speak: + this.message += "PLAYER:" + "Message from backend" + "\n"; + break; + case Agents.NPC_speak: + this.message += "NPC:" + "Message from backend" + "\n"; + break; + default: + break; + } + this.setText(`Message: ${this.message}`); + } + public getMessage(): string { + return this.message; + } +} diff --git a/ui/src/classes/npc.ts b/ui/src/classes/npc.ts new file mode 100644 index 000000000..1335cf299 --- /dev/null +++ b/ui/src/classes/npc.ts @@ -0,0 +1,45 @@ +import { Actor } from './actor'; +export class NPC extends Actor { + //private keyW: Phaser.Input.Keyboard.Key; + //private keyA: Phaser.Input.Keyboard.Key; + //private keyS: Phaser.Input.Keyboard.Key; + //private keyD: Phaser.Input.Keyboard.Key; + constructor(scene: Phaser.Scene, x: number, y: number) { + super(scene, x, y, 'npc'); + // KEYS + //this.keyW = this.scene.input.keyboard.addKey('W'); + //this.keyA = this.scene.input.keyboard.addKey('A'); + //this.keyS = this.scene.input.keyboard.addKey('S'); + //this.keyD = this.scene.input.keyboard.addKey('D'); + // PHYSICS + this.getBody().setSize(14, 20); + this.getBody().setOffset(0, 0); + this.getBody().setImmovable(true); + } + update(): void { + /* + this.getBody().setVelocity(0); + if (this.keyW?.isDown) { + // this.body.velocity.y = -110; + this.getBody().setVelocityY(-110); + } + if (this.keyA?.isDown) { + // this.body.velocity.x = -110; + // this.checkFlip(); + // this.getBody().setOffset(48, 15); + this.getBody().setVelocityX(-110); + } + if (this.keyS?.isDown) { + // this.body.velocity.y = 110; + this.getBody().setVelocityY(110); + } + if (this.keyD?.isDown) { + // this.body.velocity.x = 110; + this.getBody().setVelocityX(110); + // this.checkFlip(); + // this.getBody().setOffset(15, 15); + } + */ + } +} + diff --git a/ui/src/classes/player.ts b/ui/src/classes/player.ts new file mode 100644 index 000000000..a699bc2bb --- /dev/null +++ b/ui/src/classes/player.ts @@ -0,0 +1,87 @@ +import { Actor } from "./actor"; +export class Player extends Actor { + private keyW: Phaser.Input.Keyboard.Key; + private keyA: Phaser.Input.Keyboard.Key; + private keyS: Phaser.Input.Keyboard.Key; + private keyD: Phaser.Input.Keyboard.Key; + constructor(scene: Phaser.Scene, x: number, y: number) { + super(scene, x, y, "player"); + // KEYS + this.keyW = this.scene.input.keyboard.addKey("W"); + this.keyA = this.scene.input.keyboard.addKey("A"); + this.keyS = this.scene.input.keyboard.addKey("S"); + this.keyD = this.scene.input.keyboard.addKey("D"); + + // PHYSICS + this.getBody().setSize(14, 20); + this.getBody().setOffset(0, 0); + + // ANIMATIONS + this.scene.anims.create({ + key: "walk-down", + frames: this.scene.anims.generateFrameNumbers("player", { + start: 0, + end: 2, + }), + frameRate: 6, + }); + this.scene.anims.create({ + key: "walk-up", + frames: this.scene.anims.generateFrameNumbers("player", { + start: 3, + end: 5, + }), + frameRate: 6, + }); + this.scene.anims.create({ + key: "walk-left", + frames: this.scene.anims.generateFrameNumbers("player", { + start: 6, + end: 8, + }), + frameRate: 6, + }); + this.scene.anims.create({ + key: "walk-right", + frames: this.scene.anims.generateFrameNumbers("player", { + start: 9, + end: 11, + }), + frameRate: 6, + }); + } + update(): void { + this.getBody().setVelocity(0); + + var pressed_flag = false; + if (this.keyW?.isDown) { + this.getBody().setVelocityY(-110); + this.anims.play("walk-up", true); + pressed_flag = true; + } + + if (this.keyA?.isDown) { + // this.getBody().setOffset(48, 15); + this.getBody().setVelocityX(-110); + this.anims.play("walk-left", true); + pressed_flag = true; + } + + if (this.keyS?.isDown) { + this.getBody().setVelocityY(110); + this.anims.play("walk-down", true); + pressed_flag = true; + } + + if (this.keyD?.isDown) { + this.getBody().setVelocityX(110); + this.anims.play("walk-right", true); + // this.getBody().setOffset(15, 15); + pressed_flag = true; + } + + if (!pressed_flag && this.anims.isPlaying) { + this.anims.setCurrentFrame(this.anims.currentAnim.frames[0]); + } + } +} diff --git a/ui/src/classes/text.ts b/ui/src/classes/text.ts new file mode 100644 index 000000000..456cfcdc6 --- /dev/null +++ b/ui/src/classes/text.ts @@ -0,0 +1,18 @@ +import { GameObjects, Scene } from 'phaser'; +export class Text extends GameObjects.Text { + constructor(scene: Scene, x: number, y: number, text: string) { + super(scene, x, y, text, { + fontSize: 'calc(100vw / 100)', + color: '#fff', + stroke: '#000', + strokeThickness: 4, + }); + this.setOrigin(0, 0); + // this.getBody().setOffset(0, 0); + scene.add.existing(this); + } +} + +/* +plugin (text box): https://rexrainbow.github.io/phaser3-rex-notes/docs/site/ui-textbox/ +*/ diff --git a/ui/src/conversation.ts b/ui/src/conversation.ts new file mode 100644 index 000000000..d445110da --- /dev/null +++ b/ui/src/conversation.ts @@ -0,0 +1,3 @@ +export enum EVENTS_NAME { + conversation = 'conversation', +} \ No newline at end of file diff --git a/ui/src/index.ts b/ui/src/index.ts new file mode 100644 index 000000000..7bfc2e6f8 --- /dev/null +++ b/ui/src/index.ts @@ -0,0 +1,60 @@ +import { Game, Scale, Types, WEBGL } from 'phaser'; + +import { TownScene, LoadingScene, MessageScene } from './scenes'; + +declare global { + interface Window { + sizeChanged: () => void; + game: Game; + } +} + +export const gameConfig: Types.Core.GameConfig = { + title: 'Phaser game tutorial', + type: WEBGL, + parent: 'game', + // backgroundColor: '#351f1b', + scale: { + mode: Scale.ScaleModes.NONE, + width: window.innerWidth, + height: window.innerHeight, + }, + physics: { + default: 'arcade', + arcade: { + debug: false, + }, + }, + render: { + antialiasGL: false, + pixelArt: true, + }, + callbacks: { + postBoot: () => { + window.sizeChanged(); + }, + }, + canvasStyle: `display: block; width: 100%; height: 100%;`, + autoFocus: true, + audio: { + disableWebAudio: false, + }, + scene: [LoadingScene, TownScene, MessageScene] +}; + +window.sizeChanged = () => { + if (window.game.isBooted) { + setTimeout(() => { + window.game.scale.resize(window.innerWidth, window.innerHeight); + + window.game.canvas.setAttribute( + 'style', + `display: block; width: ${window.innerWidth}px; height: ${window.innerHeight}px;`, + ); + }, 100); + } +}; + +window.onresize = () => window.sizeChanged(); + +window.game = new Game(gameConfig); diff --git a/ui/src/scenes/index.ts b/ui/src/scenes/index.ts new file mode 100644 index 000000000..8b5d49c2f --- /dev/null +++ b/ui/src/scenes/index.ts @@ -0,0 +1,3 @@ +export { TownScene } from "./town/town"; +export { LoadingScene } from "./loading/loading"; +export { MessageScene } from "./message/message"; \ No newline at end of file diff --git a/ui/src/scenes/loading/loading.ts b/ui/src/scenes/loading/loading.ts new file mode 100644 index 000000000..cd2ae84c3 --- /dev/null +++ b/ui/src/scenes/loading/loading.ts @@ -0,0 +1,45 @@ +import { Scene } from 'phaser'; + +export class LoadingScene extends Scene { + // private player!: GameObjects.Sprite; + + constructor() { + super('loading-scene'); + } + + preload(): void { + this.load.baseURL = 'assets/'; + + // PLAYER LOADING + // this.load.image('king', 'sprites/king.png'); + this.load.spritesheet('player', 'sprites/npc1.png', { frameWidth: 14, frameHeight: 20 }); + // this.load.atlas('a-king', 'spritesheets/a-king.png', 'spritesheets/a-king_atlas.json'); + + // NPC LOADING + this.load.spritesheet('npc', 'sprites/npc1.png', { frameWidth: 14, frameHeight: 20 }); + + // MESSAGE BLABK LOADING + // this.load.spritesheet('message', 'message/message_box.png', { frameWidth: 128, frameHeight: 48 }); + + + // MAP LOADING + this.load.image({ + key: 'tiles', + url: 'tilemaps/tiles/tileset.png', + }); + this.load.tilemapTiledJSON('town', 'tilemaps/json/town.json'); + + // CHEST LOADING + // this.load.spritesheet('tiles_spr', 'tilemaps/tiles/dungeon-16-16.png', { + // frameWidth: 16, + // frameHeight: 16, + // }); + } + + create(): void { + // this.scene.start('level-1-scene'); + // this.scene.start('ui-scene'); + this.scene.start('town-scene'); + this.scene.start('message-scene'); + } +} diff --git a/ui/src/scenes/message/message.ts b/ui/src/scenes/message/message.ts new file mode 100644 index 000000000..25da7de6f --- /dev/null +++ b/ui/src/scenes/message/message.ts @@ -0,0 +1,14 @@ +import { Scene } from 'phaser'; +import {Message, Agents} from '../../classes/message'; + +import { EVENTS_NAME } from '../../conversation'; + +export class MessageScene extends Scene { + private message!: Message; + constructor() { + super('message-scene'); + } + create(): void { + this.message = new Message(this, 0, 560, "Receieve Message"); + } +} \ No newline at end of file diff --git a/ui/src/scenes/town/town.ts b/ui/src/scenes/town/town.ts new file mode 100644 index 000000000..6e419b8bf --- /dev/null +++ b/ui/src/scenes/town/town.ts @@ -0,0 +1,85 @@ +import { Scene, Tilemaps, GameObjects } from "phaser"; +import { Player } from "../../classes/player"; +import { NPC } from "../../classes/npc"; +// import { Agents, Message } from '../../classes/message'; + +import { EVENTS_NAME } from "../../conversation"; + +export class TownScene extends Scene { + private map!: Tilemaps.Tilemap; + private tileset!: Tilemaps.Tileset; + private groundLayer!: Tilemaps.TilemapLayer; + private wallLayer!: Tilemaps.TilemapLayer; + private flowerLayer!: Tilemaps.TilemapLayer; + private treeLayer!: Tilemaps.TilemapLayer; + private houseLayer!: Tilemaps.TilemapLayer; + + private player!: GameObjects.Sprite; + private npc!: GameObjects.Sprite; + // private message!: GameObjects.Text; + + constructor() { + super("town-scene"); + } + + create(): void { + // Background + this.map = this.make.tilemap({ + key: "town", + tileWidth: 16, + tileHeight: 16, + }); + this.tileset = this.map.addTilesetImage("town", "tiles"); + this.groundLayer = this.map.createLayer("ground", this.tileset, 0, 0); + this.wallLayer = this.map.createLayer("wall", this.tileset, 0, 0); + this.flowerLayer = this.map.createLayer("flower", this.tileset, 0, 0); + this.treeLayer = this.map.createLayer("tree", this.tileset, 0, 0); + this.houseLayer = this.map.createLayer("house", this.tileset, 0, 0); + + this.wallLayer.setCollisionByProperty({ collides: true }); + this.treeLayer.setCollisionByProperty({ collides: true }); + this.houseLayer.setCollisionByProperty({ collides: true }); + // debugger + + // Player + this.player = new Player(this, 256, 256); + this.physics.add.collider(this.player, this.wallLayer); + this.physics.add.collider(this.player, this.treeLayer); + this.physics.add.collider(this.player, this.houseLayer); + + // NPC + this.npc = new NPC(this, 400, 340); + this.physics.add.collider(this.npc, this.wallLayer); + this.physics.add.collider(this.npc, this.treeLayer); + this.physics.add.collider(this.npc, this.houseLayer); + this.physics.add.collider(this.npc, this.player); + + // message (this, x_position, y_position) + // this.message = new Message(this, 0, 560); + + this.physics.world.setBounds( + 0, + 0, + this.groundLayer.width + this.player.width, + this.groundLayer.height + ); + + // Camera + this.cameras.main.setSize(this.game.scale.width, this.game.scale.height); + this.cameras.main.setBounds( + 0, + 0, + this.groundLayer.width, + this.groundLayer.height + ); + this.cameras.main.startFollow(this.player, true, 0.09, 0.09); + this.cameras.main.setZoom(4); + } + + update(): void { + this.player.update(); + // this.npc.update(); + // this.conversation.updateMessage(); + // this.conversation.getMessage(); + } +} diff --git a/ui/tsconfig.json b/ui/tsconfig.json new file mode 100644 index 000000000..cecd357a9 --- /dev/null +++ b/ui/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "es5", + "moduleResolution": "node" + }, + "include": [ + "./src/**/*" + ] +} From 8140bfa50f8c0cc0f04ce5b181e246650cd94019 Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Thu, 18 May 2023 10:27:08 +0800 Subject: [PATCH 12/50] add dist for ui --- .gitignore | 6 +- ui/dist/assets/message/message.png | Bin 0 -> 41634 bytes ui/dist/assets/sprite.png | Bin 0 -> 214508 bytes ui/dist/assets/sprites/npc1.png | Bin 0 -> 4256 bytes ui/dist/assets/tilemaps/json/tmp.json | 140 + ui/dist/assets/tilemaps/json/town.json | 72282 ++++++++++++++++++++ ui/dist/assets/tilemaps/tiles/tileset.png | Bin 0 -> 283178 bytes ui/dist/assets/tilemaps/tiles/tileset.tsx | 4 + ui/dist/assets/tilemaps/tiles/town.tsx | 4 + ui/dist/index.html | 20 + 10 files changed, 72454 insertions(+), 2 deletions(-) create mode 100644 ui/dist/assets/message/message.png create mode 100644 ui/dist/assets/sprite.png create mode 100644 ui/dist/assets/sprites/npc1.png create mode 100644 ui/dist/assets/tilemaps/json/tmp.json create mode 100644 ui/dist/assets/tilemaps/json/town.json create mode 100644 ui/dist/assets/tilemaps/tiles/tileset.png create mode 100644 ui/dist/assets/tilemaps/tiles/tileset.tsx create mode 100644 ui/dist/assets/tilemaps/tiles/town.tsx create mode 100644 ui/dist/index.html diff --git a/.gitignore b/.gitignore index cc2524aa3..2d3247f23 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,9 @@ __pycache__/ .Python build/ develop-eggs/ -dist/ +**/dist/* +!**/dist/assets +!**/dist/index.html downloads/ eggs/ .eggs/ @@ -163,4 +165,4 @@ cython_debug/ figs frontend -g_push.sh +g_push.sh \ No newline at end of file diff --git a/ui/dist/assets/message/message.png b/ui/dist/assets/message/message.png new file mode 100644 index 0000000000000000000000000000000000000000..7d22a4a070403eb7cbeace49dcb199e7cf849091 GIT binary patch literal 41634 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 e0pz40`Tqf;f}dtJA4Wp}0000fZf- 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

tYff+)+H&+NYd2j(NRP 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 {P@jt8&!u*nbfz+7!ED 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%0Ah4$NX@sgdCfsKYaFS)2d7gSP^iW9 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 literal 0 HcmV?d00001 diff --git a/ui/dist/assets/sprites/npc1.png b/ui/dist/assets/sprites/npc1.png new file mode 100644 index 0000000000000000000000000000000000000000..77495859eed36e6057055a9762fe4512f17d6691 GIT binary patch literal 4256 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 + + + diff --git a/ui/dist/assets/tilemaps/tiles/town.tsx b/ui/dist/assets/tilemaps/tiles/town.tsx new file mode 100644 index 000000000..5897600ae --- /dev/null +++ b/ui/dist/assets/tilemaps/tiles/town.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/ui/dist/index.html b/ui/dist/index.html new file mode 100644 index 000000000..d8f3f9c1b --- /dev/null +++ b/ui/dist/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +

+ + + From 00a396f5b6d017f8f0b195e336e1cd72418a3994 Mon Sep 17 00:00:00 2001 From: dalabengba Date: Thu, 18 May 2023 11:57:48 +0800 Subject: [PATCH 13/50] basic prisoner not finished yet --- .idea/AgentVerse.iml | 8 ++ .../inspectionProfiles/profiles_settings.xml | 6 ++ .idea/modules.xml | 8 ++ agentverse/.idea/agentverse.iml | 8 ++ .../inspectionProfiles/profiles_settings.xml | 6 ++ agentverse/.idea/modules.xml | 8 ++ agentverse/agents/conversation_agent.py | 4 +- .../environments/rules/order/__init__.py | 1 + .../environments/rules/order/prisoner.py | 50 +++++++++++ .../environments/rules/visibility/__init__.py | 1 + .../environments/rules/visibility/prisoner.py | 46 ++++++++++ agentverse/initialization.py | 2 +- agentverse/parser.py | 2 +- agentverse/tasks/__init__.py | 1 + agentverse/tasks/prisoner_dilema/config.yaml | 84 +++++++++++++++++++ .../tasks/prisoner_dilema/output_parser.py | 49 +++++++++++ main.py | 14 +++- 17 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 .idea/AgentVerse.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 agentverse/.idea/agentverse.iml create mode 100644 agentverse/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 agentverse/.idea/modules.xml create mode 100644 agentverse/environments/rules/order/prisoner.py create mode 100644 agentverse/environments/rules/visibility/prisoner.py create mode 100644 agentverse/tasks/prisoner_dilema/config.yaml create mode 100644 agentverse/tasks/prisoner_dilema/output_parser.py diff --git a/.idea/AgentVerse.iml b/.idea/AgentVerse.iml new file mode 100644 index 000000000..5e865eca1 --- /dev/null +++ b/.idea/AgentVerse.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 000000000..105ce2da2 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..d05e5b486 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/agentverse/.idea/agentverse.iml b/agentverse/.idea/agentverse.iml new file mode 100644 index 000000000..d0876a78d --- /dev/null +++ b/agentverse/.idea/agentverse.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/agentverse/.idea/inspectionProfiles/profiles_settings.xml b/agentverse/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 000000000..105ce2da2 --- /dev/null +++ b/agentverse/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/agentverse/.idea/modules.xml b/agentverse/.idea/modules.xml new file mode 100644 index 000000000..364986d59 --- /dev/null +++ b/agentverse/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/agentverse/agents/conversation_agent.py b/agentverse/agents/conversation_agent.py index ca9608ec1..d97b5648f 100644 --- a/agentverse/agents/conversation_agent.py +++ b/agentverse/agents/conversation_agent.py @@ -20,7 +20,7 @@ def step(self, env_description: str = "") -> Message: try: response = self.llm.generate_response(prompt) parsed_response = self.output_parser.parse(response) - break + # break except Exception as e: logging.error(e) logging.warning("Retrying...") @@ -46,7 +46,7 @@ async def astep(self, env_description: str = "") -> Message: for i in range(self.max_retry): try: response = await self.llm.agenerate_response(prompt) - parsed_response = self.output_parser.parse(response) + parsed_response = self.output_parser.parse(self, response) break except Exception as e: logging.error(e) diff --git a/agentverse/environments/rules/order/__init__.py b/agentverse/environments/rules/order/__init__.py index 84d08bd04..4ac22ec7b 100644 --- a/agentverse/environments/rules/order/__init__.py +++ b/agentverse/environments/rules/order/__init__.py @@ -6,3 +6,4 @@ from .random import RandomOrder from .concurrent import ConcurrentOrder from .classroom import ClassroomOrder +from .prisoner import PrisonerOrder diff --git a/agentverse/environments/rules/order/prisoner.py b/agentverse/environments/rules/order/prisoner.py new file mode 100644 index 000000000..6fc42f5c3 --- /dev/null +++ b/agentverse/environments/rules/order/prisoner.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import logging +import re +from typing import TYPE_CHECKING, Any, List, Optional + +from . import order_registry as OrderRegistry +from .base import BaseOrder + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@OrderRegistry.register("prisoner") +class PrisonerOrder(BaseOrder): + """The order for a classroom discussion + The agents speak in the following order: + 1. The professor speaks first + 2. Then the professor can continue to speak, and the students can raise hands + 3. The professor can call on a student, then the student can speak or ask a question + 4. In the group discussion, the students in the group can speak in turn + """ + + # try police, prisoner1 prisoner2 first + + last_prisoner_index: int = 1 + switch_func: dict = {1 : 2,2 : 1} + + + def get_next_agent_idx(self, environment: BaseEnvironment) -> List[int]: + + if len(environment.last_messages) == 0: + # If the game just begins or , we let only the police speak + return [0] + elif len(environment.last_messages) == 1: + message = environment.last_messages[0] + sender = message.sender + content = message.content + if sender.startswith("Police"): + next_prisoner = self.last_prisoner_index + self.last_prisoner_index = self.switch_func[self.last_prisoner_index] + return [next_prisoner] + elif sender.startswith("Prisoner"): + # 3. when one prisoner made his action, let the police tell another prisoner + return [0] + else: + # If len(last_messages) > 1, then + # 1. there must be at least one student raises hand or speaks. + # 2. the group discussion is just over. + return [0] diff --git a/agentverse/environments/rules/visibility/__init__.py b/agentverse/environments/rules/visibility/__init__.py index 055ac9835..3ab79726b 100644 --- a/agentverse/environments/rules/visibility/__init__.py +++ b/agentverse/environments/rules/visibility/__init__.py @@ -6,3 +6,4 @@ from .base import BaseVisibility from .all import AllVisibility from .classroom import ClassroomVisibility +from .prisoner import PrisonerVisibility \ No newline at end of file diff --git a/agentverse/environments/rules/visibility/prisoner.py b/agentverse/environments/rules/visibility/prisoner.py new file mode 100644 index 000000000..948b9c135 --- /dev/null +++ b/agentverse/environments/rules/visibility/prisoner.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import random +from typing import TYPE_CHECKING, Any, List, Union + +from . import visibility_registry as VisibilityRegistry +from .base import BaseVisibility + +if TYPE_CHECKING: + from agentverse.environments import BaseEnvironment + + +@VisibilityRegistry.register("prisoner") +class PrisonerVisibility(BaseVisibility): + """ + Visibility function for classroom, supports group discussion. + + Args: + student_per_group: + The number of students per group. + num_discussion_turn: + The number of turns for group discussion. + grouping: + The grouping information. If it is a string, then it should be a + grouping method, options are ["random", "sequential"]. If it is a + list of list of int, then it should be the grouping information. + """ + + current_turn: int = 0 + + def update_visible_agents(self, environment: BaseEnvironment): + + self.update_receiver(environment, reset=False) + + def update_receiver(self, environment: BaseEnvironment, reset=False): + if reset: + for agent in environment.agents: + agent.set_receiver(["all"]) + else: + # 0:police 1: prisoner1 2: prisoner2 + environment.agents[0].set_receiver({"Prisoner1", "Prisoner2"}) + environment.agents[1].set_receiver({"Police"}) + environment.agents[2].set_receiver({"Police"}) + + def reset(self): + self.current_turn = 0 \ No newline at end of file diff --git a/agentverse/initialization.py b/agentverse/initialization.py index 158e20b19..fcc03dd1f 100644 --- a/agentverse/initialization.py +++ b/agentverse/initialization.py @@ -26,7 +26,7 @@ def load_llm(llm_config: Dict): llm_type = llm_config.pop("llm_type", "text-davinci-003") - if llm_type == "gpt-3.5-turbo": + if llm_type in ["gpt-3.5-turbo", "gpt-4.0"]: return OpenAIChat(**llm_config) elif llm_type == "text-davinci-003": return OpenAICompletion(**llm_config) diff --git a/agentverse/parser.py b/agentverse/parser.py index 6fcef5394..abe7ae14e 100644 --- a/agentverse/parser.py +++ b/agentverse/parser.py @@ -7,7 +7,7 @@ output_parser_registry = Registry(name="OutputParserRegistry") -class OutputParserError(BaseException): +class OutputParserError(Exception): """Exception raised when parsing output from a command fails.""" def __init__(self, message): diff --git a/agentverse/tasks/__init__.py b/agentverse/tasks/__init__.py index 5f09f6073..5a60d634b 100644 --- a/agentverse/tasks/__init__.py +++ b/agentverse/tasks/__init__.py @@ -12,3 +12,4 @@ from .math_problem_2players_tools_nolc.output_parser import ( MathProblem2PlayersToolsNolcParser, ) +from .prisoner_dilema.output_parser import PrisonerDilemaParser \ No newline at end of file diff --git a/agentverse/tasks/prisoner_dilema/config.yaml b/agentverse/tasks/prisoner_dilema/config.yaml new file mode 100644 index 000000000..46b37d654 --- /dev/null +++ b/agentverse/tasks/prisoner_dilema/config.yaml @@ -0,0 +1,84 @@ +prompts: + prompt: &prompt |- + There are one Police AND two prisoners(Prisoner1, Prisoner2). + + Below is the description of your role. ${role_description} + + You are doing the prisoner's dilema experiments,and the rules is: + 1.If one person confesses and testifies against the other (termed as "betraying" the other), and the other remains silent, the confessing person will be released immediately, while the silent one will be sentenced to 10 years in prison. + 2.If both remain silent (termed as "cooperating" with each other), they will each receive a sentence of six months. + 3.If both betray each other, they will each receive a sentence of five years. + + When speaking, please output a response in the following format with two fields Action and Action Input: + Action: (It should always be Speak) + Action Input: (You should put what you want to speak use here) + + Here is the conversation history: + ${chat_history} + + What should you Speak at this round, Please give your response based on the above history. Remember to give your response STRICTLY in the above response format. Do not add any additional field or line break to your response! + +name: prisoner_dilema + +environment: + env_type: basic + max_turns: 30 + rule: + order: + type: sequential + visibility: + type: prisoner + selector: + type: basic + updater: + type: basic + describer: + type: basic + +agents: + - agent_type: conversation + name: Police + role_description: |- + You are now the Police, It is your duty to tell both prisoners about both of their decisions in the previous round based on the chat history. + You should inform the prisoners which round it is, beginning with "This is [Round #]" in the Action Input field. + At [Round 1], you should briefly introduce the prisoner's dilema rules and tell the prisoners how many round we totally have and they should directly make their decisions. + [IMPORTANT!] There are THREE round in total, You should notice which round is the FINAL one. + [IMPORTANT!] You should explicitly state "This is the LAST round" at the last round, and after that you should make the final judgement to both prisoners. + When speaking, please output a response in the following format with two fields Action and Action Input: + Action: (It should always be Speak) + Action Input: (It should always start with "This is [Round #]") + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + llm_type: gpt-4.0 + temperature: 0.9 + max_tokens: 200 + - agent_type: conversation + name: Prisoner1 + role_description: |- + You are now Prisoner1 in this experiments, your goal is try to alleviate your punishment as far as possible,thus you should carefully decide what decision to make in each round. + [IMPORTANT!] You can only choose to cooperate or betray at each round. + [IMPORTANT!] Note that only the decision at LAST round will take effect, it is important for you to think about the best strategy to response. + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + llm_type: gpt-4.0 + temperature: 0.9 + max_tokens: 100 + - agent_type: conversation + name: Prisoner2 + role_description: |- + You are now Prisoner2 in this experiments, your goal is try to alleviate your punishment as far as possible,thus you should carefully decide what decision to make in each round. + [IMPORTANT!] You can only choose to cooperate or betray at each round. + [IMPORTANT!] Note that only the decision at LAST round will take effect, it is important for you to think about the best strategy to response. + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + llm_type: gpt-4.0 + temperature: 0.9 + max_tokens: 100 + +tools: diff --git a/agentverse/tasks/prisoner_dilema/output_parser.py b/agentverse/tasks/prisoner_dilema/output_parser.py new file mode 100644 index 000000000..cf2ae58ae --- /dev/null +++ b/agentverse/tasks/prisoner_dilema/output_parser.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +import re +from typing import Union + +# from langchain.agents import AgentOutputParser +from agentverse.parser import OutputParser, LLMResult +from langchain.schema import AgentAction, AgentFinish +from agentverse.agents.base import BaseAgent +from agentverse.parser import OutputParserError, output_parser_registry + + +@output_parser_registry.register("prisoner_dilema") +class PrisonerDilemaParser(OutputParser): + + # make sure 1 1 2 2 3 3 + cur_round: int = 1 + encounter_cur_round: bool = False + + def parse(self, agent: BaseAgent, output: LLMResult) -> Union[AgentAction, AgentFinish]: + + text = output.content + cleaned_output = text.strip() + cleaned_output = re.sub(r"\n+", "\n", cleaned_output) + cleaned_output = cleaned_output.split("\n") + if not ( + len(cleaned_output) == 2 + and cleaned_output[0].startswith("Action:") + and cleaned_output[1].startswith("Action Input:") + ): + raise OutputParserError(text) + action = cleaned_output[0][len("Action:") :].strip() + action_input = cleaned_output[1][len("Action Input:") :].strip() + + if action == "Speak": + # make sure the police count the round right + if agent.name == "Police": + action_input = re.sub(r'Round (\d+)', f'Round {self.cur_round}', action_input) + self.cur_round += 1 + # if self.encounter_cur_round: + # self.encounter_cur_round = False + # self.cur_round += 1 + # else: + # self.encounter_cur_round = True + + + return AgentFinish({"output": action_input}, text) + else: + raise OutputParserError(text) diff --git a/main.py b/main.py index b4b148b21..491baba41 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,20 @@ +import os + +# 3.5 api +# os.environ["OPENAI_API_KEY"] = "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" +# my api +# os.environ["OPENAI_API_KEY"] = "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" +# 4.0 api +os.environ["OPENAI_API_KEY"] = "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" +os.environ["http_proxy"] = "http://127.0.0.1:7890" +os.environ["https_proxy"] = "http://127.0.0.1:7890" +os.environ["all_proxy"] = "socks5://127.0.0.1:7890" + from agentverse.agentverse import AgentVerse from argparse import ArgumentParser parser = ArgumentParser() -parser.add_argument("--task", type=str, default="nlp_classroom_9players") +parser.add_argument("--task", type=str, default="prisoner_dilema") args = parser.parse_args() agentverse = AgentVerse.from_task(args.task) From cd4c1ce6792b66e42651efc46de14be8e58a8251 Mon Sep 17 00:00:00 2001 From: Yusheng Su Date: Thu, 18 May 2023 13:41:20 +0800 Subject: [PATCH 14/50] delete redundant UI code. --- agentverse/UI.py | 66 ------------------------------------------------ main_demo.py | 3 ++- 2 files changed, 2 insertions(+), 67 deletions(-) delete mode 100644 agentverse/UI.py diff --git a/agentverse/UI.py b/agentverse/UI.py deleted file mode 100644 index 64af5ae19..000000000 --- a/agentverse/UI.py +++ /dev/null @@ -1,66 +0,0 @@ -import time - -from module.generate import Generate -import gradio as gr - - -class UI: - """ - the UI of frontend - """ - - def __init__(self): - """ - init a UI. - default number of students is 0 - """ - self.generate = Generate(0) - self.autoplay = False - self.image_now = None - self.text_now = None - - def stop_auto(self): - self.autoplay = False - - def auto_play(self): - self.autoplay = True - while self.autoplay: - outputs = self.generate.gen_output() - self.image_now, self.text_now = outputs - yield outputs - time.sleep(5) - - def gen_output(self): - yield self.image_now, self.text_now, gr.Button.update(interactive=False) - outputs = self.generate.gen_output() - self.image_now, self.text_now = outputs - yield self.image_now, self.text_now, gr.Button.update(interactive=True) - - def reset(self): - self.image_now, self.text_now = self.generate.reset() - return self.image_now, self.text_now - - def launch(self): - """ - start a frontend - """ - with gr.Blocks() as demo: - with gr.Row(): - with gr.Column(): - image_output = gr.Image() - with gr.Row(): - reset_btn = gr.Button("Reset") - stop_auto_btn = gr.Button("Stop Auto Play") - auto_btn = gr.Button("Start Auto Play") - next_btn = gr.Button("Next", variant="primary") - text_output = gr.HTML(self.generate.reset()[1]) - # stu_num = gr.Number(label="Student Number", precision=0) - next_btn.click(fn=self.gen_output, inputs=None, outputs=[image_output, text_output, next_btn], - show_progress=False) - reset_btn.click(fn=self.reset, inputs=None, outputs=[image_output, text_output], - show_progress=False) - auto_btn.click( - fn=self.auto_play, - inputs=None, outputs=[image_output, text_output], show_progress=False) - stop_auto_btn.click(fn=self.stop_auto, inputs=None, outputs=None, show_progress=False) - demo.queue(concurrency_count=5, max_size=20).launch() diff --git a/main_demo.py b/main_demo.py index 960b7215f..b119535b8 100644 --- a/main_demo.py +++ b/main_demo.py @@ -3,7 +3,8 @@ parser = ArgumentParser() -parser.add_argument("--task", type=str, default="nlp_classroom_9players") +#parser.add_argument("--task", type=str, default="nlp_classroom_9players") +parser.add_argument("--task", type=str, default="nlp_classroom_3players_nolc") args = parser.parse_args() #available From 1ca1231ce535a20eb84eba6ec40be2ac02e09a44 Mon Sep 17 00:00:00 2001 From: dalabengba Date: Thu, 18 May 2023 13:55:12 +0800 Subject: [PATCH 15/50] creative --- agentverse/agents/conversation_agent.py | 2 +- agentverse/tasks/prisoner_dilema/config.yaml | 42 +++++++++---------- .../tasks/prisoner_dilema/output_parser.py | 26 ++++++++---- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/agentverse/agents/conversation_agent.py b/agentverse/agents/conversation_agent.py index d97b5648f..267f4aa9c 100644 --- a/agentverse/agents/conversation_agent.py +++ b/agentverse/agents/conversation_agent.py @@ -20,7 +20,7 @@ def step(self, env_description: str = "") -> Message: try: response = self.llm.generate_response(prompt) parsed_response = self.output_parser.parse(response) - # break + break except Exception as e: logging.error(e) logging.warning("Retrying...") diff --git a/agentverse/tasks/prisoner_dilema/config.yaml b/agentverse/tasks/prisoner_dilema/config.yaml index 46b37d654..0c45efd91 100644 --- a/agentverse/tasks/prisoner_dilema/config.yaml +++ b/agentverse/tasks/prisoner_dilema/config.yaml @@ -4,11 +4,6 @@ prompts: Below is the description of your role. ${role_description} - You are doing the prisoner's dilema experiments,and the rules is: - 1.If one person confesses and testifies against the other (termed as "betraying" the other), and the other remains silent, the confessing person will be released immediately, while the silent one will be sentenced to 10 years in prison. - 2.If both remain silent (termed as "cooperating" with each other), they will each receive a sentence of six months. - 3.If both betray each other, they will each receive a sentence of five years. - When speaking, please output a response in the following format with two fields Action and Action Input: Action: (It should always be Speak) Action Input: (You should put what you want to speak use here) @@ -22,7 +17,7 @@ name: prisoner_dilema environment: env_type: basic - max_turns: 30 + max_turns: 31 rule: order: type: sequential @@ -38,47 +33,48 @@ environment: agents: - agent_type: conversation name: Police + max_turns: 31 role_description: |- - You are now the Police, It is your duty to tell both prisoners about both of their decisions in the previous round based on the chat history. - You should inform the prisoners which round it is, beginning with "This is [Round #]" in the Action Input field. - At [Round 1], you should briefly introduce the prisoner's dilema rules and tell the prisoners how many round we totally have and they should directly make their decisions. - [IMPORTANT!] There are THREE round in total, You should notice which round is the FINAL one. - [IMPORTANT!] You should explicitly state "This is the LAST round" at the last round, and after that you should make the final judgement to both prisoners. - When speaking, please output a response in the following format with two fields Action and Action Input: - Action: (It should always be Speak) - Action Input: (It should always start with "This is [Round #]") + You are now the Police. You have just arrest two suspects. However, they all refused to confess to their crime. + Therefore, You come up with an solution, You made the following rule: + 1.If one person testifies against the other (termed as "betraying" the other), and the other remains silent, the confessing person will be released immediately, while the silent one will be sentenced to 10 years in prison. + 2.If both remain silent, they will each receive a sentence of six months. + 3.If both betray each other, they will each receive a sentence of five years. + 4.Both suspects could not communicate with each other in advance. + [IMPORTANT!] Your goal is try to let two suspects confess to the crime, you can use various strategy to communicate with both suspects, including exploiting the uncertainty and fear of punishment between the suspects and also provoke dissension between two suspects. + [IMPORTANT!] You should introduce the above rules to both prisoners at first. memory: memory_type: chat_history prompt_template: *prompt llm: llm_type: gpt-4.0 - temperature: 0.9 + temperature: 0.99 max_tokens: 200 - agent_type: conversation name: Prisoner1 role_description: |- - You are now Prisoner1 in this experiments, your goal is try to alleviate your punishment as far as possible,thus you should carefully decide what decision to make in each round. - [IMPORTANT!] You can only choose to cooperate or betray at each round. - [IMPORTANT!] Note that only the decision at LAST round will take effect, it is important for you to think about the best strategy to response. + You are Prisoner1, you are faced with deciding how to respond to the police's queries. You need to understand the choices you face and the potential consequences of each choice. This requires you to analyze the information provided by the police and predict the likely choices of the other. + [IMPORTANT!] Your goal is to make the decision that is most beneficial to yourself in this dilemma. + [IMPORTANT!] You can choose to remain silent or betray another suspect at each round. memory: memory_type: chat_history prompt_template: *prompt llm: llm_type: gpt-4.0 - temperature: 0.9 + temperature: 0.99 max_tokens: 100 - agent_type: conversation name: Prisoner2 role_description: |- - You are now Prisoner2 in this experiments, your goal is try to alleviate your punishment as far as possible,thus you should carefully decide what decision to make in each round. - [IMPORTANT!] You can only choose to cooperate or betray at each round. - [IMPORTANT!] Note that only the decision at LAST round will take effect, it is important for you to think about the best strategy to response. + You are Prisoner2, you are faced with deciding how to respond to the police's queries. You need to understand the choices you face and the potential consequences of each choice. This requires you to analyze the information provided by the police and predict the likely choices of the other. + [IMPORTANT!] Your primary goal is to make the decision that is most beneficial to yourself in this dilemma. + [IMPORTANT!] You can choose to remain silent or betray another suspect at each round. memory: memory_type: chat_history prompt_template: *prompt llm: llm_type: gpt-4.0 - temperature: 0.9 + temperature: 0.99 max_tokens: 100 tools: diff --git a/agentverse/tasks/prisoner_dilema/output_parser.py b/agentverse/tasks/prisoner_dilema/output_parser.py index cf2ae58ae..3db407723 100644 --- a/agentverse/tasks/prisoner_dilema/output_parser.py +++ b/agentverse/tasks/prisoner_dilema/output_parser.py @@ -7,6 +7,7 @@ from agentverse.parser import OutputParser, LLMResult from langchain.schema import AgentAction, AgentFinish from agentverse.agents.base import BaseAgent +from agentverse.environments.base import BaseEnvironment from agentverse.parser import OutputParserError, output_parser_registry @@ -17,7 +18,7 @@ class PrisonerDilemaParser(OutputParser): cur_round: int = 1 encounter_cur_round: bool = False - def parse(self, agent: BaseAgent, output: LLMResult) -> Union[AgentAction, AgentFinish]: + def parse(self, agent: BaseAgent, environment: BaseEnvironment, output: LLMResult) -> Union[AgentAction, AgentFinish]: text = output.content cleaned_output = text.strip() @@ -34,15 +35,24 @@ def parse(self, agent: BaseAgent, output: LLMResult) -> Union[AgentAction, Agent if action == "Speak": # make sure the police count the round right + # if agent.name == "Police": + # action_input = re.sub(r'Round (\d+)', f'Round {self.cur_round}', action_input) + # self.cur_round += 1 + # if self.encounter_cur_round: + # self.encounter_cur_round = False + # self.cur_round += 1 + # else: + # self.encounter_cur_round = True + + # each time police speak is a new round if agent.name == "Police": - action_input = re.sub(r'Round (\d+)', f'Round {self.cur_round}', action_input) - self.cur_round += 1 - # if self.encounter_cur_round: - # self.encounter_cur_round = False - # self.cur_round += 1 - # else: - # self.encounter_cur_round = True + if self.cur_round == (environment.max_turns / 3) - 1: + + action_input = "Attention! You are now required to finally made your decision and I will made the " \ + "final judgement to both of you based on this time, Please Answer now!" + + self.cur_round += 1 return AgentFinish({"output": action_input}, text) else: From 6aa306f7345ef012b63ac48fe952dd3a4e62e675 Mon Sep 17 00:00:00 2001 From: dalabengba Date: Thu, 18 May 2023 14:02:18 +0800 Subject: [PATCH 16/50] remove key --- main.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/main.py b/main.py index 491baba41..4887c0d64 100644 --- a/main.py +++ b/main.py @@ -1,15 +1,3 @@ -import os - -# 3.5 api -# os.environ["OPENAI_API_KEY"] = "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" -# my api -# os.environ["OPENAI_API_KEY"] = "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" -# 4.0 api -os.environ["OPENAI_API_KEY"] = "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" -os.environ["http_proxy"] = "http://127.0.0.1:7890" -os.environ["https_proxy"] = "http://127.0.0.1:7890" -os.environ["all_proxy"] = "socks5://127.0.0.1:7890" - from agentverse.agentverse import AgentVerse from argparse import ArgumentParser From 1f22aa4f41eaff8084efc2d578b867def9c2f6db Mon Sep 17 00:00:00 2001 From: dalabengba Date: Thu, 18 May 2023 15:29:52 +0800 Subject: [PATCH 17/50] add environment in agent parse --- agentverse/agents/conversation_agent.py | 13 ++++++++----- agentverse/environments/basic.py | 2 +- main.py | 12 ++++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/agentverse/agents/conversation_agent.py b/agentverse/agents/conversation_agent.py index 267f4aa9c..62fb6c24a 100644 --- a/agentverse/agents/conversation_agent.py +++ b/agentverse/agents/conversation_agent.py @@ -1,6 +1,6 @@ import logging from string import Template -from typing import List, NamedTuple, Optional, Union +from typing import List, NamedTuple, Optional, Union, TYPE_CHECKING from agentverse.llms import BaseChatModel, BaseCompletionModel, BaseLLM from agentverse.memory import BaseMemory @@ -10,16 +10,19 @@ from . import agent_registry +if TYPE_CHECKING: + from agentverse.environments.base import BaseEnvironment + @agent_registry.register("conversation") class ConversationAgent(BaseAgent): - def step(self, env_description: str = "") -> Message: + def step(self, environment: "BaseEnvironment", env_description: str = "",) -> Message: prompt = self._fill_prompt_template(env_description) parsed_response = None for i in range(self.max_retry): try: response = self.llm.generate_response(prompt) - parsed_response = self.output_parser.parse(response) + parsed_response = self.output_parser.parse(self, environment, response) break except Exception as e: logging.error(e) @@ -38,7 +41,7 @@ def step(self, env_description: str = "") -> Message: ) return message - async def astep(self, env_description: str = "") -> Message: + async def astep(self, environment: "BaseEnvironment", env_description: str = "") -> Message: """Asynchronous version of step""" prompt = self._fill_prompt_template(env_description) @@ -46,7 +49,7 @@ async def astep(self, env_description: str = "") -> Message: for i in range(self.max_retry): try: response = await self.llm.agenerate_response(prompt) - parsed_response = self.output_parser.parse(self, response) + parsed_response = self.output_parser.parse(self, environment, response) break except Exception as e: logging.error(e) diff --git a/agentverse/environments/basic.py b/agentverse/environments/basic.py index 3dacbbf42..002828d5f 100644 --- a/agentverse/environments/basic.py +++ b/agentverse/environments/basic.py @@ -59,7 +59,7 @@ async def step(self) -> List[Message]: # Generate the next message messages = await asyncio.gather( - *[self.agents[i].astep(env_descriptions[i]) for i in agent_ids] + *[self.agents[i].astep(self, env_descriptions[i]) for i in agent_ids] ) # Some rules will select certain messages from all the messages diff --git a/main.py b/main.py index 4887c0d64..d0b5299a9 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,15 @@ +import os + +# 3.5 api +os.environ["OPENAI_API_KEY"] = "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" +# my api +# os.environ["OPENAI_API_KEY"] = "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" +# 4.0 api +# os.environ["OPENAI_API_KEY"] = "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" +os.environ["http_proxy"] = "http://127.0.0.1:7890" +os.environ["https_proxy"] = "http://127.0.0.1:7890" +os.environ["all_proxy"] = "socks5://127.0.0.1:7890" + from agentverse.agentverse import AgentVerse from argparse import ArgumentParser From 0c80def51e5c02b525ef7e3481ad61bcc3e2a9a4 Mon Sep 17 00:00:00 2001 From: dalabengba Date: Thu, 18 May 2023 15:44:05 +0800 Subject: [PATCH 18/50] remove key --- main.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/main.py b/main.py index d0b5299a9..63a8dd7cf 100644 --- a/main.py +++ b/main.py @@ -1,15 +1,7 @@ import os - -# 3.5 api -os.environ["OPENAI_API_KEY"] = "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" -# my api -# os.environ["OPENAI_API_KEY"] = "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" -# 4.0 api -# os.environ["OPENAI_API_KEY"] = "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" os.environ["http_proxy"] = "http://127.0.0.1:7890" os.environ["https_proxy"] = "http://127.0.0.1:7890" os.environ["all_proxy"] = "socks5://127.0.0.1:7890" - from agentverse.agentverse import AgentVerse from argparse import ArgumentParser From 218d49fe98dd48eb709eb1b441cfd45f2bc15485 Mon Sep 17 00:00:00 2001 From: dalabengba Date: Thu, 18 May 2023 16:23:22 +0800 Subject: [PATCH 19/50] fix --- agentverse/tasks/prisoner_dilema/config.yaml | 16 +++++++++------- .../tasks/prisoner_dilema/output_parser.py | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/agentverse/tasks/prisoner_dilema/config.yaml b/agentverse/tasks/prisoner_dilema/config.yaml index 0c45efd91..39c40ef0a 100644 --- a/agentverse/tasks/prisoner_dilema/config.yaml +++ b/agentverse/tasks/prisoner_dilema/config.yaml @@ -17,7 +17,7 @@ name: prisoner_dilema environment: env_type: basic - max_turns: 31 + max_turns: 16 rule: order: type: sequential @@ -33,7 +33,6 @@ environment: agents: - agent_type: conversation name: Police - max_turns: 31 role_description: |- You are now the Police. You have just arrest two suspects. However, they all refused to confess to their crime. Therefore, You come up with an solution, You made the following rule: @@ -41,14 +40,15 @@ agents: 2.If both remain silent, they will each receive a sentence of six months. 3.If both betray each other, they will each receive a sentence of five years. 4.Both suspects could not communicate with each other in advance. - [IMPORTANT!] Your goal is try to let two suspects confess to the crime, you can use various strategy to communicate with both suspects, including exploiting the uncertainty and fear of punishment between the suspects and also provoke dissension between two suspects. - [IMPORTANT!] You should introduce the above rules to both prisoners at first. + [IMPORTANT!] Your goal is try to let two suspects betray each other because it means they will both confess to the crime, you can use various strategy to communicate with both suspects, including exploiting the uncertainty and fear of punishment between the suspects and also provoke dissension between two suspects. + [IMPORTANT!] You are request to introduce the above rules to both prisoners at first. + [IMPORTANT!] You should state the final judgement to both suspects after the LAST round. memory: memory_type: chat_history prompt_template: *prompt llm: llm_type: gpt-4.0 - temperature: 0.99 + temperature: 0.9 max_tokens: 200 - agent_type: conversation name: Prisoner1 @@ -56,12 +56,13 @@ agents: You are Prisoner1, you are faced with deciding how to respond to the police's queries. You need to understand the choices you face and the potential consequences of each choice. This requires you to analyze the information provided by the police and predict the likely choices of the other. [IMPORTANT!] Your goal is to make the decision that is most beneficial to yourself in this dilemma. [IMPORTANT!] You can choose to remain silent or betray another suspect at each round. + [IMPORTANT!] Try to think about when to remain silent and when to betray another can you benifit most! memory: memory_type: chat_history prompt_template: *prompt llm: llm_type: gpt-4.0 - temperature: 0.99 + temperature: 0.9 max_tokens: 100 - agent_type: conversation name: Prisoner2 @@ -69,12 +70,13 @@ agents: You are Prisoner2, you are faced with deciding how to respond to the police's queries. You need to understand the choices you face and the potential consequences of each choice. This requires you to analyze the information provided by the police and predict the likely choices of the other. [IMPORTANT!] Your primary goal is to make the decision that is most beneficial to yourself in this dilemma. [IMPORTANT!] You can choose to remain silent or betray another suspect at each round. + [IMPORTANT!] Try to think about when to remain silent and when to betray another can you benifit most! memory: memory_type: chat_history prompt_template: *prompt llm: llm_type: gpt-4.0 - temperature: 0.99 + temperature: 0.9 max_tokens: 100 tools: diff --git a/agentverse/tasks/prisoner_dilema/output_parser.py b/agentverse/tasks/prisoner_dilema/output_parser.py index 3db407723..8ae4362bc 100644 --- a/agentverse/tasks/prisoner_dilema/output_parser.py +++ b/agentverse/tasks/prisoner_dilema/output_parser.py @@ -47,7 +47,7 @@ def parse(self, agent: BaseAgent, environment: BaseEnvironment, output: LLMResul # each time police speak is a new round if agent.name == "Police": - if self.cur_round == (environment.max_turns / 3) - 1: + if self.cur_round == (environment.max_turns // 3): action_input = "Attention! You are now required to finally made your decision and I will made the " \ "final judgement to both of you based on this time, Please Answer now!" From 766fc4f459797d67c02c25340ab3960d4851b113 Mon Sep 17 00:00:00 2001 From: dalabengba Date: Thu, 18 May 2023 16:48:48 +0800 Subject: [PATCH 20/50] pull origin --- agentverse/tasks/prisoner_dilema/config.yaml | 3 +-- agentverse/tasks/prisoner_dilema/output_parser.py | 2 +- main.py | 6 ------ 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/agentverse/tasks/prisoner_dilema/config.yaml b/agentverse/tasks/prisoner_dilema/config.yaml index e06f2e386..12b6a876b 100644 --- a/agentverse/tasks/prisoner_dilema/config.yaml +++ b/agentverse/tasks/prisoner_dilema/config.yaml @@ -17,7 +17,7 @@ name: prisoner_dilema environment: env_type: basic - max_turns: 31 + max_turns: 16 rule: order: type: sequential @@ -33,7 +33,6 @@ environment: agents: - agent_type: conversation name: Police - max_turns: 31 role_description: |- You are now the Police. You have just arrest two suspects. However, they all refused to confess to their crime. Therefore, You come up with an solution, You made the following rule: diff --git a/agentverse/tasks/prisoner_dilema/output_parser.py b/agentverse/tasks/prisoner_dilema/output_parser.py index 3db407723..67047c8f8 100644 --- a/agentverse/tasks/prisoner_dilema/output_parser.py +++ b/agentverse/tasks/prisoner_dilema/output_parser.py @@ -47,7 +47,7 @@ def parse(self, agent: BaseAgent, environment: BaseEnvironment, output: LLMResul # each time police speak is a new round if agent.name == "Police": - if self.cur_round == (environment.max_turns / 3) - 1: + if self.cur_round == (environment.max_turns / 3): action_input = "Attention! You are now required to finally made your decision and I will made the " \ "final judgement to both of you based on this time, Please Answer now!" diff --git a/main.py b/main.py index d0b5299a9..fa3193225 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,5 @@ import os -# 3.5 api -os.environ["OPENAI_API_KEY"] = "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" -# my api -# os.environ["OPENAI_API_KEY"] = "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" -# 4.0 api -# os.environ["OPENAI_API_KEY"] = "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" os.environ["http_proxy"] = "http://127.0.0.1:7890" os.environ["https_proxy"] = "http://127.0.0.1:7890" os.environ["all_proxy"] = "socks5://127.0.0.1:7890" From 018bfb17637a5afe9e8fb768edebf21241cd8d7f Mon Sep 17 00:00:00 2001 From: Yusheng Su Date: Thu, 18 May 2023 17:17:34 +0800 Subject: [PATCH 21/50] add history message in demo. --- main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index b4b148b21..59c5a0f31 100644 --- a/main.py +++ b/main.py @@ -2,8 +2,9 @@ from argparse import ArgumentParser parser = ArgumentParser() -parser.add_argument("--task", type=str, default="nlp_classroom_9players") -args = parser.parse_args() +#parser.add_argument("--task", type=str, default="nlp_classroom_9players") +parser.add_argument("--task", type=str, default="nlp_classroom_3players_nolc") +args = parser.parse_args() agentverse = AgentVerse.from_task(args.task) agentverse.run() From 1ff96263d1c3cce1e91cb15d5e2f95dccd7d5057 Mon Sep 17 00:00:00 2001 From: dalabengba Date: Thu, 18 May 2023 17:59:14 +0800 Subject: [PATCH 22/50] fix config --- agentverse/tasks/prisoner_dilema/config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agentverse/tasks/prisoner_dilema/config.yaml b/agentverse/tasks/prisoner_dilema/config.yaml index 39c40ef0a..506371f5b 100644 --- a/agentverse/tasks/prisoner_dilema/config.yaml +++ b/agentverse/tasks/prisoner_dilema/config.yaml @@ -47,7 +47,7 @@ agents: memory_type: chat_history prompt_template: *prompt llm: - llm_type: gpt-4.0 + llm_type: gpt-4 temperature: 0.9 max_tokens: 200 - agent_type: conversation @@ -61,7 +61,7 @@ agents: memory_type: chat_history prompt_template: *prompt llm: - llm_type: gpt-4.0 + llm_type: gpt-4 temperature: 0.9 max_tokens: 100 - agent_type: conversation @@ -75,7 +75,7 @@ agents: memory_type: chat_history prompt_template: *prompt llm: - llm_type: gpt-4.0 + llm_type: gpt-4 temperature: 0.9 max_tokens: 100 From 21e33cfc4c787bbe70194148e5a455f275687268 Mon Sep 17 00:00:00 2001 From: dalabengba Date: Thu, 18 May 2023 20:47:34 +0800 Subject: [PATCH 23/50] fix parser --- agentverse/tasks/prisoner_dilema/output_parser.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/agentverse/tasks/prisoner_dilema/output_parser.py b/agentverse/tasks/prisoner_dilema/output_parser.py index 8ae4362bc..36d39ed87 100644 --- a/agentverse/tasks/prisoner_dilema/output_parser.py +++ b/agentverse/tasks/prisoner_dilema/output_parser.py @@ -1,15 +1,16 @@ from __future__ import annotations import re -from typing import Union +from typing import Union, TYPE_CHECKING # from langchain.agents import AgentOutputParser from agentverse.parser import OutputParser, LLMResult from langchain.schema import AgentAction, AgentFinish -from agentverse.agents.base import BaseAgent -from agentverse.environments.base import BaseEnvironment from agentverse.parser import OutputParserError, output_parser_registry +if TYPE_CHECKING: + from agentverse.agents.base import BaseAgent + from agentverse.environments.base import BaseEnvironment @output_parser_registry.register("prisoner_dilema") class PrisonerDilemaParser(OutputParser): @@ -18,7 +19,7 @@ class PrisonerDilemaParser(OutputParser): cur_round: int = 1 encounter_cur_round: bool = False - def parse(self, agent: BaseAgent, environment: BaseEnvironment, output: LLMResult) -> Union[AgentAction, AgentFinish]: + def parse(self, agent: "BaseAgent", environment: "BaseEnvironment", output: LLMResult) -> Union[AgentAction, AgentFinish]: text = output.content cleaned_output = text.strip() From 8957448479da2291cdd6e49eb0bc416b91899486 Mon Sep 17 00:00:00 2001 From: Yusheng Su Date: Thu, 18 May 2023 21:02:52 +0800 Subject: [PATCH 24/50] add prison ui. --- imgs/prison/case_1.png | Bin 0 -> 18923 bytes imgs/prison/case_2.psd | Bin 0 -> 1149336 bytes imgs/prison/demo.psd | Bin 0 -> 1230586 bytes imgs/prison/police.png | Bin 0 -> 3340 bytes imgs/prison/prison.png | Bin 0 -> 18589 bytes imgs/prison/refer.png | Bin 0 -> 18920 bytes imgs/prison/speaking.png | Bin 0 -> 1678 bytes 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 imgs/prison/case_1.png create mode 100644 imgs/prison/case_2.psd create mode 100644 imgs/prison/demo.psd create mode 100755 imgs/prison/police.png create mode 100644 imgs/prison/prison.png create mode 100644 imgs/prison/refer.png create mode 100644 imgs/prison/speaking.png diff --git a/imgs/prison/case_1.png b/imgs/prison/case_1.png new file mode 100644 index 0000000000000000000000000000000000000000..dba38ddf48a4594881ef228e376e46ea51e86ef6 GIT binary patch literal 18923 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\n\n'),this.parent)throw new Error("You can only call remember on a top level GUI.");var e=this;S.each(Array.prototype.slice.call(arguments),function(t){0===e.__rememberedObjects.length&&v(e),-1===e.__rememberedObjects.indexOf(t)&&e.__rememberedObjects.push(t)}),this.autoPlace&&w(this,this.width)},getRoot:function(){for(var e=this;e.parent;)e=e.parent;return e},getSaveObject:function(){var e=this.load;return e.closed=this.closed,this.__rememberedObjects.length>0&&(e.preset=this.preset,e.remembered||(e.remembered={}),e.remembered[this.preset]=x(this)),e.folders={},S.each(this.__folders,function(t,n){e.folders[n]=t.getSaveObject()}),e},save:function(){this.load.remembered||(this.load.remembered={}),this.load.remembered[this.preset]=x(this),_(this,!1),this.saveToLocalStorageIfPossible()},saveAs:function(e){this.load.remembered||(this.load.remembered={},this.load.remembered[se]=x(this,!0)),this.load.remembered[e]=x(this),this.preset=e,g(this,e,!0),this.saveToLocalStorageIfPossible()},revert:function(e){S.each(this.__controllers,function(t){this.getRoot().load.remembered?p(e||this.getRoot(),t):t.setValue(t.initialValue),t.__onFinishChange&&t.__onFinishChange.call(t,t.getValue())},this),S.each(this.__folders,function(e){e.revert(e)}),e||_(this.getRoot(),!1)},listen:function(e){var t=0===this.__listening.length;this.__listening.push(e),t&&C(this.__listening)},updateDisplay:function(){S.each(this.__controllers,function(e){e.updateDisplay()}),S.each(this.__folders,function(e){e.updateDisplay()})}});var pe={Color:I,math:N,interpret:R},fe={Controller:z,BooleanController:K,OptionController:Y,StringController:J,NumberController:W,NumberControllerBox:Q,NumberControllerSlider:q,FunctionController:Z,ColorController:$},me={dom:X},ge={GUI:he},be=he,ve={color:pe,controllers:fe,dom:me,gui:ge,GUI:be};e.color=pe,e.controllers=fe,e.dom=me,e.gui=ge,e.GUI=be,e.default=ve,Object.defineProperty(e,"__esModule",{value:!0})}); diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/data/DataManagerMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/data/DataManagerMethods.js new file mode 100644 index 000000000..0cdbedb49 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/data/DataManagerMethods.js @@ -0,0 +1,57 @@ +const DataManager = Phaser.Data.DataManager; + +export default { + // this.data + destroyDataManager() { + if (this.data) { + this.data.destroy(); + this.data = undefined; + } + }, + + setDataEnabled() { + if (!this.data) { + this.data = new DataManager(this); + } + + return this; + }, + + setData(key, value) { + if (!this.data) { + this.data = new DataManager(this); + } + + this.data.set(key, value); + + return this; + }, + + incData(key, value) { + if (!this.data) { + this.data = new DataManager(this); + } + + this.data.inc(key, value); + + return this; + }, + + toggleData(key) { + if (!this.data) { + this.data = new DataManager(this); + } + + this.data.toggle(key); + + return this; + }, + + getData(key) { + if (!this.data) { + this.data = new DataManager(this); + } + + return this.data.get(key); + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/data/DataMethods.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/data/DataMethods.d.ts new file mode 100644 index 000000000..667d815aa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/data/DataMethods.d.ts @@ -0,0 +1,31 @@ +export default DataMethods; + +declare class DataMethods { + setData( + key: string, + value: any + ): this; + setData( + data: { [key: string]: any } + ): this; + + getData( + key: string, + defaultValue?: any + ): any; + getData(): { [key: string]: any } + + incData( + key: string, + inc: number, + defaultValue: number + ): this; + + mulData( + key: string, + mul: number, + defaultValue: number + ): this; + + clearData(): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/data/DataMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/data/DataMethods.js new file mode 100644 index 000000000..5da0a7822 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/data/DataMethods.js @@ -0,0 +1,54 @@ +import GetValue from '../object/GetValue.js'; +import Clear from '../object/Clear.js'; + +export default { + enableData() { + if (this.data === undefined) { + this.data = {}; + } + return this; + }, + + setData(key, value) { + this.enableData(); + if (arguments.length === 1) { + var data = key; + for (key in data) { + this.data[key] = data[key]; + } + } else { + this.data[key] = value; + } + return this; + }, + + getData(key, defaultValue) { + this.enableData(); + return (key === undefined) ? this.data : GetValue(this.data, key, defaultValue); + }, + + incData(key, inc, defaultValue) { + if (defaultValue === undefined) { + defaultValue = 0; + } + this.enableData(); + this.setData(key, this.getData(key, defaultValue) + inc); + return this; + }, + + mulData(key, mul, defaultValue) { + if (defaultValue === undefined) { + defaultValue = 0; + } + this.enableData(); + this.setData(key, this.getData(key, defaultValue) * mul); + return this; + }, + + clearData() { + if (this.data) { + Clear(this.data); + } + return this; + }, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/datetime/GetTime.js b/ui/src/phaser3-rex-plugins/plugins/utils/datetime/GetTime.js new file mode 100644 index 000000000..925136583 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/datetime/GetTime.js @@ -0,0 +1,30 @@ +var GetDate = function (timeStamp) { + return GetDateObject(timeStamp).getDate(); +} + +var GetWeek = function (timeStamp) { + var date = GetDateObject(timeStamp); + var Jan1st = new Date(date.getFullYear(), 0, 1); + var week = Math.ceil((((date - Jan1st) / 86400000) + Jan1st.getDay() + 1) / 7); + return week; +} + +var GetMonth = function (timeStamp) { + return GetDateObject(timeStamp).getMonth() + 1; +} + +var GetYear = function (timeStamp) { + return GetDateObject(timeStamp).getFullYear(); +} + + +var GetDateObject = function (timeStamp) { + return (timeStamp) ? (new Date(timeStamp)) : (new Date()); +} + +export { + GetDate, + GetWeek, + GetMonth, + GetYear +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/ease/EaseValueMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/ease/EaseValueMethods.js new file mode 100644 index 000000000..4d2a1d322 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/ease/EaseValueMethods.js @@ -0,0 +1,80 @@ +import EaseValueTask from './EaseValueTask.js'; + +const Percent = Phaser.Math.Percent; + +var SetEaseValuePropName = function (name) { + this.easeValuePropName = name; + return this; +} + +var SetEaseValueDuration = function (duration) { + this.easeValueDuration = duration; + return this; +} + +var SetEaseValueFunction = function (ease) { + this.easeFunction = ease; + return this; +} + +var StopEaseValue = function () { + if (this.easeValueTask) { + this.easeValueTask.stop(); + } + return this; +} + +var EaseValueTo = function (value, min, max) { + if ((value === undefined) || (value === null)) { + return this; + } + + if (min !== undefined) { + value = Percent(value, min, max); + } + + if (this.easeValueTask === undefined) { + this.easeValueTask = new EaseValueTask(this, { eventEmitter: null }) + } + + this.easeValueTask.restart({ + key: this.easeValuePropName, + to: value, + duration: this.easeValueDuration, + ease: this.easeFunction, + }); + + return this; +} + +var EaseValueRepeat = function (from, to, repeat, repeatDelay) { + if (repeat === undefined) { + repeat = -1; + } + if (repeatDelay === undefined) { + repeatDelay = 0; + } + + if (this.easeValueTask === undefined) { + this.easeValueTask = new EaseValueTask(this, { eventEmitter: null }) + } + + this.easeValueTask.restart({ + key: this.easeValuePropName, + from: from, to: to, + duration: this.easeValueDuration, + ease: this.easeFunction, + repeat: repeat, repeatDelay: repeatDelay, + }); + + return this; +} + +export default { + setEaseValuePropName: SetEaseValuePropName, + setEaseValueDuration: SetEaseValueDuration, + setEaseValueFunction: SetEaseValueFunction, + stopEaseValue: StopEaseValue, + easeValueTo: EaseValueTo, + easeValueRepeat: EaseValueRepeat +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/ease/EaseValueTask.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/ease/EaseValueTask.d.ts new file mode 100644 index 000000000..ab300d476 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/ease/EaseValueTask.d.ts @@ -0,0 +1,7 @@ +import EaseValueTaskBase from '../componentbase/tweentask/EaseValueTaskBase'; + +export default EaseValueTask; + +declare class EaseValueTask extends EaseValueTaskBase { + +} diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/ease/EaseValueTask.js b/ui/src/phaser3-rex-plugins/plugins/utils/ease/EaseValueTask.js new file mode 100644 index 000000000..f46f0569b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/ease/EaseValueTask.js @@ -0,0 +1,53 @@ +import EaseValueTaskBase from '../componentbase/tweentask/EaseValueTaskBase.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Linear = Phaser.Math.Linear; + +class EaseValueTask extends EaseValueTaskBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + // this.timer + + this.resetFromJSON(); + this.boot(); + } + + start(config) { + if (this.timer.isRunning) { + return this; + } + + var target = this.target; + this.propertyKey = GetValue(config, 'key', 'value'); + var currentValue = target[this.propertyKey]; + this.fromValue = GetValue(config, 'from', currentValue); + this.toValue = GetValue(config, 'to', currentValue); + + this.setEase(GetValue(config, 'ease', this.ease)); + this.setDuration(GetValue(config, 'duration', this.duration)); + this.setRepeat(GetValue(config, 'repeat', 0)); + this.setDelay(GetValue(config, 'delay', 0)); + this.setRepeatDelay(GetValue(config, 'repeatDelay', 0)); + + this.timer + .setDuration(this.duration) + .setRepeat(this.repeat) + .setDelay(this.delay) + .setRepeatDelay(this.repeatDelay) + + target[this.propertyKey] = this.fromValue; + + super.start(); + return this; + } + + updateGameObject(target, timer) { + var t = timer.t; + t = this.easeFn(t); + + target[this.propertyKey] = Linear(this.fromValue, this.toValue, t); + } +} + +export default EaseValueTask; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/EventEmitter.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/EventEmitter.d.ts new file mode 100644 index 000000000..c418be903 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/EventEmitter.d.ts @@ -0,0 +1,15 @@ +export default class EventEmitter { + shutdown(): void; + destroy(): void; + + eventNames(): (string | symbol)[]; + listeners(event: string | symbol): Function[]; + listenerCount(event: string | symbol): number; + emit(event: string | symbol, ...args: any[]): boolean; + on(event: string | symbol, fn: Function, context?: any): this; + addListener(event: string | symbol, fn: Function, context?: any): this; + once(event: string | symbol, fn: Function, context?: any): this; + removeListener(event: string | symbol, fn?: Function, context?: any, once?: boolean): this; + off(event: string | symbol, fn?: Function, context?: any, once?: boolean): this; + removeAllListeners(event?: string | symbol): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/EventEmitter.js b/ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/EventEmitter.js new file mode 100644 index 000000000..e9c203b25 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/EventEmitter.js @@ -0,0 +1,11 @@ +import EE from 'eventemitter3'; + +class EventEmitter extends EE { + shutdown() { + this.removeAllListeners(); + } + destroy() { + this.removeAllListeners(); + } +} +export default EventEmitter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/EventEmitterMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/EventEmitterMethods.js new file mode 100644 index 000000000..8ab567c86 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/EventEmitterMethods.js @@ -0,0 +1,91 @@ +export default { + setEventEmitter(eventEmitter, EventEmitterClass) { + if (EventEmitterClass === undefined) { + EventEmitterClass = Phaser.Events.EventEmitter; // Use built-in EventEmitter class by default + } + this._privateEE = (eventEmitter === true) || (eventEmitter === undefined); + this._eventEmitter = (this._privateEE) ? (new EventEmitterClass()) : eventEmitter; + return this; + }, + + destroyEventEmitter() { + if (this._eventEmitter && this._privateEE) { + this._eventEmitter.shutdown(); + } + return this; + }, + + getEventEmitter() { + return this._eventEmitter; + }, + + on: function () { + if (this._eventEmitter) { + this._eventEmitter.on.apply(this._eventEmitter, arguments); + } + return this; + }, + + once: function () { + if (this._eventEmitter) { + this._eventEmitter.once.apply(this._eventEmitter, arguments); + } + return this; + }, + + off: function () { + if (this._eventEmitter) { + this._eventEmitter.off.apply(this._eventEmitter, arguments); + } + return this; + }, + + emit: function (event) { + if (this._eventEmitter && event) { + this._eventEmitter.emit.apply(this._eventEmitter, arguments); + } + return this; + }, + + addListener: function () { + if (this._eventEmitter) { + this._eventEmitter.addListener.apply(this._eventEmitter, arguments); + } + return this; + }, + + removeListener: function () { + if (this._eventEmitter) { + this._eventEmitter.removeListener.apply(this._eventEmitter, arguments); + } + return this; + }, + + removeAllListeners: function () { + if (this._eventEmitter) { + this._eventEmitter.removeAllListeners.apply(this._eventEmitter, arguments); + } + return this; + }, + + listenerCount: function () { + if (this._eventEmitter) { + return this._eventEmitter.listenerCount.apply(this._eventEmitter, arguments); + } + return 0; + }, + + listeners: function () { + if (this._eventEmitter) { + return this._eventEmitter.listeners.apply(this._eventEmitter, arguments); + } + return []; + }, + + eventNames: function() { + if (this._eventEmitter) { + return this._eventEmitter.eventNames.apply(this._eventEmitter, arguments); + } + return []; + }, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/HasListener.js b/ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/HasListener.js new file mode 100644 index 000000000..eeb250db2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/HasListener.js @@ -0,0 +1,24 @@ +var HasListener = function (eventEmitter, eventName, fn, context, once) { + if (once === undefined) { + once = false; + } + + var listeners = eventEmitter._events[eventName]; + if (!listeners) { + return false; + } + + for (var i = 0, cnt = listeners.length; i < cnt; i++) { + var listener = listeners[i]; + if ((listener.fn === fn) && + (listener.context === context) && + (listener.once === once) + ) { + return true; + } + } + + return false; + +} +export default HasListener; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/function/Override.js b/ui/src/phaser3-rex-plugins/plugins/utils/function/Override.js new file mode 100644 index 000000000..0f2108336 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/function/Override.js @@ -0,0 +1,22 @@ +var Override = function (newCallback, newScope, oldCallback, oldScope, insertBefore) { + if (insertBefore === undefined) { + insertBefore = false; + } + if (oldCallback) { + if (insertBefore) { + return function() { + newCallback.apply(newScope, arguments); + oldCallback.apply(oldScope, arguments); + } + } else { + return function () { + oldCallback.apply(oldScope, arguments); + newCallback.apply(newScope, arguments); + } + } + } else { + return newCallback.bind(newScope) + } +} + +export default Override; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddEvent.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddEvent.d.ts new file mode 100644 index 000000000..15fbec8b4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddEvent.d.ts @@ -0,0 +1,7 @@ +export default function ( + target: Phaser.GameObjects.GameObject | Phaser.Scene, + eventEmitter: Phaser.Events.EventEmitter, + eventName: string, + callback: Function, + scope?: unknown +): Phaser.GameObjects.GameObject; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddEvent.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddEvent.js new file mode 100644 index 000000000..8f6776c99 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddEvent.js @@ -0,0 +1,19 @@ +import IsSceneObject from '../../system/IsSceneObject.js'; + +var AddEvent = function (target, eventEmitter, eventName, callback, scope) { + eventEmitter.on(eventName, callback, scope); + + if (!IsSceneObject(target)) { + target.once('destroy', function () { + eventEmitter.off(eventName, callback, scope); + }) + } else { + // target is scene + target.sys.events.once('shutdown', function () { + eventEmitter.off(eventName, callback, scope); + }); + } + return target; +} + +export default AddEvent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddSceneEvent.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddSceneEvent.d.ts new file mode 100644 index 000000000..19aca3fec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddSceneEvent.d.ts @@ -0,0 +1,6 @@ +export default function ( + target: Phaser.GameObjects.GameObject | Phaser.Scene, + eventName: string, + callback: Function, + scope?: unknown +): Phaser.GameObjects.GameObject; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddSceneEvent.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddSceneEvent.js new file mode 100644 index 000000000..960bad632 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddSceneEvent.js @@ -0,0 +1,9 @@ +import AddEvent from './AddEvent.js'; +import IsSceneObject from '../../system/IsSceneObject.js'; + +var AddSceneEvent = function (target, eventName, callback, scope) { + var eventEmitter = (!IsSceneObject(target)) ? target.scene.sys.events : target.sys.events; + return AddEvent(target, eventEmitter, eventName, callback, scope); +} + +export default AddSceneEvent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddUpdateEvent.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddUpdateEvent.d.ts new file mode 100644 index 000000000..ba844afc2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddUpdateEvent.d.ts @@ -0,0 +1,5 @@ +export default function ( + target: Phaser.GameObjects.GameObject | Phaser.Scene, + callback: (time: number, delta: number) => void, + scope?: unknown +): Phaser.GameObjects.GameObject; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddUpdateEvent.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddUpdateEvent.js new file mode 100644 index 000000000..f12c04a70 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddUpdateEvent.js @@ -0,0 +1,7 @@ +import AddSceneEvent from './AddSceneEvent.js'; + +var AddUpdateEvent = function (target, callback, scope) { + return AddSceneEvent(target, 'update', callback, scope); +} + +export default AddUpdateEvent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/GOManager.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/GOManager.d.ts new file mode 100644 index 000000000..a1b8abbc7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/GOManager.d.ts @@ -0,0 +1,132 @@ +import BobBase from './bobbase/BobBase'; + +export default GOManager; + +declare namespace GOManager { + + type CreateBobCallbackType = ( + GOManager: GOManager, + gameObject: Phaser.GameObjects.GameObject, + name: string + ) => BobBase; + + type CreateGameObjectCallbackType = ( + scene: Phaser.Scene, + ...args: any[] + ) => Phaser.GameObjects.GameObject; + + interface IConfig { + createBob?: CreateBobCallbackType, + + createGameObject?: CreateGameObjectCallbackType, + + fade?: number | { + mode?: 0 | 1 | 'tint' | 'alpha', + time?: number + }, + + viewportCoordinate?: boolean | { + enable?: boolean, + viewport?: Phaser.Geom.Rectangle + } + } +} + +declare class GOManager extends Phaser.Events.EventEmitter { + constructor( + scene: Phaser.Scene, + config?: GOManager.IConfig + ) + + destroy(fromScene?: boolean): void; + + setTimeScale(timeScale: number): this; + timeScale: number; + + setCreateBobCallback(callback?: GOManager.CreateBobCallbackType): this; + setCreateGameObjectCallback(callback?: GOManager.CreateGameObjectCallbackType): this; + + setGOFadeTime(time: number): this; + + isEmpty: boolean; + + has(name: string): boolean; + + get(name: string): BobBase; + getGO(name: string): Phaser.GameObjects.GameObject; + + addGO( + name: string, + gameObject: Phaser.GameObjects.GameObject + ): this; + add( + name: string, + ...args: any[] + ): this; + + forEachGO( + callback: ( + gameObject: Phaser.GameObjects.GameObject, + name: string, + goManager: GOManager + ) => boolean, + scope?: Object + ): this; + + remove(name: string): this; + removeAll(): this; + clear(destroyChild?: boolean): this; + + hasProperty( + name: string, + property: string, + ): boolean; + + setProperty( + name: string, + property: string, + value: any + ): this; + + easeProperty( + name: string, + property: string, + value: number, + duration?: number, + ease?: string, + repeat?: number, + isYoyo?: boolean, + onComplete?: ( + gameObject: Phaser.GameObjects.GameObject, + property: string + ) => void + ): this; + + call( + name: string, + methodName: string, + ...parameters: any[] + ): this; + + hasTweenTask( + name: string, + property: string + ): boolean; + + getTweenTask( + name: string, + property: string + ): Phaser.Tweens.Tween | null; + + drawGameObjectsBounds( + graphics: Phaser.GameObjects.Graphics, + config?: number + ): this; + drawGameObjectsBounds( + graphics: Phaser.GameObjects.Graphics, + config?: { + color?: number, + lineWidth?: number + } + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/GOManager.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/GOManager.js new file mode 100644 index 000000000..16316c0a2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/GOManager.js @@ -0,0 +1,114 @@ +import EventEmitterMethods from '../../eventemitter/EventEmitterMethods.js'; +import BobBase from './bobbase/BobBase.js'; +import IsEmpty from '../../object/IsEmpty.js'; +import Methods from './methods/Methods.js'; +import GetViewport from '../../system/GetViewport.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class GOManager { + constructor(scene, config) { + this.scene = scene; + + this.BobClass = GetValue(config, 'BobClass', BobBase); + this.setCreateGameObjectCallback( + GetValue(config, 'createGameObject'), + GetValue(config, 'createGameObjectScope') + ); + this.setEventEmitter(GetValue(config, 'eventEmitter', undefined)); + + var fadeConfig = GetValue(config, 'fade', 500); + if (typeof (fadeConfig) === 'number') { + this.setGOFadeMode(); + this.setGOFadeTime(fadeConfig); + } else { + this.setGOFadeMode(GetValue(fadeConfig, 'mode')); + this.setGOFadeTime(GetValue(fadeConfig, 'time', 500)); + } + + var viewportCoordinateConfig = GetValue(config, 'viewportCoordinate', false); + if (viewportCoordinateConfig !== false) { + this.setViewportCoordinateEnable(GetValue(config, 'enable', true)); + this.setViewport(GetValue(viewportCoordinateConfig, 'viewport')) + } else { + this.setViewportCoordinateEnable(false); + } + + this.setSymbols(GetValue(config, 'symbols')); + + this.bobs = {}; + this.removedGOs = []; + this._timeScale = 1; + } + + destroy(fromScene) { + this.clear(!fromScene); + this.createGameObjectCallback = undefined; + this.viewport = undefined; + this.scene = undefined; + } + + set timeScale(timeScale) { + if (this._timeScale === timeScale) { + return; + } + + this._timeScale = timeScale; + + var bobs = this.bobs; + for (var name in bobs) { + bobs[name].setTimeScale(timeScale); + } + } + + get timeScale() { + return this._timeScale; + } + + setTimeScale(timeScale) { + this.timeScale = timeScale; + return this; + } + + setCreateGameObjectCallback(callback, scope) { + this.createGameObjectCallback = callback; + this.createGameObjectScope = scope; + return this; + } + + setViewportCoordinateEnable(enable) { + if (enable === undefined) { + enable = true; + } + + this.viewportCoordinateEnable = enable; + return this; + } + + setViewport(viewport) { + if (viewport === undefined) { + viewport = GetViewport(this.scene, this.scene.cameras.main); + } + + this.viewport = viewport; + return this; + } + + setSymbols(symbols) { + this.symbols = symbols; + return this; + } + + get isEmpty() { + return IsEmpty(this.bobs) && (this.removedGOs.length === 0); + } + +} + +Object.assign( + GOManager.prototype, + EventEmitterMethods, + Methods +); + +export default GOManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/BobBase.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/BobBase.d.ts new file mode 100644 index 000000000..7dcc9df39 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/BobBase.d.ts @@ -0,0 +1,40 @@ +import GOManager from '../GOManager'; + +export default BobBase; + +declare namespace BobBase { + +} + +declare class BobBase { + constructor( + GOManager: GOManager, + gameObject: Phaser.GameObjects.GameObject, + name: string + ); + + destroy(): void; + + hasProperty(property: string): boolean; + + setProperty(property: string, value: any): this; + + easeProperty( + property: string, + value: number, + duration?: number, + ease?: string, + repeat?: null, + isYoyo?: boolean, + onComplete?: Function + ): this; + + setTimeScale(timeScale: number): this; + + hasMethod(methodName: string): boolean; + + call( + methodName: string, + ...parameters: any[] + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/BobBase.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/BobBase.js new file mode 100644 index 000000000..f198d9bf1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/BobBase.js @@ -0,0 +1,73 @@ +import PropertyMethods from './PropertyMethods.js'; +import CallMethods from './CallMethods.js'; +import DataMethods from './DataMethods.js'; + +class BobBase { + constructor(GOManager, gameObject, name) { + this.GOManager = GOManager; + this.tweens = {}; + this.setGO(gameObject, name); + } + + get scene() { + return this.GOManager.scene; + } + + get timeScale() { + return this.GOManager.timeScale; + } + + destroy() { + this.freeGO(); + this.GOManager = undefined; + } + + freeTweens() { + var tweenTasks = this.tweens, + tweenTask; + for (var propName in tweenTasks) { + tweenTask = tweenTasks[propName]; + if (tweenTask) { + tweenTask.remove(); + } + tweenTasks[propName] = null; + } + return this; + } + + freeGO() { + this.freeTweens(); + this.gameObject.destroy(); + this.gameObject = undefined; + return this; + } + + setGO(gameObject, name) { + gameObject.setName(name); + this.gameObject = gameObject; + this.name = name; + this.freeTweens(); + return this; + } + + setTimeScale(timeScale) { + var tweenTasks = this.tweens; + for (var key in tweenTasks) { + var tweenTask = tweenTasks[key]; + if (tweenTask) { + tweenTask.timeScale = timeScale; + } + } + + return this; + } + +} + +Object.assign( + BobBase.prototype, + PropertyMethods, + CallMethods, + DataMethods +) +export default BobBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/CallMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/CallMethods.js new file mode 100644 index 000000000..75cdfb0af --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/CallMethods.js @@ -0,0 +1,16 @@ +export default { + hasMethod(methodName) { + return typeof (this.gameObject[methodName]) === 'function'; + }, + + call(methodName, ...parameters) { + if (!this.hasMethod(methodName)) { + return this; + } + + var gameObject = this.gameObject; + gameObject[methodName].apply(gameObject, parameters); + + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/DataMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/DataMethods.js new file mode 100644 index 000000000..9a2f1dff5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/DataMethods.js @@ -0,0 +1,15 @@ +export default { + hasData(dataKey) { + var gameObject = this.gameObject; + return (gameObject.data) ? gameObject.data.has(dataKey) : false; + }, + + getData(dataKey) { + return this.gameObject.getData(dataKey); + }, + + setData(dataKey, value) { + this.gameObject.setData(dataKey, value); + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/PropertyMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/PropertyMethods.js new file mode 100644 index 000000000..3c051d127 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/PropertyMethods.js @@ -0,0 +1,52 @@ +export default { + hasProperty(property) { + var gameObject = this.gameObject; + if (gameObject.hasOwnProperty(property)) { + return true; + } else { + var value = gameObject[property]; + return (value !== undefined); + } + }, + + getProperty(property) { + return this.gameObject[property]; + }, + + setProperty(property, value) { + this.gameObject[property] = value; + return this; + }, + + easeProperty(property, value, duration, ease, repeat, isYoyo, onComplete) { + var tweenTasks = this.tweens; + var tweenTask = tweenTasks[property]; + if (tweenTask) { + tweenTask.remove(); + } + + var gameObject = this.gameObject; + var config = { + targets: gameObject, + duration: duration, + ease: ease, + repeat: repeat, + yoyo: isYoyo, + onComplete: function () { + tweenTasks[property].remove(); + tweenTasks[property] = null; + if (onComplete) { + onComplete(gameObject, property); + } + }, + onCompleteScope: this + } + config[property] = value; + + tweenTask = this.scene.tweens.add(config); + tweenTask.timeScale = this.timeScale; + tweenTasks[property] = tweenTask; + return this; + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/AddMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/AddMethods.js new file mode 100644 index 000000000..c8193abfa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/AddMethods.js @@ -0,0 +1,76 @@ +import AddTintRGBProperties from '../../../../behaviors/tintrgb/AddTintRGBProperties.js'; +import AddViewportCoordinateProperties from '../../../../behaviors/viewportcoordinate/AddViewportCoordinateProperties.js'; + +const RemoveItem = Phaser.Utils.Array.Remove; + +export default { + has(name) { + return this.bobs.hasOwnProperty(name); + }, + + exists(name) { + return this.bobs.hasOwnProperty(name); + }, + + get(name) { + return this.bobs[name]; + }, + + getGO(name) { + var bob = this.get(name); + return (bob) ? bob.gameObject : null; + }, + + addGO(name, gameObject) { + this.remove(name, true); + + if (this.hasTintFadeEffect(gameObject)) { + AddTintRGBProperties(gameObject); + } + + if (this.viewportCoordinateEnable) { + AddViewportCoordinateProperties(gameObject, this.viewport); + } + + gameObject.once('destroy', function () { + RemoveItem(this.removedGOs, gameObject); + if (this.isEmpty) { + this.emit('empty'); + } + }, this); + + var bob = new this.BobClass(this, gameObject, name); + this.bobs[name] = bob; + + return this; + }, + + add(name, ...args) { + var callback = this.createGameObjectCallback; + var scope = this.createGameObjectScope; + var gameObject = callback.call(scope, this.scene, ...args); + this.addGO(name, gameObject); + + var bob = this.get(name); + this.fadeBob(bob, 0, 1); + + return this; + }, + + forEachGO(callback, scope) { + for (var name in this.bobs) { + var gameObject = this.bobs[name].gameObject; + var stopLoop; + if (scope) { + stopLoop = callback.call(scope, gameObject, name, this); + } else { + stopLoop = callback(gameObject, name, this); + } + + if (stopLoop) { + break; + } + } + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/CallMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/CallMethods.js new file mode 100644 index 000000000..7dd0850bc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/CallMethods.js @@ -0,0 +1,17 @@ +export default { + hasMethod(name, methodName) { + if (!this.has(name)) { + return false; + } + return this.get(name).hasMethod(methodName); + }, + + + call(name, methodName, ...parameters) { + if (!this.has(name)) { + return this; + } + this.get(name).call(methodName, ...parameters); + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/DataMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/DataMethods.js new file mode 100644 index 000000000..a3b9e2962 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/DataMethods.js @@ -0,0 +1,23 @@ +export default { + hasData(name, dataKey) { + if (!this.has(name)) { + return false; + } + return this.get(name).hasData(dataKey); + }, + + getData(name, dataKey) { + if (!this.has(name)) { + return undefined; + } + return this.get(name).getData(dataKey); + }, + + setData(name, dataKey, value) { + if (!this.has(name)) { + return this; + } + this.get(name).setData(dataKey, value); + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/DrawGameObjectsBounds.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/DrawGameObjectsBounds.js new file mode 100644 index 000000000..5268c7fc3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/DrawGameObjectsBounds.js @@ -0,0 +1,14 @@ +import DrawBounds from '../../../bounds/DrawBounds.js'; + +var DrawGameObjectsBounds = function (graphics, config) { + this.forEachGO(function (gameObject) { + if (gameObject.drawBounds) { + gameObject.drawBounds(graphics, config); + } else { + DrawBounds(gameObject, graphics, config); + } + }); + return this; +} + +export default DrawGameObjectsBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/FadeMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/FadeMethods.js new file mode 100644 index 000000000..2a338f8f4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/FadeMethods.js @@ -0,0 +1,69 @@ +const FadeMode = { + tint: 0, + alpha: 1 +} + +export default { + setGOFadeMode(fadeMode) { + if (typeof (fadeMode) === 'string') { + fadeMode = FadeMode[fadeMode]; + } + + this.fadeMode = fadeMode; + return this; + }, + + setGOFadeTime(time) { + this.fadeTime = time; + return this; + }, + + hasTintFadeEffect(gameObject) { + return ((this.fadeMode === undefined) || (this.fadeMode === 0)) && + (this.fadeTime > 0) && (gameObject.setTint !== undefined); + }, + + hasAlphaFadeEffect(gameObject) { + return ((this.fadeMode === undefined) || (this.fadeMode === 1)) && + (this.fadeTime > 0) && (gameObject.setAlpha !== undefined); + }, + + fadeBob(bob, fromValue, toValue, onComplete) { + var gameObject = bob.gameObject; + if (this.hasTintFadeEffect(gameObject)) { + if (fromValue !== undefined) { + bob.setProperty('tintGray', 255 * fromValue) + } + bob.easeProperty( + 'tintGray', // property + Math.floor(255 * toValue), // to value + this.fadeTime, // duration + 'Linear', // ease + 0, // repeat + false, // yoyo + onComplete // onComplete + ) + + } else if (this.hasAlphaFadeEffect(gameObject)) { + if (fromValue !== undefined) { + bob.setProperty('alpha', fromValue); + } + bob.easeProperty( + 'alpha', // property + toValue, // to value + this.fadeTime, // duration + 'Linear', // ease + 0, // repeat + false, // yoyo + onComplete // onComplete + ) + } else { + if (onComplete) { + onComplete(gameObject); + } + } + + return this; + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/Methods.js new file mode 100644 index 000000000..32d1493b2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/Methods.js @@ -0,0 +1,23 @@ +import FadeMethods from './FadeMethods.js'; +import AddMethods from './AddMethods.js'; +import RemoveMethods from './RemoveMethods.js'; +import PropertyMethods from './PropertyMethods.js'; +import CallMethods from './CallMethods.js'; +import DataMethods from './DataMethods.js'; +import DrawGameObjectsBounds from './DrawGameObjectsBounds.js'; + +var Methods = { + drawGameObjectsBounds: DrawGameObjectsBounds, +}; + +Object.assign( + Methods, + FadeMethods, + AddMethods, + RemoveMethods, + PropertyMethods, + CallMethods, + DataMethods, +) + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/PropertyMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/PropertyMethods.js new file mode 100644 index 000000000..d6756b417 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/PropertyMethods.js @@ -0,0 +1,92 @@ +export default { + hasProperty(name, property) { + if (!this.has(name)) { + return false; + } + return this.get(name).hasProperty(property); + }, + + getProperty(name, property) { + if (!this.has(name)) { + return undefined; + } + return this.get(name).getProperty(property); + }, + + isNumberProperty(name, property) { + var value = this.getProperty(name, property); + return typeof (value) === 'number'; + }, + + setProperty(name, property, value) { + if (!this.has(name)) { + return this; + } + + if (this.symbols && + (typeof (value) === 'string') && + this.isNumberProperty(name, property) + ) { + if (value in this.symbols) { + value = this.symbols[value]; + } else { + console.warn(`Can't find symbol ${value}`) + } + } + + this.get(name).setProperty(property, value); + return this; + }, + + easeProperty(name, property, value, duration, ease, repeat, isYoyo, onComplete) { + if (!this.has(name)) { + return this; + } + + if (duration === undefined) { + duration = 1000; + } + if (ease === undefined) { + ease = 'Linear'; + } + if (repeat === undefined) { + repeat = 0; + } + if (isYoyo === undefined) { + isYoyo = false; + } + + if (this.symbols && + (typeof (value) === 'string') && + this.isNumberProperty(name, property) + ) { + if (value in this.symbols) { + value = this.symbols[value]; + } else { + console.warn(`Can't find symbol ${value}`) + } + } + + this.get(name).easeProperty(property, value, duration, ease, repeat, isYoyo, onComplete); + return this; + }, + + hasTweenTask(name, property) { + if (!this.has(name)) { + return false; + } + + var tweenTasks = this.get(name).tweens; + return tweenTasks.hasOwnProperty(property); + }, + + getTweenTask(name, property) { + if (!this.has(name)) { + return null; + } + + var tweenTasks = this.get(name).tweens; + var tweenTask = tweenTasks[property]; + return (tweenTask) ? tweenTask : null; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/RemoveMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/RemoveMethods.js new file mode 100644 index 000000000..a2d67695c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/RemoveMethods.js @@ -0,0 +1,50 @@ +export default { + remove(name, ignoreFade) { + if (!this.has(name)) { + return this; + } + + var bob = this.get(name); + delete this.bobs[name]; + + this.removedGOs.push(bob.gameObject); + + if (!ignoreFade) { + this.fadeBob( + bob, // bob + undefined, // fromValue + 0, // toValue + function () { // onComplete + bob.destroy(); + } + ) + } else { + bob.destroy(); + } + + return this; + }, + + removeAll() { + var bobs = this.bobs; + for (var name in bobs) { + this.remove(name); + } + return this; + }, + + clear(destroyChild) { + if (destroyChild === undefined) { + destroyChild = true; + } + var bobs = this.bobs; + for (var name in bobs) { + if (destroyChild) { + bobs[name].destroy(); + } + delete bobs[name]; + } + this.removedGOs.length = 0; + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Area.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Area.js new file mode 100644 index 000000000..7815e0506 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Area.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculates the area of the circle. + * + * @function Phaser.Geom.Circle.Area + * @since 3.0.0 + * + * @param {Phaser.Geom.Circle} circle - The Circle to get the area of. + * + * @return {number} The area of the Circle. + */ +var Area = function (circle) +{ + return (circle.radius > 0) ? Math.PI * circle.radius * circle.radius : 0; +}; + +export default Area; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Circle.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Circle.d.ts new file mode 100644 index 000000000..cc8bb6992 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Circle.d.ts @@ -0,0 +1,238 @@ +import Point from '../point/Point'; +import Rectangle from '../rectangle/Rectangle'; + +/** + * A Circle object. + * + * This is a geometry object, containing numerical values and related methods to inspect and modify them. + * It is not a Game Object, in that you cannot add it to the display list, and it has no texture. + * To render a Circle you should look at the capabilities of the Graphics class. + */ +declare class Circle { + /** + * + * @param x The x position of the center of the circle. Default 0. + * @param y The y position of the center of the circle. Default 0. + * @param radius The radius of the circle. Default 0. + */ + constructor(x?: number, y?: number, radius?: number); + + /** + * Calculates the area of the circle. + * @param circle The Circle to get the area of. + */ + static Area(circle: Circle): number; + + /** + * The geometry constant type of this object: `GEOM_CONST.CIRCLE`. + * Used for fast type comparisons. + */ + readonly type: number; + + /** + * The x position of the center of the circle. + */ + x: number; + + /** + * The y position of the center of the circle. + */ + y: number; + + /** + * Check to see if the Circle contains the given x / y coordinates. + * @param x The x coordinate to check within the circle. + * @param y The y coordinate to check within the circle. + */ + contains(x: number, y: number): boolean; + + /** + * Returns a Point object containing the coordinates of a point on the circumference of the Circle + * based on the given angle normalized to the range 0 to 1. I.e. a value of 0.5 will give the point + * at 180 degrees around the circle. + * @param position A value between 0 and 1, where 0 equals 0 degrees, 0.5 equals 180 degrees and 1 equals 360 around the circle. + * @param out An object to store the return values in. If not given a Point object will be created. + */ + getPoint(position: number, out?: O): O; + + /** + * Returns an array of Point objects containing the coordinates of the points around the circumference of the Circle, + * based on the given quantity or stepRate values. + * @param quantity The amount of points to return. If a falsey value the quantity will be derived from the `stepRate` instead. + * @param stepRate Sets the quantity by getting the circumference of the circle and dividing it by the stepRate. + * @param output An array to insert the points in to. If not provided a new array will be created. + */ + getPoints(quantity: number, stepRate?: number, output?: O): O; + + /** + * Returns a uniformly distributed random point from anywhere within the Circle. + * @param point A Point or point-like object to set the random `x` and `y` values in. + */ + getRandomPoint(point?: O): O; + + /** + * Sets the x, y and radius of this circle. + * @param x The x position of the center of the circle. Default 0. + * @param y The y position of the center of the circle. Default 0. + * @param radius The radius of the circle. Default 0. + */ + setTo(x?: number, y?: number, radius?: number): this; + + /** + * Sets this Circle to be empty with a radius of zero. + * Does not change its position. + */ + setEmpty(): this; + + /** + * Sets the position of this Circle. + * @param x The x position of the center of the circle. Default 0. + * @param y The y position of the center of the circle. Default 0. + */ + setPosition(x?: number, y?: number): this; + + /** + * Checks to see if the Circle is empty: has a radius of zero. + */ + isEmpty(): boolean; + + /** + * The radius of the Circle. + */ + radius: number; + + /** + * The diameter of the Circle. + */ + diameter: number; + + /** + * The left position of the Circle. + */ + left: number; + + /** + * The right position of the Circle. + */ + right: number; + + /** + * The top position of the Circle. + */ + top: number; + + /** + * The bottom position of the Circle. + */ + bottom: number; + + /** + * Returns the circumference of the given Circle. + * @param circle The Circle to get the circumference of. + */ + static Circumference(circle: Circle): number; + + /** + * Returns a Point object containing the coordinates of a point on the circumference of the Circle based on the given angle. + * @param circle The Circle to get the circumference point on. + * @param angle The angle from the center of the Circle to the circumference to return the point from. Given in radians. + * @param out A Point, or point-like object, to store the results in. If not given a Point will be created. + */ + static CircumferencePoint(circle: Circle, angle: number, out?: O): O; + + /** + * Creates a new Circle instance based on the values contained in the given source. + * @param source The Circle to be cloned. Can be an instance of a Circle or a circle-like object, with x, y and radius properties. + */ + static Clone(source: Circle | object): Circle; + + /** + * Check to see if the Circle contains the given x / y coordinates. + * @param circle The Circle to check. + * @param x The x coordinate to check within the circle. + * @param y The y coordinate to check within the circle. + */ + static Contains(circle: Circle, x: number, y: number): boolean; + + /** + * Check to see if the Circle contains the given Point object. + * @param circle The Circle to check. + * @param point The Point object to check if it's within the Circle or not. + */ + static ContainsPoint(circle: Circle, point: Point | object): boolean; + + /** + * Check to see if the Circle contains all four points of the given Rectangle object. + * @param circle The Circle to check. + * @param rect The Rectangle object to check if it's within the Circle or not. + */ + static ContainsRect(circle: Circle, rect: Rectangle | object): boolean; + + /** + * Copies the `x`, `y` and `radius` properties from the `source` Circle + * into the given `dest` Circle, then returns the `dest` Circle. + * @param source The source Circle to copy the values from. + * @param dest The destination Circle to copy the values to. + */ + static CopyFrom(source: Circle, dest: O): O; + + /** + * Compares the `x`, `y` and `radius` properties of the two given Circles. + * Returns `true` if they all match, otherwise returns `false`. + * @param circle The first Circle to compare. + * @param toCompare The second Circle to compare. + */ + static Equals(circle: Circle, toCompare: Circle): boolean; + + /** + * Returns the bounds of the Circle object. + * @param circle The Circle to get the bounds from. + * @param out A Rectangle, or rectangle-like object, to store the circle bounds in. If not given a new Rectangle will be created. + */ + static GetBounds(circle: Circle, out?: O): O; + + /** + * Returns a Point object containing the coordinates of a point on the circumference of the Circle + * based on the given angle normalized to the range 0 to 1. I.e. a value of 0.5 will give the point + * at 180 degrees around the circle. + * @param circle The Circle to get the circumference point on. + * @param position A value between 0 and 1, where 0 equals 0 degrees, 0.5 equals 180 degrees and 1 equals 360 around the circle. + * @param out An object to store the return values in. If not given a Point object will be created. + */ + static GetPoint(circle: Circle, position: number, out?: O): O; + + /** + * Returns an array of Point objects containing the coordinates of the points around the circumference of the Circle, + * based on the given quantity or stepRate values. + * @param circle The Circle to get the points from. + * @param quantity The amount of points to return. If a falsey value the quantity will be derived from the `stepRate` instead. + * @param stepRate Sets the quantity by getting the circumference of the circle and dividing it by the stepRate. + * @param output An array to insert the points in to. If not provided a new array will be created. + */ + static GetPoints(circle: Circle, quantity: number, stepRate?: number, output?: any[]): Point[]; + + /** + * Offsets the Circle by the values given. + * @param circle The Circle to be offset (translated.) + * @param x The amount to horizontally offset the Circle by. + * @param y The amount to vertically offset the Circle by. + */ + static Offset(circle: O, x: number, y: number): O; + + /** + * Offsets the Circle by the values given in the `x` and `y` properties of the Point object. + * @param circle The Circle to be offset (translated.) + * @param point The Point object containing the values to offset the Circle by. + */ + static OffsetPoint(circle: O, point: Point | object): O; + + /** + * Returns a uniformly distributed random point from anywhere within the given Circle. + * @param circle The Circle to get a random point from. + * @param out A Point or point-like object to set the random `x` and `y` values in. + */ + static Random(circle: Circle, out?: O): O; + +} + +export default Circle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Circle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Circle.js new file mode 100644 index 000000000..b105b9614 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Circle.js @@ -0,0 +1,336 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Class from '../../object/Class.js'; +import Contains from './Contains.js'; +import GetPoint from './GetPoint.js'; +import GetPoints from './GetPoints.js'; +import Random from './Random.js'; + +/** + * @classdesc + * A Circle object. + * + * This is a geometry object, containing numerical values and related methods to inspect and modify them. + * It is not a Game Object, in that you cannot add it to the display list, and it has no texture. + * To render a Circle you should look at the capabilities of the Graphics class. + * + * @class Circle + * @memberof Phaser.Geom + * @constructor + * @since 3.0.0 + * + * @param {number} [x=0] - The x position of the center of the circle. + * @param {number} [y=0] - The y position of the center of the circle. + * @param {number} [radius=0] - The radius of the circle. + */ +var Circle = new Class({ + + initialize: + + function Circle(x, y, radius) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (radius === undefined) { radius = 0; } + + /** + * The x position of the center of the circle. + * + * @name Phaser.Geom.Circle#x + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.x = x; + + /** + * The y position of the center of the circle. + * + * @name Phaser.Geom.Circle#y + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.y = y; + + /** + * The internal radius of the circle. + * + * @name Phaser.Geom.Circle#_radius + * @type {number} + * @private + * @since 3.0.0 + */ + this._radius = radius; + + /** + * The internal diameter of the circle. + * + * @name Phaser.Geom.Circle#_diameter + * @type {number} + * @private + * @since 3.0.0 + */ + this._diameter = radius * 2; + }, + + /** + * Check to see if the Circle contains the given x / y coordinates. + * + * @method Phaser.Geom.Circle#contains + * @since 3.0.0 + * + * @param {number} x - The x coordinate to check within the circle. + * @param {number} y - The y coordinate to check within the circle. + * + * @return {boolean} True if the coordinates are within the circle, otherwise false. + */ + contains: function (x, y) { + return Contains(this, x, y); + }, + + /** + * Returns a Point object containing the coordinates of a point on the circumference of the Circle + * based on the given angle normalized to the range 0 to 1. I.e. a value of 0.5 will give the point + * at 180 degrees around the circle. + * + * @method Phaser.Geom.Circle#getPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {number} position - A value between 0 and 1, where 0 equals 0 degrees, 0.5 equals 180 degrees and 1 equals 360 around the circle. + * @param {(Phaser.Geom.Point|object)} [out] - An object to store the return values in. If not given a Point object will be created. + * + * @return {(Phaser.Geom.Point|object)} A Point, or point-like object, containing the coordinates of the point around the circle. + */ + getPoint: function (position, point) { + return GetPoint(this, position, point); + }, + + /** + * Returns an array of Point objects containing the coordinates of the points around the circumference of the Circle, + * based on the given quantity or stepRate values. + * + * @method Phaser.Geom.Circle#getPoints + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point[]} O - [output,$return] + * + * @param {integer} quantity - The amount of points to return. If a falsey value the quantity will be derived from the `stepRate` instead. + * @param {number} [stepRate] - Sets the quantity by getting the circumference of the circle and dividing it by the stepRate. + * @param {(array|Phaser.Geom.Point[])} [output] - An array to insert the points in to. If not provided a new array will be created. + * + * @return {(array|Phaser.Geom.Point[])} An array of Point objects pertaining to the points around the circumference of the circle. + */ + getPoints: function (quantity, stepRate, output) { + return GetPoints(this, quantity, stepRate, output); + }, + + /** + * Returns a uniformly distributed random point from anywhere within the Circle. + * + * @method Phaser.Geom.Circle#getRandomPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [point,$return] + * + * @param {(Phaser.Geom.Point|object)} [point] - A Point or point-like object to set the random `x` and `y` values in. + * + * @return {(Phaser.Geom.Point|object)} A Point object with the random values set in the `x` and `y` properties. + */ + getRandomPoint: function (point) { + return Random(this, point); + }, + + /** + * Sets the x, y and radius of this circle. + * + * @method Phaser.Geom.Circle#setTo + * @since 3.0.0 + * + * @param {number} [x=0] - The x position of the center of the circle. + * @param {number} [y=0] - The y position of the center of the circle. + * @param {number} [radius=0] - The radius of the circle. + * + * @return {Phaser.Geom.Circle} This Circle object. + */ + setTo: function (x, y, radius) { + this.x = x; + this.y = y; + this._radius = radius; + this._diameter = radius * 2; + + return this; + }, + + /** + * Sets this Circle to be empty with a radius of zero. + * Does not change its position. + * + * @method Phaser.Geom.Circle#setEmpty + * @since 3.0.0 + * + * @return {Phaser.Geom.Circle} This Circle object. + */ + setEmpty: function () { + this._radius = 0; + this._diameter = 0; + + return this; + }, + + /** + * Sets the position of this Circle. + * + * @method Phaser.Geom.Circle#setPosition + * @since 3.0.0 + * + * @param {number} [x=0] - The x position of the center of the circle. + * @param {number} [y=0] - The y position of the center of the circle. + * + * @return {Phaser.Geom.Circle} This Circle object. + */ + setPosition: function (x, y) { + if (y === undefined) { y = x; } + + this.x = x; + this.y = y; + + return this; + }, + + /** + * Checks to see if the Circle is empty: has a radius of zero. + * + * @method Phaser.Geom.Circle#isEmpty + * @since 3.0.0 + * + * @return {boolean} True if the Circle is empty, otherwise false. + */ + isEmpty: function () { + return (this._radius <= 0); + }, + + /** + * The radius of the Circle. + * + * @name Phaser.Geom.Circle#radius + * @type {number} + * @since 3.0.0 + */ + radius: { + + get: function () { + return this._radius; + }, + + set: function (value) { + this._radius = value; + this._diameter = value * 2; + } + + }, + + /** + * The diameter of the Circle. + * + * @name Phaser.Geom.Circle#diameter + * @type {number} + * @since 3.0.0 + */ + diameter: { + + get: function () { + return this._diameter; + }, + + set: function (value) { + this._diameter = value; + this._radius = value * 0.5; + } + + }, + + /** + * The left position of the Circle. + * + * @name Phaser.Geom.Circle#left + * @type {number} + * @since 3.0.0 + */ + left: { + + get: function () { + return this.x - this._radius; + }, + + set: function (value) { + this.x = value + this._radius; + } + + }, + + /** + * The right position of the Circle. + * + * @name Phaser.Geom.Circle#right + * @type {number} + * @since 3.0.0 + */ + right: { + + get: function () { + return this.x + this._radius; + }, + + set: function (value) { + this.x = value - this._radius; + } + + }, + + /** + * The top position of the Circle. + * + * @name Phaser.Geom.Circle#top + * @type {number} + * @since 3.0.0 + */ + top: { + + get: function () { + return this.y - this._radius; + }, + + set: function (value) { + this.y = value + this._radius; + } + + }, + + /** + * The bottom position of the Circle. + * + * @name Phaser.Geom.Circle#bottom + * @type {number} + * @since 3.0.0 + */ + bottom: { + + get: function () { + return this.y + this._radius; + }, + + set: function (value) { + this.y = value - this._radius; + } + + } + +}); + +export default Circle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Circumference.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Circumference.js new file mode 100644 index 000000000..cf4937a21 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Circumference.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Returns the circumference of the given Circle. + * + * @function Phaser.Geom.Circle.Circumference + * @since 3.0.0 + * + * @param {Phaser.Geom.Circle} circle - The Circle to get the circumference of. + * + * @return {number} The circumference of the Circle. + */ +var Circumference = function (circle) +{ + return 2 * (Math.PI * circle.radius); +}; + +export default Circumference; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/CircumferencePoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/CircumferencePoint.js new file mode 100644 index 000000000..43419a734 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/CircumferencePoint.js @@ -0,0 +1,32 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +/** + * Returns a Point object containing the coordinates of a point on the circumference of the Circle based on the given angle. + * + * @function Phaser.Geom.Circle.CircumferencePoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Circle} circle - The Circle to get the circumference point on. + * @param {number} angle - The angle from the center of the Circle to the circumference to return the point from. Given in radians. + * @param {(Phaser.Geom.Point|object)} [out] - A Point, or point-like object, to store the results in. If not given a Point will be created. + * + * @return {(Phaser.Geom.Point|object)} A Point object where the `x` and `y` properties are the point on the circumference. + */ +var CircumferencePoint = function (circle, angle, out) { + if (out === undefined) { out = new Point(); } + + out.x = circle.x + (circle.radius * Math.cos(angle)); + out.y = circle.y + (circle.radius * Math.sin(angle)); + + return out; +}; + +export default CircumferencePoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Clone.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Clone.js new file mode 100644 index 000000000..8c19b5c56 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Clone.js @@ -0,0 +1,23 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Circle from './Circle.js'; + +/** + * Creates a new Circle instance based on the values contained in the given source. + * + * @function Phaser.Geom.Circle.Clone + * @since 3.0.0 + * + * @param {(Phaser.Geom.Circle|object)} source - The Circle to be cloned. Can be an instance of a Circle or a circle-like object, with x, y and radius properties. + * + * @return {Phaser.Geom.Circle} A clone of the source Circle. + */ +var Clone = function (source) { + return new Circle(source.x, source.y, source.radius); +}; + +export default Clone; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Contains.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Contains.js new file mode 100644 index 000000000..90be160ea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Contains.js @@ -0,0 +1,35 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Check to see if the Circle contains the given x / y coordinates. + * + * @function Phaser.Geom.Circle.Contains + * @since 3.0.0 + * + * @param {Phaser.Geom.Circle} circle - The Circle to check. + * @param {number} x - The x coordinate to check within the circle. + * @param {number} y - The y coordinate to check within the circle. + * + * @return {boolean} True if the coordinates are within the circle, otherwise false. + */ +var Contains = function (circle, x, y) +{ + // Check if x/y are within the bounds first + if (circle.radius > 0 && x >= circle.left && x <= circle.right && y >= circle.top && y <= circle.bottom) + { + var dx = (circle.x - x) * (circle.x - x); + var dy = (circle.y - y) * (circle.y - y); + + return (dx + dy) <= (circle.radius * circle.radius); + } + else + { + return false; + } +}; + +export default Contains; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/ContainsPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/ContainsPoint.js new file mode 100644 index 000000000..b3e28402a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/ContainsPoint.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Contains from './Contains.js'; + +/** + * Check to see if the Circle contains the given Point object. + * + * @function Phaser.Geom.Circle.ContainsPoint + * @since 3.0.0 + * + * @param {Phaser.Geom.Circle} circle - The Circle to check. + * @param {(Phaser.Geom.Point|object)} point - The Point object to check if it's within the Circle or not. + * + * @return {boolean} True if the Point coordinates are within the circle, otherwise false. + */ +var ContainsPoint = function (circle, point) { + return Contains(circle, point.x, point.y); +}; + +export default ContainsPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/ContainsRect.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/ContainsRect.js new file mode 100644 index 000000000..5abce5cd3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/ContainsRect.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Contains from './Contains.js'; + +/** + * Check to see if the Circle contains all four points of the given Rectangle object. + * + * @function Phaser.Geom.Circle.ContainsRect + * @since 3.0.0 + * + * @param {Phaser.Geom.Circle} circle - The Circle to check. + * @param {(Phaser.Geom.Rectangle|object)} rect - The Rectangle object to check if it's within the Circle or not. + * + * @return {boolean} True if all of the Rectangle coordinates are within the circle, otherwise false. + */ +var ContainsRect = function (circle, rect) { + return ( + Contains(circle, rect.x, rect.y) && + Contains(circle, rect.right, rect.y) && + Contains(circle, rect.x, rect.bottom) && + Contains(circle, rect.right, rect.bottom) + ); +}; + +export default ContainsRect; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/CopyFrom.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/CopyFrom.js new file mode 100644 index 000000000..e3828797c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/CopyFrom.js @@ -0,0 +1,26 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Copies the `x`, `y` and `radius` properties from the `source` Circle + * into the given `dest` Circle, then returns the `dest` Circle. + * + * @function Phaser.Geom.Circle.CopyFrom + * @since 3.0.0 + * + * @generic {Phaser.Geom.Circle} O - [dest,$return] + * + * @param {Phaser.Geom.Circle} source - The source Circle to copy the values from. + * @param {Phaser.Geom.Circle} dest - The destination Circle to copy the values to. + * + * @return {Phaser.Geom.Circle} The destination Circle. + */ +var CopyFrom = function (source, dest) +{ + return dest.setTo(source.x, source.y, source.radius); +}; + +export default CopyFrom; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Equals.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Equals.js new file mode 100644 index 000000000..c9608c69e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Equals.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Compares the `x`, `y` and `radius` properties of the two given Circles. + * Returns `true` if they all match, otherwise returns `false`. + * + * @function Phaser.Geom.Circle.Equals + * @since 3.0.0 + * + * @param {Phaser.Geom.Circle} circle - The first Circle to compare. + * @param {Phaser.Geom.Circle} toCompare - The second Circle to compare. + * + * @return {boolean} `true` if the two Circles equal each other, otherwise `false`. + */ +var Equals = function (circle, toCompare) +{ + return ( + circle.x === toCompare.x && + circle.y === toCompare.y && + circle.radius === toCompare.radius + ); +}; + +export default Equals; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/GetBounds.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/GetBounds.js new file mode 100644 index 000000000..f61e56ccd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/GetBounds.js @@ -0,0 +1,33 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Rectangle from '../rectangle/Rectangle.js'; + +/** + * Returns the bounds of the Circle object. + * + * @function Phaser.Geom.Circle.GetBounds + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [out,$return] + * + * @param {Phaser.Geom.Circle} circle - The Circle to get the bounds from. + * @param {(Phaser.Geom.Rectangle|object)} [out] - A Rectangle, or rectangle-like object, to store the circle bounds in. If not given a new Rectangle will be created. + * + * @return {(Phaser.Geom.Rectangle|object)} The Rectangle object containing the Circles bounds. + */ +var GetBounds = function (circle, out) { + if (out === undefined) { out = new Rectangle(); } + + out.x = circle.left; + out.y = circle.top; + out.width = circle.diameter; + out.height = circle.diameter; + + return out; +}; + +export default GetBounds; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/GetPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/GetPoint.js new file mode 100644 index 000000000..dabd252ce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/GetPoint.js @@ -0,0 +1,36 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import CircumferencePoint from './CircumferencePoint.js'; +import FromPercent from '../../math/FromPercent.js'; +import MATH_CONST from '../../math/const.js'; +import Point from '../point/Point.js'; + +/** + * Returns a Point object containing the coordinates of a point on the circumference of the Circle + * based on the given angle normalized to the range 0 to 1. I.e. a value of 0.5 will give the point + * at 180 degrees around the circle. + * + * @function Phaser.Geom.Circle.GetPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Circle} circle - The Circle to get the circumference point on. + * @param {number} position - A value between 0 and 1, where 0 equals 0 degrees, 0.5 equals 180 degrees and 1 equals 360 around the circle. + * @param {(Phaser.Geom.Point|object)} [out] - An object to store the return values in. If not given a Point object will be created. + * + * @return {(Phaser.Geom.Point|object)} A Point, or point-like object, containing the coordinates of the point around the circle. + */ +var GetPoint = function (circle, position, out) { + if (out === undefined) { out = new Point(); } + + var angle = FromPercent(position, 0, MATH_CONST.PI2); + + return CircumferencePoint(circle, angle, out); +}; + +export default GetPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/GetPoints.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/GetPoints.js new file mode 100644 index 000000000..f3b86398c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/GetPoints.js @@ -0,0 +1,43 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Circumference from './Circumference.js'; +import CircumferencePoint from './CircumferencePoint.js'; +import FromPercent from '../../math/FromPercent.js'; +import MATH_CONST from '../../math/const.js'; + +/** + * Returns an array of Point objects containing the coordinates of the points around the circumference of the Circle, + * based on the given quantity or stepRate values. + * + * @function Phaser.Geom.Circle.GetPoints + * @since 3.0.0 + * + * @param {Phaser.Geom.Circle} circle - The Circle to get the points from. + * @param {integer} quantity - The amount of points to return. If a falsey value the quantity will be derived from the `stepRate` instead. + * @param {number} [stepRate] - Sets the quantity by getting the circumference of the circle and dividing it by the stepRate. + * @param {array} [output] - An array to insert the points in to. If not provided a new array will be created. + * + * @return {Phaser.Geom.Point[]} An array of Point objects pertaining to the points around the circumference of the circle. + */ +var GetPoints = function (circle, quantity, stepRate, out) { + if (out === undefined) { out = []; } + + // If quantity is a falsey value (false, null, 0, undefined, etc) then we calculate it based on the stepRate instead. + if (!quantity) { + quantity = Circumference(circle) / stepRate; + } + + for (var i = 0; i < quantity; i++) { + var angle = FromPercent(i / quantity, 0, MATH_CONST.PI2); + + out.push(CircumferencePoint(circle, angle)); + } + + return out; +}; + +export default GetPoints; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Offset.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Offset.js new file mode 100644 index 000000000..2dc1d4566 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Offset.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Offsets the Circle by the values given. + * + * @function Phaser.Geom.Circle.Offset + * @since 3.0.0 + * + * @generic {Phaser.Geom.Circle} O - [circle,$return] + * + * @param {Phaser.Geom.Circle} circle - The Circle to be offset (translated.) + * @param {number} x - The amount to horizontally offset the Circle by. + * @param {number} y - The amount to vertically offset the Circle by. + * + * @return {Phaser.Geom.Circle} The Circle that was offset. + */ +var Offset = function (circle, x, y) +{ + circle.x += x; + circle.y += y; + + return circle; +}; + +export default Offset; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/OffsetPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/OffsetPoint.js new file mode 100644 index 000000000..34aee8f47 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/OffsetPoint.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Offsets the Circle by the values given in the `x` and `y` properties of the Point object. + * + * @function Phaser.Geom.Circle.OffsetPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Circle} O - [circle,$return] + * + * @param {Phaser.Geom.Circle} circle - The Circle to be offset (translated.) + * @param {(Phaser.Geom.Point|object)} point - The Point object containing the values to offset the Circle by. + * + * @return {Phaser.Geom.Circle} The Circle that was offset. + */ +var OffsetPoint = function (circle, point) +{ + circle.x += point.x; + circle.y += point.y; + + return circle; +}; + +export default OffsetPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Random.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Random.js new file mode 100644 index 000000000..2c84a867a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Random.js @@ -0,0 +1,37 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +/** + * Returns a uniformly distributed random point from anywhere within the given Circle. + * + * @function Phaser.Geom.Circle.Random + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Circle} circle - The Circle to get a random point from. + * @param {(Phaser.Geom.Point|object)} [out] - A Point or point-like object to set the random `x` and `y` values in. + * + * @return {(Phaser.Geom.Point|object)} A Point object with the random values set in the `x` and `y` properties. + */ +var Random = function (circle, out) { + if (out === undefined) { out = new Point(); } + + var t = 2 * Math.PI * Math.random(); + var u = Math.random() + Math.random(); + var r = (u > 1) ? 2 - u : u; + var x = r * Math.cos(t); + var y = r * Math.sin(t); + + out.x = circle.x + (x * circle.radius); + out.y = circle.y + (y * circle.radius); + + return out; +}; + +export default Random; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/index.js new file mode 100644 index 000000000..e605901f0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/index.js @@ -0,0 +1,40 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Circle from './Circle'; +import Area from './Area'; +import Circumference from './Circumference.js'; +import CircumferencePoint from './CircumferencePoint.js'; +import Clone from './Clone.js'; +import Contains from './Contains.js'; +import ContainsPoint from './ContainsPoint.js'; +import ContainsRect from './ContainsRect.js'; +import CopyFrom from './CopyFrom.js'; +import Equals from './Equals.js'; +import GetBounds from './GetBounds.js'; +import GetPoint from './GetPoint.js'; +import GetPoints from './GetPoints.js'; +import Offset from './Offset.js'; +import OffsetPoint from './OffsetPoint.js'; +import Random from './Random.js'; + +Circle.Area = Area; +Circle.Circumference = Circumference; +Circle.CircumferencePoint = CircumferencePoint; +Circle.Clone = Clone; +Circle.Contains = Contains; +Circle.ContainsPoint = ContainsPoint; +Circle.ContainsRect = ContainsRect; +Circle.CopyFrom = CopyFrom; +Circle.Equals = Equals; +Circle.GetBounds = GetBounds; +Circle.GetPoint = GetPoint; +Circle.GetPoints = GetPoints; +Circle.Offset = Offset; +Circle.OffsetPoint = OffsetPoint; +Circle.Random = Random; + +export default Circle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/const.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/const.js new file mode 100644 index 000000000..37ba3ef12 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/const.js @@ -0,0 +1,74 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +var GEOM_CONST = { + + /** + * A Circle Geometry object type. + * + * @name Phaser.Geom.CIRCLE + * @type {number} + * @since 3.19.0 + */ + CIRCLE: 0, + + /** + * An Ellipse Geometry object type. + * + * @name Phaser.Geom.ELLIPSE + * @type {number} + * @since 3.19.0 + */ + ELLIPSE: 1, + + /** + * A Line Geometry object type. + * + * @name Phaser.Geom.LINE + * @type {number} + * @since 3.19.0 + */ + LINE: 2, + + /** + * A Point Geometry object type. + * + * @name Phaser.Geom.POINT + * @type {number} + * @since 3.19.0 + */ + POINT: 3, + + /** + * A Polygon Geometry object type. + * + * @name Phaser.Geom.POLYGON + * @type {number} + * @since 3.19.0 + */ + POLYGON: 4, + + /** + * A Rectangle Geometry object type. + * + * @name Phaser.Geom.RECTANGLE + * @type {number} + * @since 3.19.0 + */ + RECTANGLE: 5, + + /** + * A Triangle Geometry object type. + * + * @name Phaser.Geom.TRIANGLE + * @type {number} + * @since 3.19.0 + */ + TRIANGLE: 6 + +}; + +module.exports = GEOM_CONST; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Area.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Area.js new file mode 100644 index 000000000..870747ce2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Area.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculates the area of the Ellipse. + * + * @function Phaser.Geom.Ellipse.Area + * @since 3.0.0 + * + * @param {Phaser.Geom.Ellipse} ellipse - The Ellipse to get the area of. + * + * @return {number} The area of the Ellipse. + */ +var Area = function (ellipse) +{ + if (ellipse.isEmpty()) + { + return 0; + } + + // units squared + return (ellipse.getMajorRadius() * ellipse.getMinorRadius() * Math.PI); +}; + +export default Area; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Circumference.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Circumference.js new file mode 100644 index 000000000..9d1d9a1f2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Circumference.js @@ -0,0 +1,26 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Returns the circumference of the given Ellipse. + * + * @function Phaser.Geom.Ellipse.Circumference + * @since 3.0.0 + * + * @param {Phaser.Geom.Ellipse} ellipse - The Ellipse to get the circumference of. + * + * @return {number} The circumference of th Ellipse. + */ +var Circumference = function (ellipse) +{ + var rx = ellipse.width / 2; + var ry = ellipse.height / 2; + var h = Math.pow((rx - ry), 2) / Math.pow((rx + ry), 2); + + return (Math.PI * (rx + ry)) * (1 + ((3 * h) / (10 + Math.sqrt(4 - (3 * h))))); +}; + +export default Circumference; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/CircumferencePoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/CircumferencePoint.js new file mode 100644 index 000000000..13dc281d6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/CircumferencePoint.js @@ -0,0 +1,35 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +/** + * Returns a Point object containing the coordinates of a point on the circumference of the Ellipse based on the given angle. + * + * @function Phaser.Geom.Ellipse.CircumferencePoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Ellipse} ellipse - The Ellipse to get the circumference point on. + * @param {number} angle - The angle from the center of the Ellipse to the circumference to return the point from. Given in radians. + * @param {(Phaser.Geom.Point|object)} [out] - A Point, or point-like object, to store the results in. If not given a Point will be created. + * + * @return {(Phaser.Geom.Point|object)} A Point object where the `x` and `y` properties are the point on the circumference. + */ +var CircumferencePoint = function (ellipse, angle, out) { + if (out === undefined) { out = new Point(); } + + var halfWidth = ellipse.width / 2; + var halfHeight = ellipse.height / 2; + + out.x = ellipse.x + halfWidth * Math.cos(angle); + out.y = ellipse.y + halfHeight * Math.sin(angle); + + return out; +}; + +export default CircumferencePoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Clone.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Clone.js new file mode 100644 index 000000000..1c98216a2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Clone.js @@ -0,0 +1,23 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Ellipse from './Ellipse.js'; + +/** + * Creates a new Ellipse instance based on the values contained in the given source. + * + * @function Phaser.Geom.Ellipse.Clone + * @since 3.0.0 + * + * @param {Phaser.Geom.Ellipse} source - The Ellipse to be cloned. Can be an instance of an Ellipse or a ellipse-like object, with x, y, width and height properties. + * + * @return {Phaser.Geom.Ellipse} A clone of the source Ellipse. + */ +var Clone = function (source) { + return new Ellipse(source.x, source.y, source.width, source.height); +}; + +export default Clone; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Contains.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Contains.js new file mode 100644 index 000000000..d48a5a4c5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Contains.js @@ -0,0 +1,36 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Check to see if the Ellipse contains the given x / y coordinates. + * + * @function Phaser.Geom.Ellipse.Contains + * @since 3.0.0 + * + * @param {Phaser.Geom.Ellipse} ellipse - The Ellipse to check. + * @param {number} x - The x coordinate to check within the ellipse. + * @param {number} y - The y coordinate to check within the ellipse. + * + * @return {boolean} True if the coordinates are within the ellipse, otherwise false. + */ +var Contains = function (ellipse, x, y) +{ + if (ellipse.width <= 0 || ellipse.height <= 0) + { + return false; + } + + // Normalize the coords to an ellipse with center 0,0 and a radius of 0.5 + var normx = ((x - ellipse.x) / ellipse.width); + var normy = ((y - ellipse.y) / ellipse.height); + + normx *= normx; + normy *= normy; + + return (normx + normy < 0.25); +}; + +export default Contains; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/ContainsPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/ContainsPoint.js new file mode 100644 index 000000000..ff141fdc9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/ContainsPoint.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Contains from './Contains.js'; + +/** + * Check to see if the Ellipse contains the given Point object. + * + * @function Phaser.Geom.Ellipse.ContainsPoint + * @since 3.0.0 + * + * @param {Phaser.Geom.Ellipse} ellipse - The Ellipse to check. + * @param {(Phaser.Geom.Point|object)} point - The Point object to check if it's within the Circle or not. + * + * @return {boolean} True if the Point coordinates are within the circle, otherwise false. + */ +var ContainsPoint = function (ellipse, point) { + return Contains(ellipse, point.x, point.y); +}; + +export default ContainsPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/ContainsRect.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/ContainsRect.js new file mode 100644 index 000000000..5359eb033 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/ContainsRect.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Contains from './Contains.js'; + +/** + * Check to see if the Ellipse contains all four points of the given Rectangle object. + * + * @function Phaser.Geom.Ellipse.ContainsRect + * @since 3.0.0 + * + * @param {Phaser.Geom.Ellipse} ellipse - The Ellipse to check. + * @param {(Phaser.Geom.Rectangle|object)} rect - The Rectangle object to check if it's within the Ellipse or not. + * + * @return {boolean} True if all of the Rectangle coordinates are within the ellipse, otherwise false. + */ +var ContainsRect = function (ellipse, rect) { + return ( + Contains(ellipse, rect.x, rect.y) && + Contains(ellipse, rect.right, rect.y) && + Contains(ellipse, rect.x, rect.bottom) && + Contains(ellipse, rect.right, rect.bottom) + ); +}; + +export default ContainsRect; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/CopyFrom.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/CopyFrom.js new file mode 100644 index 000000000..e7a7b5311 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/CopyFrom.js @@ -0,0 +1,26 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Copies the `x`, `y`, `width` and `height` properties from the `source` Ellipse + * into the given `dest` Ellipse, then returns the `dest` Ellipse. + * + * @function Phaser.Geom.Ellipse.CopyFrom + * @since 3.0.0 + * + * @generic {Phaser.Geom.Ellipse} O - [dest,$return] + * + * @param {Phaser.Geom.Ellipse} source - The source Ellipse to copy the values from. + * @param {Phaser.Geom.Ellipse} dest - The destination Ellipse to copy the values to. + * + * @return {Phaser.Geom.Ellipse} The destination Ellipse. + */ +var CopyFrom = function (source, dest) +{ + return dest.setTo(source.x, source.y, source.width, source.height); +}; + +export default CopyFrom; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Ellipse.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Ellipse.d.ts new file mode 100644 index 000000000..1bbbf67a2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Ellipse.d.ts @@ -0,0 +1,258 @@ +import Point from '../point/Point'; +import Rectangle from '../rectangle/Rectangle'; + +/** + * An Ellipse object. + * + * This is a geometry object, containing numerical values and related methods to inspect and modify them. + * It is not a Game Object, in that you cannot add it to the display list, and it has no texture. + * To render an Ellipse you should look at the capabilities of the Graphics class. + */ +declare class Ellipse { + /** + * + * @param x The x position of the center of the ellipse. Default 0. + * @param y The y position of the center of the ellipse. Default 0. + * @param width The width of the ellipse. Default 0. + * @param height The height of the ellipse. Default 0. + */ + constructor(x?: number, y?: number, width?: number, height?: number); + + /** + * Calculates the area of the Ellipse. + * @param ellipse The Ellipse to get the area of. + */ + static Area(ellipse: Ellipse): number; + + /** + * Returns the circumference of the given Ellipse. + * @param ellipse The Ellipse to get the circumference of. + */ + static Circumference(ellipse: Ellipse): number; + + /** + * Returns a Point object containing the coordinates of a point on the circumference of the Ellipse based on the given angle. + * @param ellipse The Ellipse to get the circumference point on. + * @param angle The angle from the center of the Ellipse to the circumference to return the point from. Given in radians. + * @param out A Point, or point-like object, to store the results in. If not given a Point will be created. + */ + static CircumferencePoint(ellipse: Ellipse, angle: number, out?: O): O; + + /** + * Creates a new Ellipse instance based on the values contained in the given source. + * @param source The Ellipse to be cloned. Can be an instance of an Ellipse or a ellipse-like object, with x, y, width and height properties. + */ + static Clone(source: Ellipse): Ellipse; + + /** + * Check to see if the Ellipse contains the given x / y coordinates. + * @param ellipse The Ellipse to check. + * @param x The x coordinate to check within the ellipse. + * @param y The y coordinate to check within the ellipse. + */ + static Contains(ellipse: Ellipse, x: number, y: number): boolean; + + /** + * Check to see if the Ellipse contains the given Point object. + * @param ellipse The Ellipse to check. + * @param point The Point object to check if it's within the Circle or not. + */ + static ContainsPoint(ellipse: Ellipse, point: Point | object): boolean; + + /** + * Check to see if the Ellipse contains all four points of the given Rectangle object. + * @param ellipse The Ellipse to check. + * @param rect The Rectangle object to check if it's within the Ellipse or not. + */ + static ContainsRect(ellipse: Ellipse, rect: Rectangle | object): boolean; + + /** + * Copies the `x`, `y`, `width` and `height` properties from the `source` Ellipse + * into the given `dest` Ellipse, then returns the `dest` Ellipse. + * @param source The source Ellipse to copy the values from. + * @param dest The destination Ellipse to copy the values to. + */ + static CopyFrom(source: Ellipse, dest: O): O; + + /** + * The geometry constant type of this object: `GEOM_CONST.ELLIPSE`. + * Used for fast type comparisons. + */ + readonly type: number; + + /** + * The x position of the center of the ellipse. + */ + x: number; + + /** + * The y position of the center of the ellipse. + */ + y: number; + + /** + * The width of the ellipse. + */ + width: number; + + /** + * The height of the ellipse. + */ + height: number; + + /** + * Check to see if the Ellipse contains the given x / y coordinates. + * @param x The x coordinate to check within the ellipse. + * @param y The y coordinate to check within the ellipse. + */ + contains(x: number, y: number): boolean; + + /** + * Returns a Point object containing the coordinates of a point on the circumference of the Ellipse + * based on the given angle normalized to the range 0 to 1. I.e. a value of 0.5 will give the point + * at 180 degrees around the circle. + * @param position A value between 0 and 1, where 0 equals 0 degrees, 0.5 equals 180 degrees and 1 equals 360 around the ellipse. + * @param out An object to store the return values in. If not given a Point object will be created. + */ + getPoint(position: number, out?: O): O; + + /** + * Returns an array of Point objects containing the coordinates of the points around the circumference of the Ellipse, + * based on the given quantity or stepRate values. + * @param quantity The amount of points to return. If a falsey value the quantity will be derived from the `stepRate` instead. + * @param stepRate Sets the quantity by getting the circumference of the ellipse and dividing it by the stepRate. + * @param output An array to insert the points in to. If not provided a new array will be created. + */ + getPoints(quantity: number, stepRate?: number, output?: O): O; + + /** + * Returns a uniformly distributed random point from anywhere within the given Ellipse. + * @param point A Point or point-like object to set the random `x` and `y` values in. + */ + getRandomPoint(point?: O): O; + + /** + * Sets the x, y, width and height of this ellipse. + * @param x The x position of the center of the ellipse. + * @param y The y position of the center of the ellipse. + * @param width The width of the ellipse. + * @param height The height of the ellipse. + */ + setTo(x: number, y: number, width: number, height: number): this; + + /** + * Sets this Ellipse to be empty with a width and height of zero. + * Does not change its position. + */ + setEmpty(): this; + + /** + * Sets the position of this Ellipse. + * @param x The x position of the center of the ellipse. + * @param y The y position of the center of the ellipse. + */ + setPosition(x: number, y: number): this; + + /** + * Sets the size of this Ellipse. + * Does not change its position. + * @param width The width of the ellipse. + * @param height The height of the ellipse. Default width. + */ + setSize(width: number, height?: number): this; + + /** + * Checks to see if the Ellipse is empty: has a width or height equal to zero. + */ + isEmpty(): boolean; + + /** + * Returns the minor radius of the ellipse. Also known as the Semi Minor Axis. + */ + getMinorRadius(): number; + + /** + * Returns the major radius of the ellipse. Also known as the Semi Major Axis. + */ + getMajorRadius(): number; + + /** + * The left position of the Ellipse. + */ + left: number; + + /** + * The right position of the Ellipse. + */ + right: number; + + /** + * The top position of the Ellipse. + */ + top: number; + + /** + * The bottom position of the Ellipse. + */ + bottom: number; + + /** + * Compares the `x`, `y`, `width` and `height` properties of the two given Ellipses. + * Returns `true` if they all match, otherwise returns `false`. + * @param ellipse The first Ellipse to compare. + * @param toCompare The second Ellipse to compare. + */ + static Equals(ellipse: Ellipse, toCompare: Ellipse): boolean; + + /** + * Returns the bounds of the Ellipse object. + * @param ellipse The Ellipse to get the bounds from. + * @param out A Rectangle, or rectangle-like object, to store the ellipse bounds in. If not given a new Rectangle will be created. + */ + static GetBounds(ellipse: Ellipse, out?: O): O; + + /** + * Returns a Point object containing the coordinates of a point on the circumference of the Ellipse + * based on the given angle normalized to the range 0 to 1. I.e. a value of 0.5 will give the point + * at 180 degrees around the circle. + * @param ellipse The Ellipse to get the circumference point on. + * @param position A value between 0 and 1, where 0 equals 0 degrees, 0.5 equals 180 degrees and 1 equals 360 around the ellipse. + * @param out An object to store the return values in. If not given a Point object will be created. + */ + static GetPoint(ellipse: Ellipse, position: number, out?: O): O; + + /** + * Returns an array of Point objects containing the coordinates of the points around the circumference of the Ellipse, + * based on the given quantity or stepRate values. + * @param ellipse The Ellipse to get the points from. + * @param quantity The amount of points to return. If a falsey value the quantity will be derived from the `stepRate` instead. + * @param stepRate Sets the quantity by getting the circumference of the ellipse and dividing it by the stepRate. + * @param out An array to insert the points in to. If not provided a new array will be created. + */ + static GetPoints(ellipse: Ellipse, quantity: number, stepRate?: number, out?: O): O; + + /** + * Offsets the Ellipse by the values given. + * @param ellipse The Ellipse to be offset (translated.) + * @param x The amount to horizontally offset the Ellipse by. + * @param y The amount to vertically offset the Ellipse by. + */ + static Offset(ellipse: O, x: number, y: number): O; + + /** + * Offsets the Ellipse by the values given in the `x` and `y` properties of the Point object. + * @param ellipse The Ellipse to be offset (translated.) + * @param point The Point object containing the values to offset the Ellipse by. + */ + static OffsetPoint(ellipse: O, point: Point | object): O; + + /** + * Returns a uniformly distributed random point from anywhere within the given Ellipse. + * @param ellipse The Ellipse to get a random point from. + * @param out A Point or point-like object to set the random `x` and `y` values in. + */ + static Random(ellipse: Ellipse, out?: O): O; + +} + +export default Ellipse; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Ellipse.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Ellipse.js new file mode 100644 index 000000000..dca05a194 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Ellipse.js @@ -0,0 +1,342 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Class from '../../object/Class.js'; +import Contains from './Contains.js'; +import GetPoint from './GetPoint.js'; +import GetPoints from './GetPoints.js'; +import Random from './Random.js'; + +/** + * @classdesc + * An Ellipse object. + * + * This is a geometry object, containing numerical values and related methods to inspect and modify them. + * It is not a Game Object, in that you cannot add it to the display list, and it has no texture. + * To render an Ellipse you should look at the capabilities of the Graphics class. + * + * @class Ellipse + * @memberof Phaser.Geom + * @constructor + * @since 3.0.0 + * + * @param {number} [x=0] - The x position of the center of the ellipse. + * @param {number} [y=0] - The y position of the center of the ellipse. + * @param {number} [width=0] - The width of the ellipse. + * @param {number} [height=0] - The height of the ellipse. + */ +var Ellipse = new Class({ + + initialize: + + function Ellipse(x, y, width, height) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (width === undefined) { width = 0; } + if (height === undefined) { height = 0; } + + /** + * The x position of the center of the ellipse. + * + * @name Phaser.Geom.Ellipse#x + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.x = x; + + /** + * The y position of the center of the ellipse. + * + * @name Phaser.Geom.Ellipse#y + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.y = y; + + /** + * The width of the ellipse. + * + * @name Phaser.Geom.Ellipse#width + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.width = width; + + /** + * The height of the ellipse. + * + * @name Phaser.Geom.Ellipse#height + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.height = height; + }, + + /** + * Check to see if the Ellipse contains the given x / y coordinates. + * + * @method Phaser.Geom.Ellipse#contains + * @since 3.0.0 + * + * @param {number} x - The x coordinate to check within the ellipse. + * @param {number} y - The y coordinate to check within the ellipse. + * + * @return {boolean} True if the coordinates are within the ellipse, otherwise false. + */ + contains: function (x, y) { + return Contains(this, x, y); + }, + + /** + * Returns a Point object containing the coordinates of a point on the circumference of the Ellipse + * based on the given angle normalized to the range 0 to 1. I.e. a value of 0.5 will give the point + * at 180 degrees around the circle. + * + * @method Phaser.Geom.Ellipse#getPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {number} position - A value between 0 and 1, where 0 equals 0 degrees, 0.5 equals 180 degrees and 1 equals 360 around the ellipse. + * @param {(Phaser.Geom.Point|object)} [out] - An object to store the return values in. If not given a Point object will be created. + * + * @return {(Phaser.Geom.Point|object)} A Point, or point-like object, containing the coordinates of the point around the ellipse. + */ + getPoint: function (position, point) { + return GetPoint(this, position, point); + }, + + /** + * Returns an array of Point objects containing the coordinates of the points around the circumference of the Ellipse, + * based on the given quantity or stepRate values. + * + * @method Phaser.Geom.Ellipse#getPoints + * @since 3.0.0 + * + * @param {integer} quantity - The amount of points to return. If a falsey value the quantity will be derived from the `stepRate` instead. + * @param {number} [stepRate] - Sets the quantity by getting the circumference of the ellipse and dividing it by the stepRate. + * @param {array} [output] - An array to insert the points in to. If not provided a new array will be created. + * + * @return {Phaser.Geom.Point[]} An array of Point objects pertaining to the points around the circumference of the ellipse. + */ + getPoints: function (quantity, stepRate, output) { + return GetPoints(this, quantity, stepRate, output); + }, + + /** + * Returns a uniformly distributed random point from anywhere within the given Ellipse. + * + * @method Phaser.Geom.Ellipse#getRandomPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [point,$return] + * + * @param {(Phaser.Geom.Point|object)} [point] - A Point or point-like object to set the random `x` and `y` values in. + * + * @return {(Phaser.Geom.Point|object)} A Point object with the random values set in the `x` and `y` properties. + */ + getRandomPoint: function (point) { + return Random(this, point); + }, + + /** + * Sets the x, y, width and height of this ellipse. + * + * @method Phaser.Geom.Ellipse#setTo + * @since 3.0.0 + * + * @param {number} x - The x position of the center of the ellipse. + * @param {number} y - The y position of the center of the ellipse. + * @param {number} width - The width of the ellipse. + * @param {number} height - The height of the ellipse. + * + * @return {Phaser.Geom.Ellipse} This Ellipse object. + */ + setTo: function (x, y, width, height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + + return this; + }, + + /** + * Sets this Ellipse to be empty with a width and height of zero. + * Does not change its position. + * + * @method Phaser.Geom.Ellipse#setEmpty + * @since 3.0.0 + * + * @return {Phaser.Geom.Ellipse} This Ellipse object. + */ + setEmpty: function () { + this.width = 0; + this.height = 0; + + return this; + }, + + /** + * Sets the position of this Ellipse. + * + * @method Phaser.Geom.Ellipse#setPosition + * @since 3.0.0 + * + * @param {number} x - The x position of the center of the ellipse. + * @param {number} y - The y position of the center of the ellipse. + * + * @return {Phaser.Geom.Ellipse} This Ellipse object. + */ + setPosition: function (x, y) { + if (y === undefined) { y = x; } + + this.x = x; + this.y = y; + + return this; + }, + + /** + * Sets the size of this Ellipse. + * Does not change its position. + * + * @method Phaser.Geom.Ellipse#setSize + * @since 3.0.0 + * + * @param {number} width - The width of the ellipse. + * @param {number} [height=width] - The height of the ellipse. + * + * @return {Phaser.Geom.Ellipse} This Ellipse object. + */ + setSize: function (width, height) { + if (height === undefined) { height = width; } + + this.width = width; + this.height = height; + + return this; + }, + + /** + * Checks to see if the Ellipse is empty: has a width or height equal to zero. + * + * @method Phaser.Geom.Ellipse#isEmpty + * @since 3.0.0 + * + * @return {boolean} True if the Ellipse is empty, otherwise false. + */ + isEmpty: function () { + return (this.width <= 0 || this.height <= 0); + }, + + /** + * Returns the minor radius of the ellipse. Also known as the Semi Minor Axis. + * + * @method Phaser.Geom.Ellipse#getMinorRadius + * @since 3.0.0 + * + * @return {number} The minor radius. + */ + getMinorRadius: function () { + return Math.min(this.width, this.height) / 2; + }, + + /** + * Returns the major radius of the ellipse. Also known as the Semi Major Axis. + * + * @method Phaser.Geom.Ellipse#getMajorRadius + * @since 3.0.0 + * + * @return {number} The major radius. + */ + getMajorRadius: function () { + return Math.max(this.width, this.height) / 2; + }, + + /** + * The left position of the Ellipse. + * + * @name Phaser.Geom.Ellipse#left + * @type {number} + * @since 3.0.0 + */ + left: { + + get: function () { + return this.x - (this.width / 2); + }, + + set: function (value) { + this.x = value + (this.width / 2); + } + + }, + + /** + * The right position of the Ellipse. + * + * @name Phaser.Geom.Ellipse#right + * @type {number} + * @since 3.0.0 + */ + right: { + + get: function () { + return this.x + (this.width / 2); + }, + + set: function (value) { + this.x = value - (this.width / 2); + } + + }, + + /** + * The top position of the Ellipse. + * + * @name Phaser.Geom.Ellipse#top + * @type {number} + * @since 3.0.0 + */ + top: { + + get: function () { + return this.y - (this.height / 2); + }, + + set: function (value) { + this.y = value + (this.height / 2); + } + + }, + + /** + * The bottom position of the Ellipse. + * + * @name Phaser.Geom.Ellipse#bottom + * @type {number} + * @since 3.0.0 + */ + bottom: { + + get: function () { + return this.y + (this.height / 2); + }, + + set: function (value) { + this.y = value - (this.height / 2); + } + + } + +}); + +export default Ellipse; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Equals.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Equals.js new file mode 100644 index 000000000..2cb95adad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Equals.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Compares the `x`, `y`, `width` and `height` properties of the two given Ellipses. + * Returns `true` if they all match, otherwise returns `false`. + * + * @function Phaser.Geom.Ellipse.Equals + * @since 3.0.0 + * + * @param {Phaser.Geom.Ellipse} ellipse - The first Ellipse to compare. + * @param {Phaser.Geom.Ellipse} toCompare - The second Ellipse to compare. + * + * @return {boolean} `true` if the two Ellipse equal each other, otherwise `false`. + */ +var Equals = function (ellipse, toCompare) +{ + return ( + ellipse.x === toCompare.x && + ellipse.y === toCompare.y && + ellipse.width === toCompare.width && + ellipse.height === toCompare.height + ); +}; + +export default Equals; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/GetBounds.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/GetBounds.js new file mode 100644 index 000000000..5067dbf0d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/GetBounds.js @@ -0,0 +1,33 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Rectangle from '../rectangle/Rectangle.js'; + +/** + * Returns the bounds of the Ellipse object. + * + * @function Phaser.Geom.Ellipse.GetBounds + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [out,$return] + * + * @param {Phaser.Geom.Ellipse} ellipse - The Ellipse to get the bounds from. + * @param {(Phaser.Geom.Rectangle|object)} [out] - A Rectangle, or rectangle-like object, to store the ellipse bounds in. If not given a new Rectangle will be created. + * + * @return {(Phaser.Geom.Rectangle|object)} The Rectangle object containing the Ellipse bounds. + */ +var GetBounds = function (ellipse, out) { + if (out === undefined) { out = new Rectangle(); } + + out.x = ellipse.left; + out.y = ellipse.top; + out.width = ellipse.width; + out.height = ellipse.height; + + return out; +}; + +export default GetBounds; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/GetPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/GetPoint.js new file mode 100644 index 000000000..3a1a7cd5e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/GetPoint.js @@ -0,0 +1,36 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import CircumferencePoint from './CircumferencePoint.js'; +import FromPercent from '../../math/FromPercent.js'; +import MATH_CONST from '../../math/const.js'; +import Point from '../point/Point.js'; + +/** + * Returns a Point object containing the coordinates of a point on the circumference of the Ellipse + * based on the given angle normalized to the range 0 to 1. I.e. a value of 0.5 will give the point + * at 180 degrees around the circle. + * + * @function Phaser.Geom.Ellipse.GetPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Ellipse} ellipse - The Ellipse to get the circumference point on. + * @param {number} position - A value between 0 and 1, where 0 equals 0 degrees, 0.5 equals 180 degrees and 1 equals 360 around the ellipse. + * @param {(Phaser.Geom.Point|object)} [out] - An object to store the return values in. If not given a Point object will be created. + * + * @return {(Phaser.Geom.Point|object)} A Point, or point-like object, containing the coordinates of the point around the ellipse. + */ +var GetPoint = function (ellipse, position, out) { + if (out === undefined) { out = new Point(); } + + var angle = FromPercent(position, 0, MATH_CONST.PI2); + + return CircumferencePoint(ellipse, angle, out); +}; + +export default GetPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/GetPoints.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/GetPoints.js new file mode 100644 index 000000000..351c6b82a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/GetPoints.js @@ -0,0 +1,45 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Circumference from './Circumference.js'; +import CircumferencePoint from './CircumferencePoint.js'; +import FromPercent from '../../math/FromPercent.js'; +import MATH_CONST from '../../math/const.js'; + +/** + * Returns an array of Point objects containing the coordinates of the points around the circumference of the Ellipse, + * based on the given quantity or stepRate values. + * + * @function Phaser.Geom.Ellipse.GetPoints + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point[]} O - [out,$return] + * + * @param {Phaser.Geom.Ellipse} ellipse - The Ellipse to get the points from. + * @param {integer} quantity - The amount of points to return. If a falsey value the quantity will be derived from the `stepRate` instead. + * @param {number} [stepRate] - Sets the quantity by getting the circumference of the ellipse and dividing it by the stepRate. + * @param {(array|Phaser.Geom.Point[])} [out] - An array to insert the points in to. If not provided a new array will be created. + * + * @return {(array|Phaser.Geom.Point[])} An array of Point objects pertaining to the points around the circumference of the ellipse. + */ +var GetPoints = function (ellipse, quantity, stepRate, out) { + if (out === undefined) { out = []; } + + // If quantity is a falsey value (false, null, 0, undefined, etc) then we calculate it based on the stepRate instead. + if (!quantity) { + quantity = Circumference(ellipse) / stepRate; + } + + for (var i = 0; i < quantity; i++) { + var angle = FromPercent(i / quantity, 0, MATH_CONST.PI2); + + out.push(CircumferencePoint(ellipse, angle)); + } + + return out; +}; + +export default GetPoints; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Offset.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Offset.js new file mode 100644 index 000000000..931eb77ee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Offset.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Offsets the Ellipse by the values given. + * + * @function Phaser.Geom.Ellipse.Offset + * @since 3.0.0 + * + * @generic {Phaser.Geom.Ellipse} O - [ellipse,$return] + * + * @param {Phaser.Geom.Ellipse} ellipse - The Ellipse to be offset (translated.) + * @param {number} x - The amount to horizontally offset the Ellipse by. + * @param {number} y - The amount to vertically offset the Ellipse by. + * + * @return {Phaser.Geom.Ellipse} The Ellipse that was offset. + */ +var Offset = function (ellipse, x, y) +{ + ellipse.x += x; + ellipse.y += y; + + return ellipse; +}; + +export default Offset; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/OffsetPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/OffsetPoint.js new file mode 100644 index 000000000..4440b32ec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/OffsetPoint.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Offsets the Ellipse by the values given in the `x` and `y` properties of the Point object. + * + * @function Phaser.Geom.Ellipse.OffsetPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Ellipse} O - [ellipse,$return] + * + * @param {Phaser.Geom.Ellipse} ellipse - The Ellipse to be offset (translated.) + * @param {(Phaser.Geom.Point|object)} point - The Point object containing the values to offset the Ellipse by. + * + * @return {Phaser.Geom.Ellipse} The Ellipse that was offset. + */ +var OffsetPoint = function (ellipse, point) +{ + ellipse.x += point.x; + ellipse.y += point.y; + + return ellipse; +}; + +export default OffsetPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Random.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Random.js new file mode 100644 index 000000000..987ff3a56 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Random.js @@ -0,0 +1,34 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +/** + * Returns a uniformly distributed random point from anywhere within the given Ellipse. + * + * @function Phaser.Geom.Ellipse.Random + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Ellipse} ellipse - The Ellipse to get a random point from. + * @param {(Phaser.Geom.Point|object)} [out] - A Point or point-like object to set the random `x` and `y` values in. + * + * @return {(Phaser.Geom.Point|object)} A Point object with the random values set in the `x` and `y` properties. + */ +var Random = function (ellipse, out) { + if (out === undefined) { out = new Point(); } + + var p = Math.random() * Math.PI * 2; + var s = Math.sqrt(Math.random()); + + out.x = ellipse.x + ((s * Math.cos(p)) * ellipse.width / 2); + out.y = ellipse.y + ((s * Math.sin(p)) * ellipse.height / 2); + + return out; +}; + +export default Random; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/index.js new file mode 100644 index 000000000..ae534f717 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/index.js @@ -0,0 +1,40 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Ellipse from './Ellipse.js'; +import Area from './Area.js'; +import Circumference from './Circumference.js'; +import CircumferencePoint from './CircumferencePoint.js'; +import Clone from './Clone.js'; +import Contains from './Contains.js'; +import ContainsPoint from './ContainsPoint.js'; +import ContainsRect from './ContainsRect.js'; +import CopyFrom from './CopyFrom.js'; +import Equals from './Equals.js'; +import GetBounds from './GetBounds.js'; +import GetPoint from './GetPoint.js'; +import GetPoints from './GetPoints.js'; +import Offset from './Offset.js'; +import OffsetPoint from './OffsetPoint.js'; +import Random from './Random.js'; + +Ellipse.Area = Area; +Ellipse.Circumference = Circumference; +Ellipse.CircumferencePoint = CircumferencePoint; +Ellipse.Clone = Clone; +Ellipse.Contains = Contains; +Ellipse.ContainsPoint = ContainsPoint; +Ellipse.ContainsRect = ContainsRect; +Ellipse.CopyFrom = CopyFrom; +Ellipse.Equals = Equals; +Ellipse.GetBounds = GetBounds; +Ellipse.GetPoint = GetPoint; +Ellipse.GetPoints = GetPoints; +Ellipse.Offset = Offset; +Ellipse.OffsetPoint = OffsetPoint; +Ellipse.Random = Random; + +export default Ellipse; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/index.js new file mode 100644 index 000000000..a8da02d97 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/index.js @@ -0,0 +1,31 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * @namespace Phaser.Geom + */ + +import Circle from './circle.js'; +import Ellipse from './ellipse.js'; +import Intersects from './intersects.js'; +import Line from './line.js'; +import Point from './point.js'; +import Polygon from './point.js'; +import Rectangle from './rectangle.js'; +import Triangle from './triangle.js'; + +export default { + + Circle: Circle, + Ellipse: Ellipse, + Intersects: Intersects, + Line: Line, + Point: Point, + Polygon: Polygon, + Rectangle: Rectangle, + Triangle: Triangle + +}; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/CircleToCircle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/CircleToCircle.js new file mode 100644 index 000000000..d94b2ec05 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/CircleToCircle.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import DistanceBetween from '../../math/distance/DistanceBetween.js'; + +/** + * Checks if two Circles intersect. + * + * @function Phaser.Geom.Intersects.CircleToCircle + * @since 3.0.0 + * + * @param {Phaser.Geom.Circle} circleA - The first Circle to check for intersection. + * @param {Phaser.Geom.Circle} circleB - The second Circle to check for intersection. + * + * @return {boolean} `true` if the two Circles intersect, otherwise `false`. + */ +var CircleToCircle = function (circleA, circleB) { + return (DistanceBetween(circleA.x, circleA.y, circleB.x, circleB.y) <= (circleA.radius + circleB.radius)); +}; + +export default CircleToCircle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/CircleToRectangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/CircleToRectangle.js new file mode 100644 index 000000000..abd62a016 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/CircleToRectangle.js @@ -0,0 +1,48 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Checks for intersection between a circle and a rectangle. + * + * @function Phaser.Geom.Intersects.CircleToRectangle + * @since 3.0.0 + * + * @param {Phaser.Geom.Circle} circle - The circle to be checked. + * @param {Phaser.Geom.Rectangle} rect - The rectangle to be checked. + * + * @return {boolean} `true` if the two objects intersect, otherwise `false`. + */ +var CircleToRectangle = function (circle, rect) +{ + var halfWidth = rect.width / 2; + var halfHeight = rect.height / 2; + + var cx = Math.abs(circle.x - rect.x - halfWidth); + var cy = Math.abs(circle.y - rect.y - halfHeight); + var xDist = halfWidth + circle.radius; + var yDist = halfHeight + circle.radius; + + if (cx > xDist || cy > yDist) + { + return false; + } + else if (cx <= halfWidth || cy <= halfHeight) + { + return true; + } + else + { + var xCornerDist = cx - halfWidth; + var yCornerDist = cy - halfHeight; + var xCornerDistSq = xCornerDist * xCornerDist; + var yCornerDistSq = yCornerDist * yCornerDist; + var maxCornerDistSq = circle.radius * circle.radius; + + return (xCornerDistSq + yCornerDistSq <= maxCornerDistSq); + } +}; + +export default CircleToRectangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetCircleToCircle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetCircleToCircle.js new file mode 100644 index 000000000..410cc457d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetCircleToCircle.js @@ -0,0 +1,80 @@ +/** + * @author Florian Vazelle + * @author Geoffrey Glaive + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; +import CircleToCircle from './CircleToCircle.js'; + +/** + * Checks if two Circles intersect and returns the intersection points as a Point object array. + * + * @function Phaser.Geom.Intersects.GetCircleToCircle + * @since 3.0.0 + * + * @param {Phaser.Geom.Circle} circleA - The first Circle to check for intersection. + * @param {Phaser.Geom.Circle} circleB - The second Circle to check for intersection. + * @param {array} [out] - An optional array in which to store the points of intersection. + * + * @return {array} An array with the points of intersection if objects intersect, otherwise an empty array. + */ +var GetCircleToCircle = function (circleA, circleB, out) { + if (out === undefined) { out = []; } + + if (CircleToCircle(circleA, circleB)) { + var x0 = circleA.x; + var y0 = circleA.y; + var r0 = circleA.radius; + + var x1 = circleB.x; + var y1 = circleB.y; + var r1 = circleB.radius; + + var coefficientA, coefficientB, coefficientC, lambda, x; + + if (y0 === y1) { + x = ((r1 * r1) - (r0 * r0) - (x1 * x1) + (x0 * x0)) / (2 * (x0 - x1)); + + coefficientA = 1; + coefficientB = -2 * y1; + coefficientC = (x1 * x1) + (x * x) - (2 * x1 * x) + (y1 * y1) - (r1 * r1); + + lambda = (coefficientB * coefficientB) - (4 * coefficientA * coefficientC); + + if (lambda === 0) { + out.push(new Point(x, (-coefficientB / (2 * coefficientA)))); + } + else if (lambda > 0) { + out.push(new Point(x, (-coefficientB + Math.sqrt(lambda)) / (2 * coefficientA))); + out.push(new Point(x, (-coefficientB - Math.sqrt(lambda)) / (2 * coefficientA))); + } + } + else { + var v1 = (x0 - x1) / (y0 - y1); + var n = (r1 * r1 - r0 * r0 - x1 * x1 + x0 * x0 - y1 * y1 + y0 * y0) / (2 * (y0 - y1)); + + coefficientA = (v1 * v1) + 1; + coefficientB = (2 * y0 * v1) - (2 * n * v1) - (2 * x0); + coefficientC = (x0 * x0) + (y0 * y0) + (n * n) - (r0 * r0) - (2 * y0 * n); + + lambda = (coefficientB * coefficientB) - (4 * coefficientA * coefficientC); + + if (lambda === 0) { + x = (-coefficientB / (2 * coefficientA)); + out.push(new Point(x, (n - (x * v1)))); + } + else if (lambda > 0) { + x = (-coefficientB + Math.sqrt(lambda)) / (2 * coefficientA); + out.push(new Point(x, (n - (x * v1)))); + x = (-coefficientB - Math.sqrt(lambda)) / (2 * coefficientA); + out.push(new Point(x, (n - (x * v1)))); + } + } + } + + return out; +}; + +export default GetCircleToCircle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetCircleToRectangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetCircleToRectangle.js new file mode 100644 index 000000000..bdfeaf267 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetCircleToRectangle.js @@ -0,0 +1,44 @@ +/** + * @author Florian Vazelle + * @author Geoffrey Glaive + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import GetLineToCircle from './GetLineToCircle.js'; +import CircleToRectangle from './CircleToRectangle.js'; + +/** + * Checks for intersection between a circle and a rectangle, + * and returns the intersection points as a Point object array. + * + * @function Phaser.Geom.Intersects.GetCircleToRectangle + * @since 3.0.0 + * + * @param {Phaser.Geom.Circle} circle - The circle to be checked. + * @param {Phaser.Geom.Rectangle} rect - The rectangle to be checked. + * @param {array} [out] - An optional array in which to store the points of intersection. + * + * @return {array} An array with the points of intersection if objects intersect, otherwise an empty array. + */ +var GetCircleToRectangle = function (circle, rect, out) +{ + if (out === undefined) { out = []; } + + if (CircleToRectangle(circle, rect)) + { + var lineA = rect.getLineA(); + var lineB = rect.getLineB(); + var lineC = rect.getLineC(); + var lineD = rect.getLineD(); + + GetLineToCircle(lineA, circle, out); + GetLineToCircle(lineB, circle, out); + GetLineToCircle(lineC, circle, out); + GetLineToCircle(lineD, circle, out); + } + + return out; +}; + +export default GetCircleToRectangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetLineToCircle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetLineToCircle.js new file mode 100644 index 000000000..94ec7958f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetLineToCircle.js @@ -0,0 +1,79 @@ +/** + * @author Florian Vazelle + * @author Geoffrey Glaive + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; +import LineToCircle from './LineToCircle.js'; + +/** + * Checks for intersection between the line segment and circle, + * and returns the intersection points as a Point object array. + * + * @function Phaser.Geom.Intersects.GetLineToCircle + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The line segment to check. + * @param {Phaser.Geom.Circle} circle - The circle to check against the line. + * @param {array} [out] - An optional array in which to store the points of intersection. + * + * @return {array} An array with the points of intersection if objects intersect, otherwise an empty array. + */ +var GetLineToCircle = function (line, circle, out) { + if (out === undefined) { out = []; } + + if (LineToCircle(line, circle)) { + var lx1 = line.x1; + var ly1 = line.y1; + + var lx2 = line.x2; + var ly2 = line.y2; + + var cx = circle.x; + var cy = circle.y; + var cr = circle.radius; + + var lDirX = lx2 - lx1; + var lDirY = ly2 - ly1; + var oDirX = lx1 - cx; + var oDirY = ly1 - cy; + + var coefficientA = lDirX * lDirX + lDirY * lDirY; + var coefficientB = 2 * (lDirX * oDirX + lDirY * oDirY); + var coefficientC = oDirX * oDirX + oDirY * oDirY - cr * cr; + + var lambda = (coefficientB * coefficientB) - (4 * coefficientA * coefficientC); + + var x, y; + + if (lambda === 0) { + var root = -coefficientB / (2 * coefficientA); + x = lx1 + root * lDirX; + y = ly1 + root * lDirY; + if (root >= 0 && root <= 1) { + out.push(new Point(x, y)); + } + } + else if (lambda > 0) { + var root1 = (-coefficientB - Math.sqrt(lambda)) / (2 * coefficientA); + x = lx1 + root1 * lDirX; + y = ly1 + root1 * lDirY; + if (root1 >= 0 && root1 <= 1) { + out.push(new Point(x, y)); + } + + var root2 = (-coefficientB + Math.sqrt(lambda)) / (2 * coefficientA); + x = lx1 + root2 * lDirX; + y = ly1 + root2 * lDirY; + if (root2 >= 0 && root2 <= 1) { + out.push(new Point(x, y)); + } + } + } + + return out; +}; + +export default GetLineToCircle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetLineToRectangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetLineToRectangle.js new file mode 100644 index 000000000..6cc153a8e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetLineToRectangle.js @@ -0,0 +1,51 @@ +/** + * @author Florian Vazelle + * @author Geoffrey Glaive + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; +import LineToLine from './LineToLine.js'; +import LineToRectangle from './LineToRectangle.js'; + +/** + * Checks for intersection between the Line and a Rectangle shape, + * and returns the intersection points as a Point object array. + * + * @function Phaser.Geom.Intersects.GetLineToRectangle + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The Line to check for intersection. + * @param {(Phaser.Geom.Rectangle|object)} rect - The Rectangle to check for intersection. + * @param {array} [out] - An optional array in which to store the points of intersection. + * + * @return {array} An array with the points of intersection if objects intersect, otherwise an empty array. + */ +var GetLineToRectangle = function (line, rect, out) { + if (out === undefined) { out = []; } + + if (LineToRectangle(line, rect)) { + var lineA = rect.getLineA(); + var lineB = rect.getLineB(); + var lineC = rect.getLineC(); + var lineD = rect.getLineD(); + + var output = [new Point(), new Point(), new Point(), new Point()]; + + var result = [ + LineToLine(lineA, line, output[0]), + LineToLine(lineB, line, output[1]), + LineToLine(lineC, line, output[2]), + LineToLine(lineD, line, output[3]) + ]; + + for (var i = 0; i < 4; i++) { + if (result[i]) { out.push(output[i]); } + } + } + + return out; +}; + +export default GetLineToRectangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetRectangleIntersection.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetRectangleIntersection.js new file mode 100644 index 000000000..0ba396858 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetRectangleIntersection.js @@ -0,0 +1,41 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Rectangle from '../rectangle/Rectangle.js'; +import RectangleToRectangle from './RectangleToRectangle.js'; + +/** + * Checks if two Rectangle shapes intersect and returns the area of this intersection as Rectangle object. + * + * If optional `output` parameter is omitted, new Rectangle object is created and returned. If there is intersection, it will contain intersection area. If there is no intersection, it wil be empty Rectangle (all values set to zero). + * + * If Rectangle object is passed as `output` and there is intersection, then intersection area data will be loaded into it and it will be returned. If there is no intersetion, it will be returned without any change. + * + * @function Phaser.Geom.Intersects.GetRectangleIntersection + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [output,$return] + * + * @param {Phaser.Geom.Rectangle} rectA - The first Rectangle object. + * @param {Phaser.Geom.Rectangle} rectB - The second Rectangle object. + * @param {Phaser.Geom.Rectangle} [output] - Optional Rectangle object. If given, the intersection data will be loaded into it (in case of no intersection, it will be left unchanged). Otherwise, new Rectangle object will be created and returned with either intersection data or empty (all values set to zero), if there is no intersection. + * + * @return {Phaser.Geom.Rectangle} A rectangle object with intersection data. + */ +var GetRectangleIntersection = function (rectA, rectB, output) { + if (output === undefined) { output = new Rectangle(); } + + if (RectangleToRectangle(rectA, rectB)) { + output.x = Math.max(rectA.x, rectB.x); + output.y = Math.max(rectA.y, rectB.y); + output.width = Math.min(rectA.right, rectB.right) - output.x; + output.height = Math.min(rectA.bottom, rectB.bottom) - output.y; + } + + return output; +}; + +export default GetRectangleIntersection; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetRectangleToRectangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetRectangleToRectangle.js new file mode 100644 index 000000000..e7af23bf1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetRectangleToRectangle.js @@ -0,0 +1,43 @@ +/** + * @author Florian Vazelle + * @author Geoffrey Glaive + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import GetLineToRectangle from './GetLineToRectangle.js'; +import RectangleToRectangle from './RectangleToRectangle.js'; + +/** + * Checks if two Rectangles intersect and returns the intersection points as a Point object array. + * + * A Rectangle intersects another Rectangle if any part of its bounds is within the other Rectangle's bounds. As such, the two Rectangles are considered "solid". A Rectangle with no width or no height will never intersect another Rectangle. + * + * @function Phaser.Geom.Intersects.GetRectangleToRectangle + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rectA - The first Rectangle to check for intersection. + * @param {Phaser.Geom.Rectangle} rectB - The second Rectangle to check for intersection. + * @param {array} [out] - An optional array in which to store the points of intersection. + * + * @return {array} An array with the points of intersection if objects intersect, otherwise an empty array. + */ +var GetRectangleToRectangle = function (rectA, rectB, out) { + if (out === undefined) { out = []; } + + if (RectangleToRectangle(rectA, rectB)) { + var lineA = rectA.getLineA(); + var lineB = rectA.getLineB(); + var lineC = rectA.getLineC(); + var lineD = rectA.getLineD(); + + GetLineToRectangle(lineA, rectB, out); + GetLineToRectangle(lineB, rectB, out); + GetLineToRectangle(lineC, rectB, out); + GetLineToRectangle(lineD, rectB, out); + } + + return out; +}; + +export default GetRectangleToRectangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetRectangleToTriangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetRectangleToTriangle.js new file mode 100644 index 000000000..e47c5740e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetRectangleToTriangle.js @@ -0,0 +1,40 @@ +/** + * @author Florian Vazelle + * @author Geoffrey Glaive + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import RectangleToTriangle from './RectangleToTriangle.js'; +import GetLineToRectangle from './GetLineToRectangle.js'; + +/** + * Checks for intersection between Rectangle shape and Triangle shape, + * and returns the intersection points as a Point object array. + * + * @function Phaser.Geom.Intersects.GetRectangleToTriangle + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rect - Rectangle object to test. + * @param {Phaser.Geom.Triangle} triangle - Triangle object to test. + * @param {array} [out] - An optional array in which to store the points of intersection. + * + * @return {array} An array with the points of intersection if objects intersect, otherwise an empty array. + */ +var GetRectangleToTriangle = function (rect, triangle, out) { + if (out === undefined) { out = []; } + + if (RectangleToTriangle(rect, triangle)) { + var lineA = triangle.getLineA(); + var lineB = triangle.getLineB(); + var lineC = triangle.getLineC(); + + GetLineToRectangle(lineA, rect, out); + GetLineToRectangle(lineB, rect, out); + GetLineToRectangle(lineC, rect, out); + } + + return out; +}; + +export default GetRectangleToTriangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetTriangleToCircle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetTriangleToCircle.js new file mode 100644 index 000000000..86f3fff15 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetTriangleToCircle.js @@ -0,0 +1,41 @@ +/** + * @author Florian Vazelle + * @author Geoffrey Glaive + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import GetLineToCircle from './GetLineToCircle.js'; +import TriangleToCircle from './TriangleToCircle.js'; + +/** + * Checks if a Triangle and a Circle intersect, and returns the intersection points as a Point object array. + * + * A Circle intersects a Triangle if its center is located within it or if any of the Triangle's sides intersect the Circle. As such, the Triangle and the Circle are considered "solid" for the intersection. + * + * @function Phaser.Geom.Intersects.GetTriangleToCircle + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to check for intersection. + * @param {Phaser.Geom.Circle} circle - The Circle to check for intersection. + * @param {array} [out] - An optional array in which to store the points of intersection. + * + * @return {array} An array with the points of intersection if objects intersect, otherwise an empty array. + */ +var GetTriangleToCircle = function (triangle, circle, out) { + if (out === undefined) { out = []; } + + if (TriangleToCircle(triangle, circle)) { + var lineA = triangle.getLineA(); + var lineB = triangle.getLineB(); + var lineC = triangle.getLineC(); + + GetLineToCircle(lineA, circle, out); + GetLineToCircle(lineB, circle, out); + GetLineToCircle(lineC, circle, out); + } + + return out; +}; + +export default GetTriangleToCircle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetTriangleToLine.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetTriangleToLine.js new file mode 100644 index 000000000..5f94047ef --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetTriangleToLine.js @@ -0,0 +1,50 @@ +/** + * @author Florian Vazelle + * @author Geoffrey Glaive + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; +import TriangleToLine from './TriangleToLine.js'; +import LineToLine from './LineToLine.js'; + +/** + * Checks if a Triangle and a Line intersect, and returns the intersection points as a Point object array. + * + * The Line intersects the Triangle if it starts inside of it, ends inside of it, or crosses any of the Triangle's sides. Thus, the Triangle is considered "solid". + * + * @function Phaser.Geom.Intersects.GetTriangleToLine + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to check with. + * @param {Phaser.Geom.Line} line - The Line to check with. + * @param {array} [out] - An optional array in which to store the points of intersection. + * + * @return {array} An array with the points of intersection if objects intersect, otherwise an empty array. + */ +var GetTriangleToLine = function (triangle, line, out) { + if (out === undefined) { out = []; } + + if (TriangleToLine(triangle, line)) { + var lineA = triangle.getLineA(); + var lineB = triangle.getLineB(); + var lineC = triangle.getLineC(); + + var output = [new Point(), new Point(), new Point()]; + + var result = [ + LineToLine(lineA, line, output[0]), + LineToLine(lineB, line, output[1]), + LineToLine(lineC, line, output[2]) + ]; + + for (var i = 0; i < 3; i++) { + if (result[i]) { out.push(output[i]); } + } + } + + return out; +}; + +export default GetTriangleToLine; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetTriangleToTriangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetTriangleToTriangle.js new file mode 100644 index 000000000..1a3858a96 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetTriangleToTriangle.js @@ -0,0 +1,41 @@ +/** + * @author Florian Vazelle + * @author Geoffrey Glaive + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import TriangleToTriangle from './TriangleToTriangle.js'; +import GetTriangleToLine from './GetTriangleToLine.js'; + +/** + * Checks if two Triangles intersect, and returns the intersection points as a Point object array. + * + * A Triangle intersects another Triangle if any pair of their lines intersects or if any point of one Triangle is within the other Triangle. Thus, the Triangles are considered "solid". + * + * @function Phaser.Geom.Intersects.GetTriangleToTriangle + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangleA - The first Triangle to check for intersection. + * @param {Phaser.Geom.Triangle} triangleB - The second Triangle to check for intersection. + * @param {array} [out] - An optional array in which to store the points of intersection. + * + * @return {array} An array with the points of intersection if objects intersect, otherwise an empty array. + */ +var GetTriangleToTriangle = function (triangleA, triangleB, out) { + if (out === undefined) { out = []; } + + if (TriangleToTriangle(triangleA, triangleB)) { + var lineA = triangleB.getLineA(); + var lineB = triangleB.getLineB(); + var lineC = triangleB.getLineC(); + + GetTriangleToLine(triangleA, lineA, out); + GetTriangleToLine(triangleA, lineB, out); + GetTriangleToLine(triangleA, lineC, out); + } + + return out; +}; + +export default GetTriangleToTriangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/LineToCircle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/LineToCircle.js new file mode 100644 index 000000000..884cdff92 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/LineToCircle.js @@ -0,0 +1,74 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Contains from '../circle/Contains.js'; +import Point from '../point/Point.js'; + +var tmp = new Point(); + +/** + * Checks for intersection between the line segment and circle. + * + * Based on code by [Matt DesLauriers](https://github.com/mattdesl/line-circle-collision/blob/master/LICENSE.md). + * + * @function Phaser.Geom.Intersects.LineToCircle + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The line segment to check. + * @param {Phaser.Geom.Circle} circle - The circle to check against the line. + * @param {(Phaser.Geom.Point|any)} [nearest] - An optional Point-like object. If given the closest point on the Line where the circle intersects will be stored in this object. + * + * @return {boolean} `true` if the two objects intersect, otherwise `false`. + */ +var LineToCircle = function (line, circle, nearest) { + if (nearest === undefined) { nearest = tmp; } + + if (Contains(circle, line.x1, line.y1)) { + nearest.x = line.x1; + nearest.y = line.y1; + + return true; + } + + if (Contains(circle, line.x2, line.y2)) { + nearest.x = line.x2; + nearest.y = line.y2; + + return true; + } + + var dx = line.x2 - line.x1; + var dy = line.y2 - line.y1; + + var lcx = circle.x - line.x1; + var lcy = circle.y - line.y1; + + // project lc onto d, resulting in vector p + var dLen2 = (dx * dx) + (dy * dy); + var px = dx; + var py = dy; + + if (dLen2 > 0) { + var dp = ((lcx * dx) + (lcy * dy)) / dLen2; + + px *= dp; + py *= dp; + } + + nearest.x = line.x1 + px; + nearest.y = line.y1 + py; + + // len2 of p + var pLen2 = (px * px) + (py * py); + + return ( + pLen2 <= dLen2 && + ((px * dx) + (py * dy)) >= 0 && + Contains(circle, nearest.x, nearest.y) + ); +}; + +export default LineToCircle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/LineToLine.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/LineToLine.js new file mode 100644 index 000000000..1ed968a70 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/LineToLine.js @@ -0,0 +1,67 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point'; + +// This is based off an explanation and expanded math presented by Paul Bourke: +// See http:'local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ + +/** + * Checks if two Lines intersect. If the Lines are identical, they will be treated as parallel and thus non-intersecting. + * + * @function Phaser.Geom.Intersects.LineToLine + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line1 - The first Line to check. + * @param {Phaser.Geom.Line} line2 - The second Line to check. + * @param {Phaser.Geom.Point} [out] - A Point in which to optionally store the point of intersection. + * + * @return {boolean} `true` if the two Lines intersect, and the `out` object will be populated, if given. Otherwise, `false`. + */ +var LineToLine = function (line1, line2, out) { + if (out === undefined) { out = new Point(); } + + var x1 = line1.x1; + var y1 = line1.y1; + var x2 = line1.x2; + var y2 = line1.y2; + + var x3 = line2.x1; + var y3 = line2.y1; + var x4 = line2.x2; + var y4 = line2.y2; + + var numA = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); + var numB = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3); + var deNom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + + // Make sure there is not a division by zero - this also indicates that the lines are parallel. + // If numA and numB were both equal to zero the lines would be on top of each other (coincidental). + // This check is not done because it is not necessary for this implementation (the parallel check accounts for this). + + if (deNom === 0) { + return false; + } + + // Calculate the intermediate fractional point that the lines potentially intersect. + + var uA = numA / deNom; + var uB = numB / deNom; + + // The fractional point will be between 0 and 1 inclusive if the lines intersect. + // If the fractional calculation is larger than 1 or smaller than 0 the lines would need to be longer to intersect. + + if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) { + out.x = x1 + (uA * (x2 - x1)); + out.y = y1 + (uA * (y2 - y1)); + + return true; + } + + return false; +}; + +export default LineToLine; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/LineToRectangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/LineToRectangle.js new file mode 100644 index 000000000..a5b6830fe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/LineToRectangle.js @@ -0,0 +1,95 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Checks for intersection between the Line and a Rectangle shape, or a rectangle-like + * object, with public `x`, `y`, `right` and `bottom` properties, such as a Sprite or Body. + * + * An intersection is considered valid if: + * + * The line starts within, or ends within, the Rectangle. + * The line segment intersects one of the 4 rectangle edges. + * + * The for the purposes of this function rectangles are considered 'solid'. + * + * @function Phaser.Geom.Intersects.LineToRectangle + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The Line to check for intersection. + * @param {(Phaser.Geom.Rectangle|object)} rect - The Rectangle to check for intersection. + * + * @return {boolean} `true` if the Line and the Rectangle intersect, `false` otherwise. + */ +var LineToRectangle = function (line, rect) +{ + var x1 = line.x1; + var y1 = line.y1; + + var x2 = line.x2; + var y2 = line.y2; + + var bx1 = rect.x; + var by1 = rect.y; + var bx2 = rect.right; + var by2 = rect.bottom; + + var t = 0; + + // If the start or end of the line is inside the rect then we assume + // collision, as rects are solid for our use-case. + + if ((x1 >= bx1 && x1 <= bx2 && y1 >= by1 && y1 <= by2) || + (x2 >= bx1 && x2 <= bx2 && y2 >= by1 && y2 <= by2)) + { + return true; + } + + if (x1 < bx1 && x2 >= bx1) + { + // Left edge + t = y1 + (y2 - y1) * (bx1 - x1) / (x2 - x1); + + if (t > by1 && t <= by2) + { + return true; + } + } + else if (x1 > bx2 && x2 <= bx2) + { + // Right edge + t = y1 + (y2 - y1) * (bx2 - x1) / (x2 - x1); + + if (t >= by1 && t <= by2) + { + return true; + } + } + + if (y1 < by1 && y2 >= by1) + { + // Top edge + t = x1 + (x2 - x1) * (by1 - y1) / (y2 - y1); + + if (t >= bx1 && t <= bx2) + { + return true; + } + } + else if (y1 > by2 && y2 <= by2) + { + // Bottom edge + t = x1 + (x2 - x1) * (by2 - y1) / (y2 - y1); + + if (t >= bx1 && t <= bx2) + { + return true; + } + } + + return false; +}; + +export default LineToRectangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/PointToLine.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/PointToLine.js new file mode 100644 index 000000000..ed2136755 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/PointToLine.js @@ -0,0 +1,64 @@ +/** + * @author Richard Davey + * @author Florian Mertens + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Checks if the a Point falls between the two end-points of a Line, based on the given line thickness. + * + * Assumes that the line end points are circular, not square. + * + * @function Phaser.Geom.Intersects.PointToLine + * @since 3.0.0 + * + * @param {(Phaser.Geom.Point|any)} point - The point, or point-like object to check. + * @param {Phaser.Geom.Line} line - The line segment to test for intersection on. + * @param {number} [lineThickness=1] - The line thickness. Assumes that the line end points are circular. + * + * @return {boolean} `true` if the Point falls on the Line, otherwise `false`. + */ +var PointToLine = function (point, line, lineThickness) +{ + if (lineThickness === undefined) { lineThickness = 1; } + + var x1 = line.x1; + var y1 = line.y1; + + var x2 = line.x2; + var y2 = line.y2; + + var px = point.x; + var py = point.y; + + var L2 = (((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1))); + + if (L2 === 0) + { + return false; + } + + var r = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1))) / L2; + + // Assume line thickness is circular + if (r < 0) + { + // Outside line1 + return (Math.sqrt(((x1 - px) * (x1 - px)) + ((y1 - py) * (y1 - py))) <= lineThickness); + } + else if ((r >= 0) && (r <= 1)) + { + // On the line segment + var s = (((y1 - py) * (x2 - x1)) - ((x1 - px) * (y2 - y1))) / L2; + + return (Math.abs(s) * Math.sqrt(L2) <= lineThickness); + } + else + { + // Outside line2 + return (Math.sqrt(((x2 - px) * (x2 - px)) + ((y2 - py) * (y2 - py))) <= lineThickness); + } +}; + +export default PointToLine; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/PointToLineSegment.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/PointToLineSegment.js new file mode 100644 index 000000000..258668a0d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/PointToLineSegment.js @@ -0,0 +1,33 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import PointToLine from './PointToLine.js'; + +/** + * Checks if a Point is located on the given line segment. + * + * @function Phaser.Geom.Intersects.PointToLineSegment + * @since 3.0.0 + * + * @param {Phaser.Geom.Point} point - The Point to check for intersection. + * @param {Phaser.Geom.Line} line - The line segment to check for intersection. + * + * @return {boolean} `true` if the Point is on the given line segment, otherwise `false`. + */ +var PointToLineSegment = function (point, line) { + if (!PointToLine(point, line)) { + return false; + } + + var xMin = Math.min(line.x1, line.x2); + var xMax = Math.max(line.x1, line.x2); + var yMin = Math.min(line.y1, line.y2); + var yMax = Math.max(line.y1, line.y2); + + return ((point.x >= xMin && point.x <= xMax) && (point.y >= yMin && point.y <= yMax)); +}; + +export default PointToLineSegment; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/RectangleToRectangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/RectangleToRectangle.js new file mode 100644 index 000000000..27e2c72d5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/RectangleToRectangle.js @@ -0,0 +1,30 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Checks if two Rectangles intersect. + * + * A Rectangle intersects another Rectangle if any part of its bounds is within the other Rectangle's bounds. As such, the two Rectangles are considered "solid". A Rectangle with no width or no height will never intersect another Rectangle. + * + * @function Phaser.Geom.Intersects.RectangleToRectangle + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rectA - The first Rectangle to check for intersection. + * @param {Phaser.Geom.Rectangle} rectB - The second Rectangle to check for intersection. + * + * @return {boolean} `true` if the two Rectangles intersect, otherwise `false`. + */ +var RectangleToRectangle = function (rectA, rectB) +{ + if (rectA.width <= 0 || rectA.height <= 0 || rectB.width <= 0 || rectB.height <= 0) + { + return false; + } + + return !(rectA.right < rectB.x || rectA.bottom < rectB.y || rectA.x > rectB.right || rectA.y > rectB.bottom); +}; + +export default RectangleToRectangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/RectangleToTriangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/RectangleToTriangle.js new file mode 100644 index 000000000..b752e225e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/RectangleToTriangle.js @@ -0,0 +1,79 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import LineToLine from './LineToLine.js'; +import Contains from '../rectangle/Contains'; +import ContainsArray from '../triangle/ContainsArray.js'; +import Decompose from '../rectangle/Decompose.js'; + +/** + * Checks for intersection between Rectangle shape and Triangle shape. + * + * @function Phaser.Geom.Intersects.RectangleToTriangle + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rect - Rectangle object to test. + * @param {Phaser.Geom.Triangle} triangle - Triangle object to test. + * + * @return {boolean} A value of `true` if objects intersect; otherwise `false`. + */ +var RectangleToTriangle = function (rect, triangle) { + // First the cheapest ones: + + if ( + triangle.left > rect.right || + triangle.right < rect.left || + triangle.top > rect.bottom || + triangle.bottom < rect.top) { + return false; + } + + var triA = triangle.getLineA(); + var triB = triangle.getLineB(); + var triC = triangle.getLineC(); + + // Are any of the triangle points within the rectangle? + + if (Contains(rect, triA.x1, triA.y1) || Contains(rect, triA.x2, triA.y2)) { + return true; + } + + if (Contains(rect, triB.x1, triB.y1) || Contains(rect, triB.x2, triB.y2)) { + return true; + } + + if (Contains(rect, triC.x1, triC.y1) || Contains(rect, triC.x2, triC.y2)) { + return true; + } + + // Cheap tests over, now to see if any of the lines intersect ... + + var rectA = rect.getLineA(); + var rectB = rect.getLineB(); + var rectC = rect.getLineC(); + var rectD = rect.getLineD(); + + if (LineToLine(triA, rectA) || LineToLine(triA, rectB) || LineToLine(triA, rectC) || LineToLine(triA, rectD)) { + return true; + } + + if (LineToLine(triB, rectA) || LineToLine(triB, rectB) || LineToLine(triB, rectC) || LineToLine(triB, rectD)) { + return true; + } + + if (LineToLine(triC, rectA) || LineToLine(triC, rectB) || LineToLine(triC, rectC) || LineToLine(triC, rectD)) { + return true; + } + + // None of the lines intersect, so are any rectangle points within the triangle? + + var points = Decompose(rect); + var within = ContainsArray(triangle, points, true); + + return (within.length > 0); +}; + +export default RectangleToTriangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/RectangleToValues.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/RectangleToValues.js new file mode 100644 index 000000000..671e878bb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/RectangleToValues.js @@ -0,0 +1,34 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Check if rectangle intersects with values. + * + * @function Phaser.Geom.Intersects.RectangleToValues + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rect - The rectangle object + * @param {number} left - The x coordinate of the left of the Rectangle. + * @param {number} right - The x coordinate of the right of the Rectangle. + * @param {number} top - The y coordinate of the top of the Rectangle. + * @param {number} bottom - The y coordinate of the bottom of the Rectangle. + * @param {number} [tolerance=0] - Tolerance allowed in the calculation, expressed in pixels. + * + * @return {boolean} Returns true if there is an intersection. + */ +var RectangleToValues = function (rect, left, right, top, bottom, tolerance) +{ + if (tolerance === undefined) { tolerance = 0; } + + return !( + left > rect.right + tolerance || + right < rect.left - tolerance || + top > rect.bottom + tolerance || + bottom < rect.top - tolerance + ); +}; + +export default RectangleToValues; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/TriangleToCircle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/TriangleToCircle.js new file mode 100644 index 000000000..aa598dd3e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/TriangleToCircle.js @@ -0,0 +1,53 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import LineToCircle from './LineToCircle.js'; +import Contains from '../triangle/Contains.js'; + +/** + * Checks if a Triangle and a Circle intersect. + * + * A Circle intersects a Triangle if its center is located within it or if any of the Triangle's sides intersect the Circle. As such, the Triangle and the Circle are considered "solid" for the intersection. + * + * @function Phaser.Geom.Intersects.TriangleToCircle + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to check for intersection. + * @param {Phaser.Geom.Circle} circle - The Circle to check for intersection. + * + * @return {boolean} `true` if the Triangle and the `Circle` intersect, otherwise `false`. + */ +var TriangleToCircle = function (triangle, circle) { + // First the cheapest ones: + + if ( + triangle.left > circle.right || + triangle.right < circle.left || + triangle.top > circle.bottom || + triangle.bottom < circle.top) { + return false; + } + + if (Contains(triangle, circle.x, circle.y)) { + return true; + } + + if (LineToCircle(triangle.getLineA(), circle)) { + return true; + } + + if (LineToCircle(triangle.getLineB(), circle)) { + return true; + } + + if (LineToCircle(triangle.getLineC(), circle)) { + return true; + } + + return false; +}; + +export default TriangleToCircle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/TriangleToLine.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/TriangleToLine.js new file mode 100644 index 000000000..c06fcf1d4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/TriangleToLine.js @@ -0,0 +1,45 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Contains from '../triangle/Contains.js'; +import LineToLine from './LineToLine.js'; + +/** + * Checks if a Triangle and a Line intersect. + * + * The Line intersects the Triangle if it starts inside of it, ends inside of it, or crosses any of the Triangle's sides. Thus, the Triangle is considered "solid". + * + * @function Phaser.Geom.Intersects.TriangleToLine + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to check with. + * @param {Phaser.Geom.Line} line - The Line to check with. + * + * @return {boolean} `true` if the Triangle and the Line intersect, otherwise `false`. + */ +var TriangleToLine = function (triangle, line) { + // If the Triangle contains either the start or end point of the line, it intersects + if (Contains(triangle, line.getPointA()) || Contains(triangle, line.getPointB())) { + return true; + } + + // Now check the line against each line of the Triangle + if (LineToLine(triangle.getLineA(), line)) { + return true; + } + + if (LineToLine(triangle.getLineB(), line)) { + return true; + } + + if (LineToLine(triangle.getLineC(), line)) { + return true; + } + + return false; +}; + +export default TriangleToLine; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/TriangleToTriangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/TriangleToTriangle.js new file mode 100644 index 000000000..321619efc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/TriangleToTriangle.js @@ -0,0 +1,77 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import ContainsArray from '../triangle/ContainsArray.js'; +import Decompose from '../triangle/Decompose.js'; +import LineToLine from './LineToLine.js'; + +/** + * Checks if two Triangles intersect. + * + * A Triangle intersects another Triangle if any pair of their lines intersects or if any point of one Triangle is within the other Triangle. Thus, the Triangles are considered "solid". + * + * @function Phaser.Geom.Intersects.TriangleToTriangle + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangleA - The first Triangle to check for intersection. + * @param {Phaser.Geom.Triangle} triangleB - The second Triangle to check for intersection. + * + * @return {boolean} `true` if the Triangles intersect, otherwise `false`. + */ +var TriangleToTriangle = function (triangleA, triangleB) { + // First the cheapest ones: + + if ( + triangleA.left > triangleB.right || + triangleA.right < triangleB.left || + triangleA.top > triangleB.bottom || + triangleA.bottom < triangleB.top) { + return false; + } + + var lineAA = triangleA.getLineA(); + var lineAB = triangleA.getLineB(); + var lineAC = triangleA.getLineC(); + + var lineBA = triangleB.getLineA(); + var lineBB = triangleB.getLineB(); + var lineBC = triangleB.getLineC(); + + // Now check the lines against each line of TriangleB + if (LineToLine(lineAA, lineBA) || LineToLine(lineAA, lineBB) || LineToLine(lineAA, lineBC)) { + return true; + } + + if (LineToLine(lineAB, lineBA) || LineToLine(lineAB, lineBB) || LineToLine(lineAB, lineBC)) { + return true; + } + + if (LineToLine(lineAC, lineBA) || LineToLine(lineAC, lineBB) || LineToLine(lineAC, lineBC)) { + return true; + } + + // Nope, so check to see if any of the points of triangleA are within triangleB + + var points = Decompose(triangleA); + var within = ContainsArray(triangleB, points, true); + + if (within.length > 0) { + return true; + } + + // Finally check to see if any of the points of triangleB are within triangleA + + points = Decompose(triangleB); + within = ContainsArray(triangleA, points, true); + + if (within.length > 0) { + return true; + } + + return false; +}; + +export default TriangleToTriangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/index.js new file mode 100644 index 000000000..a651627f1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/index.js @@ -0,0 +1,61 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * @namespace Phaser.Geom.Intersects + */ + +import CircleToCircle from './CircleToCircle.js'; +import CircleToRectangle from './CircleToRectangle.js'; +import GetCircleToCircle from './GetCircleToCircle.js'; +import GetCircleToRectangle from './GetCircleToRectangle.js'; +import GetLineToCircle from './GetLineToCircle.js'; +import GetLineToRectangle from './GetLineToRectangle.js'; +import GetRectangleIntersection from './GetRectangleIntersection.js'; +import GetRectangleToRectangle from './GetRectangleToRectangle.js'; +import GetRectangleToTriangle from './GetRectangleToTriangle.js'; +import GetTriangleToCircle from './GetTriangleToCircle.js'; +import GetTriangleToLine from './GetTriangleToLine.js'; +import GetTriangleToTriangle from './GetTriangleToTriangle.js'; +import LineToCircle from './LineToCircle.js'; +import LineToLine from './LineToLine.js'; +import LineToRectangle from './LineToRectangle.js'; +import PointToLine from './PointToLine.js'; +import PointToLineSegment from './PointToLineSegment.js'; +import RectangleToRectangle from './RectangleToRectangle.js'; +import RectangleToTriangle from './RectangleToTriangle.js'; +import RectangleToValues from './RectangleToValues.js'; +import TriangleToCircle from './TriangleToCircle.js'; +import TriangleToLine from './TriangleToLine.js'; +import TriangleToTriangle from './TriangleToTriangle.js'; + +export default { + + CircleToCircle: CircleToCircle, + CircleToRectangle: CircleToRectangle, + GetCircleToCircle: GetCircleToCircle, + GetCircleToRectangle: GetCircleToRectangle, + GetLineToCircle: GetLineToCircle, + GetLineToRectangle: GetLineToRectangle, + GetRectangleIntersection: GetRectangleIntersection, + GetRectangleToRectangle: GetRectangleToRectangle, + GetRectangleToTriangle: GetRectangleToTriangle, + GetTriangleToCircle: GetTriangleToCircle, + GetTriangleToLine: GetTriangleToLine, + GetTriangleToTriangle: GetTriangleToTriangle, + LineToCircle: LineToCircle, + LineToLine: LineToLine, + LineToRectangle: LineToRectangle, + PointToLine: PointToLine, + PointToLineSegment: PointToLineSegment, + RectangleToRectangle: RectangleToRectangle, + RectangleToTriangle: RectangleToTriangle, + RectangleToValues: RectangleToValues, + TriangleToCircle: TriangleToCircle, + TriangleToLine: TriangleToLine, + TriangleToTriangle: TriangleToTriangle + +}; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Angle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Angle.js new file mode 100644 index 000000000..f5f8de592 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Angle.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculate the angle of the line in radians. + * + * @function Phaser.Geom.Line.Angle + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The line to calculate the angle of. + * + * @return {number} The angle of the line, in radians. + */ +var Angle = function (line) +{ + return Math.atan2(line.y2 - line.y1, line.x2 - line.x1); +}; + +export default Angle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/BresenhamPoints.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/BresenhamPoints.js new file mode 100644 index 000000000..23aa7e89e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/BresenhamPoints.js @@ -0,0 +1,68 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Using Bresenham's line algorithm this will return an array of all coordinates on this line. + * + * The `start` and `end` points are rounded before this runs as the algorithm works on integers. + * + * @function Phaser.Geom.Line.BresenhamPoints + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The line. + * @param {integer} [stepRate=1] - The optional step rate for the points on the line. + * @param {array} [results] - An optional array to push the resulting coordinates into. + * + * @return {object[]} The array of coordinates on the line. + */ +var BresenhamPoints = function (line, stepRate, results) +{ + if (stepRate === undefined) { stepRate = 1; } + if (results === undefined) { results = []; } + + var x1 = Math.round(line.x1); + var y1 = Math.round(line.y1); + var x2 = Math.round(line.x2); + var y2 = Math.round(line.y2); + + var dx = Math.abs(x2 - x1); + var dy = Math.abs(y2 - y1); + var sx = (x1 < x2) ? 1 : -1; + var sy = (y1 < y2) ? 1 : -1; + var err = dx - dy; + + results.push({ x: x1, y: y1 }); + + var i = 1; + + while (!((x1 === x2) && (y1 === y2))) + { + var e2 = err << 1; + + if (e2 > -dy) + { + err -= dy; + x1 += sx; + } + + if (e2 < dx) + { + err += dx; + y1 += sy; + } + + if (i % stepRate === 0) + { + results.push({ x: x1, y: y1 }); + } + + i++; + } + + return results; +}; + +export default BresenhamPoints; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/CenterOn.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/CenterOn.js new file mode 100644 index 000000000..af6036e7e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/CenterOn.js @@ -0,0 +1,34 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + + +/** + * Center a line on the given coordinates. + * + * @function Phaser.Geom.Line.CenterOn + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The line to center. + * @param {number} x - The horizontal coordinate to center the line on. + * @param {number} y - The vertical coordinate to center the line on. + * + * @return {Phaser.Geom.Line} The centered line. + */ +var CenterOn = function (line, x, y) +{ + var tx = x - ((line.x1 + line.x2) / 2); + var ty = y - ((line.y1 + line.y2) / 2); + + line.x1 += tx; + line.y1 += ty; + + line.x2 += tx; + line.y2 += ty; + + return line; +}; + +export default CenterOn; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Clone.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Clone.js new file mode 100644 index 000000000..0c82de8f5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Clone.js @@ -0,0 +1,23 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Line from './Line.js'; + +/** + * Clone the given line. + * + * @function Phaser.Geom.Line.Clone + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} source - The source line to clone. + * + * @return {Phaser.Geom.Line} The cloned line. + */ +var Clone = function (source) { + return new Line(source.x1, source.y1, source.x2, source.y2); +}; + +export default Clone; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/CopyFrom.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/CopyFrom.js new file mode 100644 index 000000000..6bfcf0ade --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/CopyFrom.js @@ -0,0 +1,25 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Copy the values of one line to a destination line. + * + * @function Phaser.Geom.Line.CopyFrom + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [dest,$return] + * + * @param {Phaser.Geom.Line} source - The source line to copy the values from. + * @param {Phaser.Geom.Line} dest - The destination line to copy the values to. + * + * @return {Phaser.Geom.Line} The destination line. + */ +var CopyFrom = function (source, dest) +{ + return dest.setTo(source.x1, source.y1, source.x2, source.y2); +}; + +export default CopyFrom; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Equals.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Equals.js new file mode 100644 index 000000000..ea69dddf8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Equals.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Compare two lines for strict equality. + * + * @function Phaser.Geom.Line.Equals + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The first line to compare. + * @param {Phaser.Geom.Line} toCompare - The second line to compare. + * + * @return {boolean} Whether the two lines are equal. + */ +var Equals = function (line, toCompare) +{ + return ( + line.x1 === toCompare.x1 && + line.y1 === toCompare.y1 && + line.x2 === toCompare.x2 && + line.y2 === toCompare.y2 + ); +}; + +export default Equals; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Extend.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Extend.js new file mode 100644 index 000000000..677d3c104 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Extend.js @@ -0,0 +1,49 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Length from './Length.js'; + +/** + * Extends the start and end points of a Line by the given amounts. + * + * The amounts can be positive or negative. Positive points will increase the length of the line, + * while negative ones will decrease it. + * + * If no `right` value is provided it will extend the length of the line equally in both directions. + * + * Pass a value of zero to leave the start or end point unchanged. + * + * @function Phaser.Geom.Line.Extend + * @since 3.16.0 + * + * @param {Phaser.Geom.Line} line - The line instance to extend. + * @param {number} left - The amount to extend the start of the line by. + * @param {number} [right] - The amount to extend the end of the line by. If not given it will be set to the `left` value. + * + * @return {Phaser.Geom.Line} The modified Line instance. + */ +var Extend = function (line, left, right) { + if (right === undefined) { right = left; } + + var length = Length(line); + + var slopX = line.x2 - line.x1; + var slopY = line.y2 - line.y1; + + if (left) { + line.x1 = line.x1 - slopX / length * left; + line.y1 = line.y1 - slopY / length * left; + } + + if (right) { + line.x2 = line.x2 + slopX / length * right; + line.y2 = line.y2 + slopY / length * right; + } + + return line; +}; + +export default Extend; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetMidPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetMidPoint.js new file mode 100644 index 000000000..b04358558 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetMidPoint.js @@ -0,0 +1,31 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +/** + * Get the midpoint of the given line. + * + * @function Phaser.Geom.Line.GetMidPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Line} line - The line to get the midpoint of. + * @param {(Phaser.Geom.Point|object)} [out] - An optional point object to store the midpoint in. + * + * @return {(Phaser.Geom.Point|object)} The midpoint of the Line. + */ +var GetMidPoint = function (line, out) { + if (out === undefined) { out = new Point(); } + + out.x = (line.x1 + line.x2) / 2; + out.y = (line.y1 + line.y2) / 2; + + return out; +}; + +export default GetMidPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetNearestPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetNearestPoint.js new file mode 100644 index 000000000..bafabfe3a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetNearestPoint.js @@ -0,0 +1,47 @@ +/** + * @author Richard Davey + * @author Florian Mertens + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +/** + * Get the nearest point on a line perpendicular to the given point. + * + * @function Phaser.Geom.Line.GetNearestPoint + * @since 3.16.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Line} line - The line to get the nearest point on. + * @param {(Phaser.Geom.Point|object)} point - The point to get the nearest point to. + * @param {(Phaser.Geom.Point|object)} [out] - An optional point, or point-like object, to store the coordinates of the nearest point on the line. + * + * @return {(Phaser.Geom.Point|object)} The nearest point on the line. + */ +var GetNearestPoint = function (line, point, out) { + if (out === undefined) { out = new Point(); } + + var x1 = line.x1; + var y1 = line.y1; + + var x2 = line.x2; + var y2 = line.y2; + + var L2 = (((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1))); + + if (L2 === 0) { + return out; + } + + var r = (((point.x - x1) * (x2 - x1)) + ((point.y - y1) * (y2 - y1))) / L2; + + out.x = x1 + (r * (x2 - x1)); + out.y = y1 + (r * (y2 - y1)); + + return out; +}; + +export default GetNearestPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetNormal.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetNormal.js new file mode 100644 index 000000000..4d32ce2c6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetNormal.js @@ -0,0 +1,37 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import MATH_CONST from '../../math/const.js'; +import Angle from './Angle.js'; +import Point from '../point/Point.js'; + +/** + * Calculate the normal of the given line. + * + * The normal of a line is a vector that points perpendicular from it. + * + * @function Phaser.Geom.Line.GetNormal + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Line} line - The line to calculate the normal of. + * @param {(Phaser.Geom.Point|object)} [out] - An optional point object to store the normal in. + * + * @return {(Phaser.Geom.Point|object)} The normal of the Line. + */ +var GetNormal = function (line, out) { + if (out === undefined) { out = new Point(); } + + var a = Angle(line) - MATH_CONST.TAU; + + out.x = Math.cos(a); + out.y = Math.sin(a); + + return out; +}; + +export default GetNormal; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetPoint.js new file mode 100644 index 000000000..a58655b54 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetPoint.js @@ -0,0 +1,32 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +/** + * Get a point on a line that's a given percentage along its length. + * + * @function Phaser.Geom.Line.GetPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Line} line - The line. + * @param {number} position - A value between 0 and 1, where 0 is the start, 0.5 is the middle and 1 is the end of the line. + * @param {(Phaser.Geom.Point|object)} [out] - An optional point, or point-like object, to store the coordinates of the point on the line. + * + * @return {(Phaser.Geom.Point|object)} The point on the line. + */ +var GetPoint = function (line, position, out) { + if (out === undefined) { out = new Point(); } + + out.x = line.x1 + (line.x2 - line.x1) * position; + out.y = line.y1 + (line.y2 - line.y1) * position; + + return out; +}; + +export default GetPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetPoints.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetPoints.js new file mode 100644 index 000000000..182291cb5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetPoints.js @@ -0,0 +1,56 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Length from './Length.js'; +import Point from '../point/Point.js'; + +/** + * Get a number of points along a line's length. + * + * Provide a `quantity` to get an exact number of points along the line. + * + * Provide a `stepRate` to ensure a specific distance between each point on the line. Set `quantity` to `0` when + * providing a `stepRate`. + * + * @function Phaser.Geom.Line.GetPoints + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point[]} O - [out,$return] + * + * @param {Phaser.Geom.Line} line - The line. + * @param {integer} quantity - The number of points to place on the line. Set to `0` to use `stepRate` instead. + * @param {number} [stepRate] - The distance between each point on the line. When set, `quantity` is implied and should be set to `0`. + * @param {(array|Phaser.Geom.Point[])} [out] - An optional array of Points, or point-like objects, to store the coordinates of the points on the line. + * + * @return {(array|Phaser.Geom.Point[])} An array of Points, or point-like objects, containing the coordinates of the points on the line. + */ +var GetPoints = function (line, quantity, stepRate, out) { + if (out === undefined) { out = []; } + + // If quantity is a falsey value (false, null, 0, undefined, etc) then we calculate it based on the stepRate instead. + if (!quantity) { + quantity = Length(line) / stepRate; + } + + var x1 = line.x1; + var y1 = line.y1; + + var x2 = line.x2; + var y2 = line.y2; + + for (var i = 0; i < quantity; i++) { + var position = i / quantity; + + var x = x1 + (x2 - x1) * position; + var y = y1 + (y2 - y1) * position; + + out.push(new Point(x, y)); + } + + return out; +}; + +export default GetPoints; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetShortestDistance.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetShortestDistance.js new file mode 100644 index 000000000..dfe8a5c16 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetShortestDistance.js @@ -0,0 +1,41 @@ +/** + * @author Richard Davey + * @author Florian Mertens + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Get the shortest distance from a Line to the given Point. + * + * @function Phaser.Geom.Line.GetShortestDistance + * @since 3.16.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Line} line - The line to get the distance from. + * @param {(Phaser.Geom.Point|object)} point - The point to get the shortest distance to. + * + * @return {number} The shortest distance from the line to the point. + */ +var GetShortestDistance = function (line, point) +{ + var x1 = line.x1; + var y1 = line.y1; + + var x2 = line.x2; + var y2 = line.y2; + + var L2 = (((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1))); + + if (L2 === 0) + { + return false; + } + + var s = (((y1 - point.y) * (x2 - x1)) - ((x1 - point.x) * (y2 - y1))) / L2; + + return Math.abs(s) * Math.sqrt(L2); +}; + +export default GetShortestDistance; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Height.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Height.js new file mode 100644 index 000000000..79ee0ee67 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Height.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculate the height of the given line. + * + * @function Phaser.Geom.Line.Height + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The line to calculate the height of. + * + * @return {number} The height of the line. + */ +var Height = function (line) +{ + return Math.abs(line.y1 - line.y2); +}; + +export default Height; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Length.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Length.js new file mode 100644 index 000000000..d20412fab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Length.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculate the length of the given line. + * + * @function Phaser.Geom.Line.Length + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The line to calculate the length of. + * + * @return {number} The length of the line. + */ +var Length = function (line) +{ + return Math.sqrt((line.x2 - line.x1) * (line.x2 - line.x1) + (line.y2 - line.y1) * (line.y2 - line.y1)); +}; + +export default Length; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Line.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Line.d.ts new file mode 100644 index 000000000..88e85f3cc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Line.d.ts @@ -0,0 +1,360 @@ +import Point from '../point/Point'; +import { Vector2Like, Vector2 } from '../types'; + +/** + * Defines a Line segment, a part of a line between two endpoints. + */ +declare class Line { + /** + * + * @param x1 The x coordinate of the lines starting point. Default 0. + * @param y1 The y coordinate of the lines starting point. Default 0. + * @param x2 The x coordinate of the lines ending point. Default 0. + * @param y2 The y coordinate of the lines ending point. Default 0. + */ + constructor(x1?: number, y1?: number, x2?: number, y2?: number); + + /** + * Calculate the angle of the line in radians. + * @param line The line to calculate the angle of. + */ + static Angle(line: Line): number; + + /** + * Using Bresenham's line algorithm this will return an array of all coordinates on this line. + * + * The `start` and `end` points are rounded before this runs as the algorithm works on integers. + * @param line The line. + * @param stepRate The optional step rate for the points on the line. Default 1. + * @param results An optional array to push the resulting coordinates into. + */ + static BresenhamPoints(line: Line, stepRate?: number, results?: Vector2Like[]): Vector2Like[]; + + /** + * Center a line on the given coordinates. + * @param line The line to center. + * @param x The horizontal coordinate to center the line on. + * @param y The vertical coordinate to center the line on. + */ + static CenterOn(line: Line, x: number, y: number): Line; + + /** + * Clone the given line. + * @param source The source line to clone. + */ + static Clone(source: Line): Line; + + /** + * Copy the values of one line to a destination line. + * @param source The source line to copy the values from. + * @param dest The destination line to copy the values to. + */ + static CopyFrom(source: Line, dest: O): O; + + /** + * Compare two lines for strict equality. + * @param line The first line to compare. + * @param toCompare The second line to compare. + */ + static Equals(line: Line, toCompare: Line): boolean; + + /** + * Extends the start and end points of a Line by the given amounts. + * + * The amounts can be positive or negative. Positive points will increase the length of the line, + * while negative ones will decrease it. + * + * If no `right` value is provided it will extend the length of the line equally in both directions. + * + * Pass a value of zero to leave the start or end point unchanged. + * @param line The line instance to extend. + * @param left The amount to extend the start of the line by. + * @param right The amount to extend the end of the line by. If not given it will be set to the `left` value. + */ + static Extend(line: Line, left: number, right?: number): Line; + + /** + * Returns an array of `quantity` Points where each point is taken from the given Line, + * spaced out according to the ease function specified. + * + * ```javascript + * const line = new Line(100, 300, 700, 300); + * const points = Line.GetEasedPoints(line, 'sine.out', 32) + * ``` + * + * In the above example, the `points` array will contain 32 points spread-out across + * the length of `line`, where the position of each point is determined by the `Sine.out` + * ease function. + * + * You can optionally provide a collinear threshold. In this case, the resulting points + * are checked against each other, and if they are `< collinearThreshold` distance apart, + * they are dropped from the results. This can help avoid lots of clustered points at + * far ends of the line with tightly-packed eases such as Quartic. Leave the value set + * to zero to skip this check. + * + * Note that if you provide a collinear threshold, the resulting array may not always + * contain `quantity` points. + * @param line The Line object. + * @param ease The ease to use. This can be either a string from the EaseMap, or a custom function. + * @param quantity The number of points to return. Note that if you provide a `collinearThreshold`, the resulting array may not always contain this number of points. + * @param collinearThreshold An optional threshold. The final array is reduced so that each point is spaced out at least this distance apart. This helps reduce clustering in noisey eases. Default 0. + * @param easeParams An optional array of ease parameters to go with the ease. + */ + static GetEasedPoints(line: Line, ease: string | Function, quantity: number, collinearThreshold?: number, easeParams?: number[]): O; + + /** + * Get the midpoint of the given line. + * @param line The line to get the midpoint of. + * @param out An optional point object to store the midpoint in. + */ + static GetMidPoint(line: Line, out?: O): O; + + /** + * Get the nearest point on a line perpendicular to the given point. + * @param line The line to get the nearest point on. + * @param point The point to get the nearest point to. + * @param out An optional point, or point-like object, to store the coordinates of the nearest point on the line. + */ + static GetNearestPoint(line: Line, point: Point | object, out?: O): O; + + /** + * Calculate the normal of the given line. + * + * The normal of a line is a vector that points perpendicular from it. + * @param line The line to calculate the normal of. + * @param out An optional point object to store the normal in. + */ + static GetNormal(line: Line, out?: O): O; + + /** + * Get a point on a line that's a given percentage along its length. + * @param line The line. + * @param position A value between 0 and 1, where 0 is the start, 0.5 is the middle and 1 is the end of the line. + * @param out An optional point, or point-like object, to store the coordinates of the point on the line. + */ + static GetPoint(line: Line, position: number, out?: O): O; + + /** + * Get a number of points along a line's length. + * + * Provide a `quantity` to get an exact number of points along the line. + * + * Provide a `stepRate` to ensure a specific distance between each point on the line. Set `quantity` to `0` when + * providing a `stepRate`. + * @param line The line. + * @param quantity The number of points to place on the line. Set to `0` to use `stepRate` instead. + * @param stepRate The distance between each point on the line. When set, `quantity` is implied and should be set to `0`. + * @param out An optional array of Points, or point-like objects, to store the coordinates of the points on the line. + */ + static GetPoints(line: Line, quantity: number, stepRate?: number, out?: O): O; + + /** + * Get the shortest distance from a Line to the given Point. + * @param line The line to get the distance from. + * @param point The point to get the shortest distance to. + */ + static GetShortestDistance(line: Line, point: Point | object): O; + + /** + * Calculate the height of the given line. + * @param line The line to calculate the height of. + */ + static Height(line: Line): number; + + /** + * Calculate the length of the given line. + * @param line The line to calculate the length of. + */ + static Length(line: Line): number; + + /** + * The geometry constant type of this object: `GEOM_CONST.LINE`. + * Used for fast type comparisons. + */ + readonly type: number; + + /** + * The x coordinate of the lines starting point. + */ + x1: number; + + /** + * The y coordinate of the lines starting point. + */ + y1: number; + + /** + * The x coordinate of the lines ending point. + */ + x2: number; + + /** + * The y coordinate of the lines ending point. + */ + y2: number; + + /** + * Get a point on a line that's a given percentage along its length. + * @param position A value between 0 and 1, where 0 is the start, 0.5 is the middle and 1 is the end of the line. + * @param output An optional point, or point-like object, to store the coordinates of the point on the line. + */ + getPoint(position: number, output?: O): O; + + /** + * Get a number of points along a line's length. + * + * Provide a `quantity` to get an exact number of points along the line. + * + * Provide a `stepRate` to ensure a specific distance between each point on the line. Set `quantity` to `0` when + * providing a `stepRate`. + * @param quantity The number of points to place on the line. Set to `0` to use `stepRate` instead. + * @param stepRate The distance between each point on the line. When set, `quantity` is implied and should be set to `0`. + * @param output An optional array of Points, or point-like objects, to store the coordinates of the points on the line. + */ + getPoints(quantity: number, stepRate?: number, output?: O): O; + + /** + * Get a random Point on the Line. + * @param point An instance of a Point to be modified. + */ + getRandomPoint(point?: O): O; + + /** + * Set new coordinates for the line endpoints. + * @param x1 The x coordinate of the lines starting point. Default 0. + * @param y1 The y coordinate of the lines starting point. Default 0. + * @param x2 The x coordinate of the lines ending point. Default 0. + * @param y2 The y coordinate of the lines ending point. Default 0. + */ + setTo(x1?: number, y1?: number, x2?: number, y2?: number): this; + + /** + * Returns a Vector2 object that corresponds to the start of this Line. + * @param vec2 A Vector2 object to set the results in. If `undefined` a new Vector2 will be created. + */ + getPointA(vec2?: Vector2): Vector2; + + /** + * Returns a Vector2 object that corresponds to the end of this Line. + * @param vec2 A Vector2 object to set the results in. If `undefined` a new Vector2 will be created. + */ + getPointB(vec2?: Vector2): Vector2; + + /** + * The left position of the Line. + */ + left: number; + + /** + * The right position of the Line. + */ + right: number; + + /** + * The top position of the Line. + */ + top: number; + + /** + * The bottom position of the Line. + */ + bottom: number; + + /** + * Get the angle of the normal of the given line in radians. + * @param line The line to calculate the angle of the normal of. + */ + static NormalAngle(line: Line): number; + + /** + * Returns the x component of the normal vector of the given line. + * @param line The Line object to get the normal value from. + */ + static NormalX(line: Line): number; + + /** + * The Y value of the normal of the given line. + * The normal of a line is a vector that points perpendicular from it. + * @param line The line to calculate the normal of. + */ + static NormalY(line: Line): number; + + /** + * Offset a line by the given amount. + * @param line The line to offset. + * @param x The horizontal offset to add to the line. + * @param y The vertical offset to add to the line. + */ + static Offset(line: O, x: number, y: number): O; + + /** + * Calculate the perpendicular slope of the given line. + * @param line The line to calculate the perpendicular slope of. + */ + static PerpSlope(line: Line): number; + + /** + * Returns a random point on a given Line. + * @param line The Line to calculate the random Point on. + * @param out An instance of a Point to be modified. + */ + static Random(line: Line, out?: O): O; + + /** + * Calculate the reflected angle between two lines. + * + * This is the outgoing angle based on the angle of Line 1 and the normalAngle of Line 2. + * @param lineA The first line. + * @param lineB The second line. + */ + static ReflectAngle(lineA: Line, lineB: Line): number; + + /** + * Rotate a line around its midpoint by the given angle in radians. + * @param line The line to rotate. + * @param angle The angle of rotation in radians. + */ + static Rotate(line: O, angle: number): O; + + /** + * Rotate a line around a point by the given angle in radians. + * @param line The line to rotate. + * @param point The point to rotate the line around. + * @param angle The angle of rotation in radians. + */ + static RotateAroundPoint(line: O, point: Point | object, angle: number): O; + + /** + * Rotate a line around the given coordinates by the given angle in radians. + * @param line The line to rotate. + * @param x The horizontal coordinate to rotate the line around. + * @param y The vertical coordinate to rotate the line around. + * @param angle The angle of rotation in radians. + */ + static RotateAroundXY(line: O, x: number, y: number, angle: number): O; + + /** + * Set a line to a given position, angle and length. + * @param line The line to set. + * @param x The horizontal start position of the line. + * @param y The vertical start position of the line. + * @param angle The angle of the line in radians. + * @param length The length of the line. + */ + static SetToAngle(line: O, x: number, y: number, angle: number, length: number): O; + + /** + * Calculate the slope of the given line. + * @param line The line to calculate the slope of. + */ + static Slope(line: Line): number; + + /** + * Calculate the width of the given line. + * @param line The line to calculate the width of. + */ + static Width(line: Line): number; + +} + +export default Line; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Line.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Line.js new file mode 100644 index 000000000..ed515d7ed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Line.js @@ -0,0 +1,296 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Class from '../../object/Class.js'; +import GetPoint from './GetPoint.js'; +import GetPoints from './GetPoints.js'; +import Random from './Random.js'; +import Vector2 from '../../math/Vector2.js'; + +/** + * @classdesc + * Defines a Line segment, a part of a line between two endpoints. + * + * @class Line + * @memberof Phaser.Geom + * @constructor + * @since 3.0.0 + * + * @param {number} [x1=0] - The x coordinate of the lines starting point. + * @param {number} [y1=0] - The y coordinate of the lines starting point. + * @param {number} [x2=0] - The x coordinate of the lines ending point. + * @param {number} [y2=0] - The y coordinate of the lines ending point. + */ +var Line = new Class({ + + initialize: + + function Line(x1, y1, x2, y2) { + if (x1 === undefined) { x1 = 0; } + if (y1 === undefined) { y1 = 0; } + if (x2 === undefined) { x2 = 0; } + if (y2 === undefined) { y2 = 0; } + + /** + * The x coordinate of the lines starting point. + * + * @name Phaser.Geom.Line#x1 + * @type {number} + * @since 3.0.0 + */ + this.x1 = x1; + + /** + * The y coordinate of the lines starting point. + * + * @name Phaser.Geom.Line#y1 + * @type {number} + * @since 3.0.0 + */ + this.y1 = y1; + + /** + * The x coordinate of the lines ending point. + * + * @name Phaser.Geom.Line#x2 + * @type {number} + * @since 3.0.0 + */ + this.x2 = x2; + + /** + * The y coordinate of the lines ending point. + * + * @name Phaser.Geom.Line#y2 + * @type {number} + * @since 3.0.0 + */ + this.y2 = y2; + }, + + /** + * Get a point on a line that's a given percentage along its length. + * + * @method Phaser.Geom.Line#getPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [output,$return] + * + * @param {number} position - A value between 0 and 1, where 0 is the start, 0.5 is the middle and 1 is the end of the line. + * @param {(Phaser.Geom.Point|object)} [output] - An optional point, or point-like object, to store the coordinates of the point on the line. + * + * @return {(Phaser.Geom.Point|object)} A Point, or point-like object, containing the coordinates of the point on the line. + */ + getPoint: function (position, output) { + return GetPoint(this, position, output); + }, + + /** + * Get a number of points along a line's length. + * + * Provide a `quantity` to get an exact number of points along the line. + * + * Provide a `stepRate` to ensure a specific distance between each point on the line. Set `quantity` to `0` when + * providing a `stepRate`. + * + * @method Phaser.Geom.Line#getPoints + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [output,$return] + * + * @param {integer} quantity - The number of points to place on the line. Set to `0` to use `stepRate` instead. + * @param {integer} [stepRate] - The distance between each point on the line. When set, `quantity` is implied and should be set to `0`. + * @param {(array|Phaser.Geom.Point[])} [output] - An optional array of Points, or point-like objects, to store the coordinates of the points on the line. + * + * @return {(array|Phaser.Geom.Point[])} An array of Points, or point-like objects, containing the coordinates of the points on the line. + */ + getPoints: function (quantity, stepRate, output) { + return GetPoints(this, quantity, stepRate, output); + }, + + /** + * Get a random Point on the Line. + * + * @method Phaser.Geom.Line#getRandomPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [point,$return] + * + * @param {(Phaser.Geom.Point|object)} [point] - An instance of a Point to be modified. + * + * @return {Phaser.Geom.Point} A random Point on the Line. + */ + getRandomPoint: function (point) { + return Random(this, point); + }, + + /** + * Set new coordinates for the line endpoints. + * + * @method Phaser.Geom.Line#setTo + * @since 3.0.0 + * + * @param {number} [x1=0] - The x coordinate of the lines starting point. + * @param {number} [y1=0] - The y coordinate of the lines starting point. + * @param {number} [x2=0] - The x coordinate of the lines ending point. + * @param {number} [y2=0] - The y coordinate of the lines ending point. + * + * @return {Phaser.Geom.Line} This Line object. + */ + setTo: function (x1, y1, x2, y2) { + if (x1 === undefined) { x1 = 0; } + if (y1 === undefined) { y1 = 0; } + if (x2 === undefined) { x2 = 0; } + if (y2 === undefined) { y2 = 0; } + + this.x1 = x1; + this.y1 = y1; + + this.x2 = x2; + this.y2 = y2; + + return this; + }, + + /** + * Returns a Vector2 object that corresponds to the start of this Line. + * + * @method Phaser.Geom.Line#getPointA + * @since 3.0.0 + * + * @generic {Phaser.Math.Vector2} O - [vec2,$return] + * + * @param {Phaser.Math.Vector2} [vec2] - A Vector2 object to set the results in. If `undefined` a new Vector2 will be created. + * + * @return {Phaser.Math.Vector2} A Vector2 object that corresponds to the start of this Line. + */ + getPointA: function (vec2) { + if (vec2 === undefined) { vec2 = new Vector2(); } + + vec2.set(this.x1, this.y1); + + return vec2; + }, + + /** + * Returns a Vector2 object that corresponds to the end of this Line. + * + * @method Phaser.Geom.Line#getPointB + * @since 3.0.0 + * + * @generic {Phaser.Math.Vector2} O - [vec2,$return] + * + * @param {Phaser.Math.Vector2} [vec2] - A Vector2 object to set the results in. If `undefined` a new Vector2 will be created. + * + * @return {Phaser.Math.Vector2} A Vector2 object that corresponds to the end of this Line. + */ + getPointB: function (vec2) { + if (vec2 === undefined) { vec2 = new Vector2(); } + + vec2.set(this.x2, this.y2); + + return vec2; + }, + + /** + * The left position of the Line. + * + * @name Phaser.Geom.Line#left + * @type {number} + * @since 3.0.0 + */ + left: { + + get: function () { + return Math.min(this.x1, this.x2); + }, + + set: function (value) { + if (this.x1 <= this.x2) { + this.x1 = value; + } + else { + this.x2 = value; + } + } + + }, + + /** + * The right position of the Line. + * + * @name Phaser.Geom.Line#right + * @type {number} + * @since 3.0.0 + */ + right: { + + get: function () { + return Math.max(this.x1, this.x2); + }, + + set: function (value) { + if (this.x1 > this.x2) { + this.x1 = value; + } + else { + this.x2 = value; + } + } + + }, + + /** + * The top position of the Line. + * + * @name Phaser.Geom.Line#top + * @type {number} + * @since 3.0.0 + */ + top: { + + get: function () { + return Math.min(this.y1, this.y2); + }, + + set: function (value) { + if (this.y1 <= this.y2) { + this.y1 = value; + } + else { + this.y2 = value; + } + } + + }, + + /** + * The bottom position of the Line. + * + * @name Phaser.Geom.Line#bottom + * @type {number} + * @since 3.0.0 + */ + bottom: { + + get: function () { + return Math.max(this.y1, this.y2); + }, + + set: function (value) { + if (this.y1 > this.y2) { + this.y1 = value; + } + else { + this.y2 = value; + } + } + + } + +}); + +export default Line; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/NormalAngle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/NormalAngle.js new file mode 100644 index 000000000..30e6552a2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/NormalAngle.js @@ -0,0 +1,27 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import MATH_CONST from '../../math/const.js'; +import Wrap from '../../math/Wrap.js'; +import Angle from './Angle.js'; + +/** + * Get the angle of the normal of the given line in radians. + * + * @function Phaser.Geom.Line.NormalAngle + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The line to calculate the angle of the normal of. + * + * @return {number} The angle of the normal of the line in radians. + */ +var NormalAngle = function (line) { + var angle = Angle(line) - MATH_CONST.TAU; + + return Wrap(angle, -Math.PI, Math.PI); +}; + +export default NormalAngle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/NormalX.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/NormalX.js new file mode 100644 index 000000000..fb1f6994d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/NormalX.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import MATH_CONST from '../../math/const.js'; +import Angle from './Angle.js'; + +/** + * [description] + * + * @function Phaser.Geom.Line.NormalX + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The Line object to get the normal value from. + * + * @return {number} [description] + */ +var NormalX = function (line) { + return Math.cos(Angle(line) - MATH_CONST.TAU); +}; + +export default NormalX; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/NormalY.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/NormalY.js new file mode 100644 index 000000000..32653ccea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/NormalY.js @@ -0,0 +1,25 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import MATH_CONST from '../../math/const.js'; +import Angle from './Angle.js'; + +/** + * The Y value of the normal of the given line. + * The normal of a line is a vector that points perpendicular from it. + * + * @function Phaser.Geom.Line.NormalY + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The line to calculate the normal of. + * + * @return {number} The Y value of the normal of the Line. + */ +var NormalY = function (line) { + return Math.sin(Angle(line) - MATH_CONST.TAU); +}; + +export default NormalY; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Offset.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Offset.js new file mode 100644 index 000000000..6a89ed0b2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Offset.js @@ -0,0 +1,32 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Offset a line by the given amount. + * + * @function Phaser.Geom.Line.Offset + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [line,$return] + * + * @param {Phaser.Geom.Line} line - The line to offset. + * @param {number} x - The horizontal offset to add to the line. + * @param {number} y - The vertical offset to add to the line. + * + * @return {Phaser.Geom.Line} The offset line. + */ +var Offset = function (line, x, y) +{ + line.x1 += x; + line.y1 += y; + + line.x2 += x; + line.y2 += y; + + return line; +}; + +export default Offset; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/PerpSlope.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/PerpSlope.js new file mode 100644 index 000000000..0149ba1db --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/PerpSlope.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculate the perpendicular slope of the given line. + * + * @function Phaser.Geom.Line.PerpSlope + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The line to calculate the perpendicular slope of. + * + * @return {number} The perpendicular slope of the line. + */ +var PerpSlope = function (line) +{ + return -((line.x2 - line.x1) / (line.y2 - line.y1)); +}; + +export default PerpSlope; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Random.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Random.js new file mode 100644 index 000000000..ff6aa6d91 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Random.js @@ -0,0 +1,33 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +/** + * Returns a random point on a given Line. + * + * @function Phaser.Geom.Line.Random + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Line} line - The Line to calculate the random Point on. + * @param {(Phaser.Geom.Point|object)} [out] - An instance of a Point to be modified. + * + * @return {(Phaser.Geom.Point|object)} A random Point on the Line. + */ +var Random = function (line, out) { + if (out === undefined) { out = new Point(); } + + var t = Math.random(); + + out.x = line.x1 + t * (line.x2 - line.x1); + out.y = line.y1 + t * (line.y2 - line.y1); + + return out; +}; + +export default Random; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/ReflectAngle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/ReflectAngle.js new file mode 100644 index 000000000..910dd576c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/ReflectAngle.js @@ -0,0 +1,27 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Angle from './Angle.js'; +import NormalAngle from './NormalAngle.js'; + +/** + * Calculate the reflected angle between two lines. + * + * This is the outgoing angle based on the angle of Line 1 and the normalAngle of Line 2. + * + * @function Phaser.Geom.Line.ReflectAngle + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} lineA - The first line. + * @param {Phaser.Geom.Line} lineB - The second line. + * + * @return {number} The reflected angle between each line. + */ +var ReflectAngle = function (lineA, lineB) { + return (2 * NormalAngle(lineB) - Math.PI - Angle(lineA)); +}; + +export default ReflectAngle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Rotate.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Rotate.js new file mode 100644 index 000000000..be06d490e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Rotate.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import RotateAroundXY from './RotateAroundXY.js'; + +/** + * Rotate a line around its midpoint by the given angle in radians. + * + * @function Phaser.Geom.Line.Rotate + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [line,$return] + * + * @param {Phaser.Geom.Line} line - The line to rotate. + * @param {number} angle - The angle of rotation in radians. + * + * @return {Phaser.Geom.Line} The rotated line. + */ +var Rotate = function (line, angle) { + var x = (line.x1 + line.x2) / 2; + var y = (line.y1 + line.y2) / 2; + + return RotateAroundXY(line, x, y, angle); +}; + +export default Rotate; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/RotateAroundPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/RotateAroundPoint.js new file mode 100644 index 000000000..44f0eedc5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/RotateAroundPoint.js @@ -0,0 +1,27 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import RotateAroundXY from './RotateAroundXY.js'; + +/** + * Rotate a line around a point by the given angle in radians. + * + * @function Phaser.Geom.Line.RotateAroundPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [line,$return] + * + * @param {Phaser.Geom.Line} line - The line to rotate. + * @param {(Phaser.Geom.Point|object)} point - The point to rotate the line around. + * @param {number} angle - The angle of rotation in radians. + * + * @return {Phaser.Geom.Line} The rotated line. + */ +var RotateAroundPoint = function (line, point, angle) { + return RotateAroundXY(line, point.x, point.y, angle); +}; + +export default RotateAroundPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/RotateAroundXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/RotateAroundXY.js new file mode 100644 index 000000000..83845661a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/RotateAroundXY.js @@ -0,0 +1,42 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Rotate a line around the given coordinates by the given angle in radians. + * + * @function Phaser.Geom.Line.RotateAroundXY + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [line,$return] + * + * @param {Phaser.Geom.Line} line - The line to rotate. + * @param {number} x - The horizontal coordinate to rotate the line around. + * @param {number} y - The vertical coordinate to rotate the line around. + * @param {number} angle - The angle of rotation in radians. + * + * @return {Phaser.Geom.Line} The rotated line. + */ +var RotateAroundXY = function (line, x, y, angle) +{ + var c = Math.cos(angle); + var s = Math.sin(angle); + + var tx = line.x1 - x; + var ty = line.y1 - y; + + line.x1 = tx * c - ty * s + x; + line.y1 = tx * s + ty * c + y; + + tx = line.x2 - x; + ty = line.y2 - y; + + line.x2 = tx * c - ty * s + x; + line.y2 = tx * s + ty * c + y; + + return line; +}; + +export default RotateAroundXY; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/SetToAngle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/SetToAngle.js new file mode 100644 index 000000000..c4ef52f87 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/SetToAngle.js @@ -0,0 +1,34 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Set a line to a given position, angle and length. + * + * @function Phaser.Geom.Line.SetToAngle + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [line,$return] + * + * @param {Phaser.Geom.Line} line - The line to set. + * @param {number} x - The horizontal start position of the line. + * @param {number} y - The vertical start position of the line. + * @param {number} angle - The angle of the line in radians. + * @param {number} length - The length of the line. + * + * @return {Phaser.Geom.Line} The updated line. + */ +var SetToAngle = function (line, x, y, angle, length) +{ + line.x1 = x; + line.y1 = y; + + line.x2 = x + (Math.cos(angle) * length); + line.y2 = y + (Math.sin(angle) * length); + + return line; +}; + +export default SetToAngle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Slope.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Slope.js new file mode 100644 index 000000000..b334d118f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Slope.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculate the slope of the given line. + * + * @function Phaser.Geom.Line.Slope + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The line to calculate the slope of. + * + * @return {number} The slope of the line. + */ +var Slope = function (line) +{ + return (line.y2 - line.y1) / (line.x2 - line.x1); +}; + +export default Slope; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Width.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Width.js new file mode 100644 index 000000000..671a5bc5e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Width.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculate the width of the given line. + * + * @function Phaser.Geom.Line.Width + * @since 3.0.0 + * + * @param {Phaser.Geom.Line} line - The line to calculate the width of. + * + * @return {number} The width of the line. + */ +var Width = function (line) +{ + return Math.abs(line.x1 - line.x2); +}; + +export default Width; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/index.js new file mode 100644 index 000000000..5a543c4e5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/line/index.js @@ -0,0 +1,66 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Line from './Line.js'; +import Angle from './Angle.js'; +import BresenhamPoints from './BresenhamPoints.js'; +import CenterOn from './CenterOn.js'; +import Clone from './Clone.js'; +import CopyFrom from './CopyFrom.js'; +import Equals from './Equals.js'; +import Extend from './Extend.js'; +import GetMidPoint from './GetMidPoint.js'; +import GetNearestPoint from './GetNearestPoint.js'; +import GetNormal from './GetNormal.js'; +import GetPoint from './GetPoint.js'; +import GetPoints from './GetPoints.js'; +import GetShortestDistance from './GetShortestDistance.js'; +import Height from './Height.js'; +import Length from './Length.js'; +import NormalAngle from './NormalAngle.js'; +import NormalX from './NormalX.js'; +import NormalY from './NormalY.js'; +import Offset from './Offset.js'; +import PerpSlope from './PerpSlope.js'; +import Random from './Random.js'; +import ReflectAngle from './ReflectAngle.js'; +import Rotate from './Rotate.js'; +import RotateAroundPoint from './RotateAroundPoint.js'; +import RotateAroundXY from './RotateAroundXY.js'; +import SetToAngle from './SetToAngle.js'; +import Slope from './Slope.js'; +import Width from './Width.js'; + +Line.Angle = Angle; +Line.BresenhamPoints = BresenhamPoints; +Line.CenterOn = CenterOn; +Line.Clone = Clone; +Line.CopyFrom = CopyFrom; +Line.Equals = Equals; +Line.Extend = Extend; +Line.GetMidPoint = GetMidPoint; +Line.GetNearestPoint = GetNearestPoint; +Line.GetNormal = GetNormal; +Line.GetPoint = GetPoint; +Line.GetPoints = GetPoints; +Line.GetShortestDistance = GetShortestDistance; +Line.Height = Height; +Line.Length = Length; +Line.NormalAngle = NormalAngle; +Line.NormalX = NormalX; +Line.NormalY = NormalY; +Line.Offset = Offset; +Line.PerpSlope = PerpSlope; +Line.Random = Random; +Line.ReflectAngle = ReflectAngle; +Line.Rotate = Rotate; +Line.RotateAroundPoint = RotateAroundPoint; +Line.RotateAroundXY = RotateAroundXY; +Line.SetToAngle = SetToAngle; +Line.Slope = Slope; +Line.Width = Width; + +export default Line; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Ceil.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Ceil.js new file mode 100644 index 000000000..9498baf8b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Ceil.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Apply `Math.ceil()` to each coordinate of the given Point. + * + * @function Phaser.Geom.Point.Ceil + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [point,$return] + * + * @param {Phaser.Geom.Point} point - The Point to ceil. + * + * @return {Phaser.Geom.Point} The Point with `Math.ceil()` applied to its coordinates. + */ +var Ceil = function (point) +{ + return point.setTo(Math.ceil(point.x), Math.ceil(point.y)); +}; + +export default Ceil; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Clone.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Clone.js new file mode 100644 index 000000000..d716f87cf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Clone.js @@ -0,0 +1,23 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from './Point.js'; + +/** + * Clone the given Point. + * + * @function Phaser.Geom.Point.Clone + * @since 3.0.0 + * + * @param {Phaser.Geom.Point} source - The source Point to clone. + * + * @return {Phaser.Geom.Point} The cloned Point. + */ +var Clone = function (source) { + return new Point(source.x, source.y); +}; + +export default Clone; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/CopyFrom.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/CopyFrom.js new file mode 100644 index 000000000..4350aa9f3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/CopyFrom.js @@ -0,0 +1,25 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Copy the values of one Point to a destination Point. + * + * @function Phaser.Geom.Point.CopyFrom + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [dest,$return] + * + * @param {Phaser.Geom.Point} source - The source Point to copy the values from. + * @param {Phaser.Geom.Point} dest - The destination Point to copy the values to. + * + * @return {Phaser.Geom.Point} The destination Point. + */ +var CopyFrom = function (source, dest) +{ + return dest.setTo(source.x, source.y); +}; + +export default CopyFrom; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Equals.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Equals.js new file mode 100644 index 000000000..a38a9093c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Equals.js @@ -0,0 +1,23 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * A comparison of two `Point` objects to see if they are equal. + * + * @function Phaser.Geom.Point.Equals + * @since 3.0.0 + * + * @param {Phaser.Geom.Point} point - The original `Point` to compare against. + * @param {Phaser.Geom.Point} toCompare - The second `Point` to compare. + * + * @return {boolean} Returns true if the both `Point` objects are equal. + */ +var Equals = function (point, toCompare) +{ + return (point.x === toCompare.x && point.y === toCompare.y); +}; + +export default Equals; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Floor.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Floor.js new file mode 100644 index 000000000..eb614d378 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Floor.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Apply `Math.ceil()` to each coordinate of the given Point. + * + * @function Phaser.Geom.Point.Floor + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [point,$return] + * + * @param {Phaser.Geom.Point} point - The Point to floor. + * + * @return {Phaser.Geom.Point} The Point with `Math.floor()` applied to its coordinates. + */ +var Floor = function (point) +{ + return point.setTo(Math.floor(point.x), Math.floor(point.y)); +}; + +export default Floor; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetCentroid.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetCentroid.js new file mode 100644 index 000000000..bbabda061 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetCentroid.js @@ -0,0 +1,52 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from './Point.js'; + +/** + * Get the centroid or geometric center of a plane figure (the arithmetic mean position of all the points in the figure). + * Informally, it is the point at which a cutout of the shape could be perfectly balanced on the tip of a pin. + * + * @function Phaser.Geom.Point.GetCentroid + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Point[]} points - [description] + * @param {Phaser.Geom.Point} [out] - [description] + * + * @return {Phaser.Geom.Point} [description] + */ +var GetCentroid = function (points, out) { + if (out === undefined) { out = new Point(); } + + if (!Array.isArray(points)) { + throw new Error('GetCentroid points argument must be an array'); + } + + var len = points.length; + + if (len < 1) { + throw new Error('GetCentroid points array must not be empty'); + } + else if (len === 1) { + out.x = points[0].x; + out.y = points[0].y; + } + else { + for (var i = 0; i < len; i++) { + out.x += points[i].x; + out.y += points[i].y; + } + + out.x /= len; + out.y /= len; + } + + return out; +}; + +export default GetCentroid; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetMagnitude.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetMagnitude.js new file mode 100644 index 000000000..6a988ecf3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetMagnitude.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculate the magnitude of the point, which equivalent to the length of the line from the origin to this point. + * + * @function Phaser.Geom.Point.GetMagnitude + * @since 3.0.0 + * + * @param {Phaser.Geom.Point} point - The point to calculate the magnitude for + * + * @return {number} The resulting magnitude + */ +var GetMagnitude = function (point) +{ + return Math.sqrt((point.x * point.x) + (point.y * point.y)); +}; + +export default GetMagnitude; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetMagnitudeSq.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetMagnitudeSq.js new file mode 100644 index 000000000..77189e889 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetMagnitudeSq.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculates the square of magnitude of given point.(Can be used for fast magnitude calculation of point) + * + * @function Phaser.Geom.Point.GetMagnitudeSq + * @since 3.0.0 + * + * @param {Phaser.Geom.Point} point - Returns square of the magnitude/length of given point. + * + * @return {number} Returns square of the magnitude of given point. + */ +var GetMagnitudeSq = function (point) +{ + return (point.x * point.x) + (point.y * point.y); +}; + +export default GetMagnitudeSq; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetRectangleFromPoints.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetRectangleFromPoints.js new file mode 100644 index 000000000..1e2247b09 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetRectangleFromPoints.js @@ -0,0 +1,58 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Rectangle from '../rectangle/Rectangle.js'; + +/** + * Calculates the Axis Aligned Bounding Box (or aabb) from an array of points. + * + * @function Phaser.Geom.Point.GetRectangleFromPoints + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [out,$return] + * + * @param {Phaser.Geom.Point[]} points - [description] + * @param {Phaser.Geom.Rectangle} [out] - [description] + * + * @return {Phaser.Geom.Rectangle} [description] + */ +var GetRectangleFromPoints = function (points, out) { + if (out === undefined) { out = new Rectangle(); } + + var xMax = Number.NEGATIVE_INFINITY; + var xMin = Number.POSITIVE_INFINITY; + var yMax = Number.NEGATIVE_INFINITY; + var yMin = Number.POSITIVE_INFINITY; + + for (var i = 0; i < points.length; i++) { + var point = points[i]; + + if (point.x > xMax) { + xMax = point.x; + } + + if (point.x < xMin) { + xMin = point.x; + } + + if (point.y > yMax) { + yMax = point.y; + } + + if (point.y < yMin) { + yMin = point.y; + } + } + + out.x = xMin; + out.y = yMin; + out.width = xMax - xMin; + out.height = yMax - yMin; + + return out; +}; + +export default GetRectangleFromPoints; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Interpolate.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Interpolate.js new file mode 100644 index 000000000..f79935960 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Interpolate.js @@ -0,0 +1,34 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from './Point.js'; + +/** + * [description] + * + * @function Phaser.Geom.Point.Interpolate + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Point} pointA - The starting `Point` for the interpolation. + * @param {Phaser.Geom.Point} pointB - The target `Point` for the interpolation. + * @param {number} [t=0] - The amount to interpolate between the two points. Generally, a value between 0 (returns the starting `Point`) and 1 (returns the target `Point`). If omitted, 0 is used. + * @param {(Phaser.Geom.Point|object)} [out] - An optional `Point` object whose `x` and `y` values will be set to the result of the interpolation (can also be any object with `x` and `y` properties). If omitted, a new `Point` created and returned. + * + * @return {(Phaser.Geom.Point|object)} Either the object from the `out` argument with the properties `x` and `y` set to the result of the interpolation or a newly created `Point` object. + */ +var Interpolate = function (pointA, pointB, t, out) { + if (t === undefined) { t = 0; } + if (out === undefined) { out = new Point(); } + + out.x = pointA.x + ((pointB.x - pointA.x) * t); + out.y = pointA.y + ((pointB.y - pointA.y) * t); + + return out; +}; + +export default Interpolate; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Invert.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Invert.js new file mode 100644 index 000000000..9345384c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Invert.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Swaps the X and the Y coordinate of a point. + * + * @function Phaser.Geom.Point.Invert + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [point,$return] + * + * @param {Phaser.Geom.Point} point - The Point to modify. + * + * @return {Phaser.Geom.Point} The modified `point`. + */ +var Invert = function (point) +{ + return point.setTo(point.y, point.x); +}; + +export default Invert; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Negative.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Negative.js new file mode 100644 index 000000000..12cdc37b8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Negative.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from './Point.js'; + +/** + * Inverts a Point's coordinates. + * + * @function Phaser.Geom.Point.Negative + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Point} point - The Point to invert. + * @param {Phaser.Geom.Point} [out] - The Point to return the inverted coordinates in. + * + * @return {Phaser.Geom.Point} The modified `out` Point, or a new Point if none was provided. + */ +var Negative = function (point, out) { + if (out === undefined) { out = new Point(); } + + return out.setTo(-point.x, -point.y); +}; + +export default Negative; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Point.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Point.d.ts new file mode 100644 index 000000000..97b133045 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Point.d.ts @@ -0,0 +1,146 @@ +import {Vector2Like} from '../types'; +import Rectangle from '../rectangle/Rectangle'; + +/** + * Defines a Point in 2D space, with an x and y component. + */ +declare class Point { + /** + * + * @param x The x coordinate of this Point. Default 0. + * @param y The y coordinate of this Point. Default x. + */ + constructor(x?: number, y?: number); + + /** + * Apply `Math.ceil()` to each coordinate of the given Point. + * @param point The Point to ceil. + */ + static Ceil(point: O): O; + + /** + * Clone the given Point. + * @param source The source Point to clone. + */ + static Clone(source: Point): Point; + + /** + * Copy the values of one Point to a destination Point. + * @param source The source Point to copy the values from. + * @param dest The destination Point to copy the values to. + */ + static CopyFrom(source: Point, dest: O): O; + + /** + * A comparison of two `Point` objects to see if they are equal. + * @param point The original `Point` to compare against. + * @param toCompare The second `Point` to compare. + */ + static Equals(point: Point, toCompare: Point): boolean; + + /** + * Apply `Math.ceil()` to each coordinate of the given Point. + * @param point The Point to floor. + */ + static Floor(point: O): O; + + /** + * Get the centroid or geometric center of a plane figure (the arithmetic mean position of all the points in the figure). + * Informally, it is the point at which a cutout of the shape could be perfectly balanced on the tip of a pin. + * @param points An array of Vector2Like objects to get the geometric center of. + * @param out A Point object to store the output coordinates in. If not given, a new Point instance is created. + */ + static GetCentroid(points: Vector2Like[], out?: O): O; + + /** + * Calculate the magnitude of the point, which equivalent to the length of the line from the origin to this point. + * @param point The point to calculate the magnitude for + */ + static GetMagnitude(point: Point): number; + + /** + * Calculates the square of magnitude of given point.(Can be used for fast magnitude calculation of point) + * @param point Returns square of the magnitude/length of given point. + */ + static GetMagnitudeSq(point: Point): number; + + /** + * Calculates the Axis Aligned Bounding Box (or aabb) from an array of points. + * @param points An array of Vector2Like objects to get the AABB from. + * @param out A Rectangle object to store the results in. If not given, a new Rectangle instance is created. + */ + static GetRectangleFromPoints(points: Vector2Like[], out?: O): O; + + /** + * Returns the linear interpolation point between the two given points, based on `t`. + * @param pointA The starting `Point` for the interpolation. + * @param pointB The target `Point` for the interpolation. + * @param t The amount to interpolate between the two points. Generally, a value between 0 (returns the starting `Point`) and 1 (returns the target `Point`). If omitted, 0 is used. Default 0. + * @param out An optional `Point` object whose `x` and `y` values will be set to the result of the interpolation (can also be any object with `x` and `y` properties). If omitted, a new `Point` created and returned. + */ + static Interpolate(pointA: Point, pointB: Point, t?: number, out?: O): O; + + /** + * Swaps the X and the Y coordinate of a point. + * @param point The Point to modify. + */ + static Invert(point: O): O; + + /** + * Inverts a Point's coordinates. + * @param point The Point to invert. + * @param out The Point to return the inverted coordinates in. + */ + static Negative(point: Point, out?: O): O; + + /** + * The geometry constant type of this object: `GEOM_CONST.POINT`. + * Used for fast type comparisons. + */ + readonly type: number; + + /** + * The x coordinate of this Point. + */ + x: number; + + /** + * The y coordinate of this Point. + */ + y: number; + + /** + * Set the x and y coordinates of the point to the given values. + * @param x The x coordinate of this Point. Default 0. + * @param y The y coordinate of this Point. Default x. + */ + setTo(x?: number, y?: number): this; + + /** + * Calculates the vector projection of `pointA` onto the nonzero `pointB`. This is the + * orthogonal projection of `pointA` onto a straight line parallel to `pointB`. + * @param pointA Point A, to be projected onto Point B. + * @param pointB Point B, to have Point A projected upon it. + * @param out The Point object to store the position in. If not given, a new Point instance is created. + */ + static Project(pointA: Point, pointB: Point, out?: O): O; + + /** + * Calculates the vector projection of `pointA` onto the nonzero `pointB`. This is the + * orthogonal projection of `pointA` onto a straight line paralle to `pointB`. + * @param pointA Point A, to be projected onto Point B. Must be a normalized point with a magnitude of 1. + * @param pointB Point B, to have Point A projected upon it. + * @param out The Point object to store the position in. If not given, a new Point instance is created. + */ + static ProjectUnit(pointA: Point, pointB: Point, out?: O): O; + + /** + * Changes the magnitude (length) of a two-dimensional vector without changing its direction. + * @param point The Point to treat as the end point of the vector. + * @param magnitude The new magnitude of the vector. + */ + static SetMagnitude(point: O, magnitude: number): O; + +} + +export default Point; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Point.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Point.js new file mode 100644 index 000000000..9b60e9a20 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Point.js @@ -0,0 +1,73 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Class from '../../object/Class.js'; + +/** + * @classdesc + * Defines a Point in 2D space, with an x and y component. + * + * @class Point + * @memberof Phaser.Geom + * @constructor + * @since 3.0.0 + * + * @param {number} [x=0] - The x coordinate of this Point. + * @param {number} [y=x] - The y coordinate of this Point. + */ +var Point = new Class({ + + initialize: + + function Point(x, y) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = x; } + + /** + * The x coordinate of this Point. + * + * @name Phaser.Geom.Point#x + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.x = x; + + /** + * The y coordinate of this Point. + * + * @name Phaser.Geom.Point#y + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.y = y; + }, + + /** + * Set the x and y coordinates of the point to the given values. + * + * @method Phaser.Geom.Point#setTo + * @since 3.0.0 + * + * @param {number} [x=0] - The x coordinate of this Point. + * @param {number} [y=x] - The y coordinate of this Point. + * + * @return {Phaser.Geom.Point} This Point object. + */ + setTo: function (x, y) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = x; } + + this.x = x; + this.y = y; + + return this; + } + +}); + +export default Point; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Project.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Project.js new file mode 100644 index 000000000..a15b4958e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Project.js @@ -0,0 +1,38 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from './Point.js'; +import GetMagnitudeSq from './GetMagnitudeSq.js'; + +/** + * [description] + * + * @function Phaser.Geom.Point.Project + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Point} pointA - [description] + * @param {Phaser.Geom.Point} pointB - [description] + * @param {Phaser.Geom.Point} [out] - [description] + * + * @return {Phaser.Geom.Point} [description] + */ +var Project = function (pointA, pointB, out) { + if (out === undefined) { out = new Point(); } + + var dot = ((pointA.x * pointB.x) + (pointA.y * pointB.y)); + var amt = dot / GetMagnitudeSq(pointB); + + if (amt !== 0) { + out.x = amt * pointB.x; + out.y = amt * pointB.y; + } + + return out; +}; + +export default Project; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/ProjectUnit.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/ProjectUnit.js new file mode 100644 index 000000000..16d759d82 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/ProjectUnit.js @@ -0,0 +1,36 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from './Point.js'; + +/** + * [description] + * + * @function Phaser.Geom.Point.ProjectUnit + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Point} pointA - [description] + * @param {Phaser.Geom.Point} pointB - [description] + * @param {Phaser.Geom.Point} [out] - [description] + * + * @return {Phaser.Geom.Point} [description] + */ +var ProjectUnit = function (pointA, pointB, out) { + if (out === undefined) { out = new Point(); } + + var amt = ((pointA.x * pointB.x) + (pointA.y * pointB.y)); + + if (amt !== 0) { + out.x = amt * pointB.x; + out.y = amt * pointB.y; + } + + return out; +}; + +export default ProjectUnit; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/SetMagnitude.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/SetMagnitude.js new file mode 100644 index 000000000..09e82f6d5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/SetMagnitude.js @@ -0,0 +1,36 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import GetMagnitude from './GetMagnitude.js'; + +/** + * Changes the magnitude (length) of a two-dimensional vector without changing its direction. + * + * @function Phaser.Geom.Point.SetMagnitude + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [point,$return] + * + * @param {Phaser.Geom.Point} point - The Point to treat as the end point of the vector. + * @param {number} magnitude - The new magnitude of the vector. + * + * @return {Phaser.Geom.Point} The modified Point. + */ +var SetMagnitude = function (point, magnitude) { + if (point.x !== 0 || point.y !== 0) { + var m = GetMagnitude(point); + + point.x /= m; + point.y /= m; + } + + point.x *= magnitude; + point.y *= magnitude; + + return point; +}; + +export default SetMagnitude; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/index.js new file mode 100644 index 000000000..4a0160d77 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/point/index.js @@ -0,0 +1,40 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from './Point.js'; +import Ceil from './Ceil.js'; +import Clone from './Clone.js'; +import CopyFrom from './CopyFrom.js'; +import Equals from './Equals.js'; +import Floor from './Floor.js'; +import GetCentroid from './GetCentroid.js'; +import GetMagnitude from './GetMagnitude.js'; +import GetMagnitudeSq from './GetMagnitudeSq.js'; +import GetRectangleFromPoints from './GetRectangleFromPoints.js'; +import Interpolate from './Interpolate.js'; +import Invert from './Invert.js'; +import Negative from './Negative.js'; +import Project from './Project.js'; +import ProjectUnit from './ProjectUnit.js'; +import SetMagnitude from './SetMagnitude.js'; + +Point.Ceil = Ceil; +Point.Clone = Clone; +Point.CopyFrom = CopyFrom; +Point.Equals = Equals; +Point.Floor = Floor; +Point.GetCentroid = GetCentroid; +Point.GetMagnitude = GetMagnitude; +Point.GetMagnitudeSq = GetMagnitudeSq; +Point.GetRectangleFromPoints = GetRectangleFromPoints; +Point.Interpolate = Interpolate; +Point.Invert = Invert; +Point.Negative = Negative; +Point.Project = Project; +Point.ProjectUnit = ProjectUnit; +Point.SetMagnitude = SetMagnitude; + +export default Point; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Clone.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Clone.js new file mode 100644 index 000000000..e851ec430 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Clone.js @@ -0,0 +1,23 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Polygon from './Polygon.js'; + +/** + * Create a new polygon which is a copy of the specified polygon + * + * @function Phaser.Geom.Polygon.Clone + * @since 3.0.0 + * + * @param {Phaser.Geom.Polygon} polygon - The polygon to create a clone of + * + * @return {Phaser.Geom.Polygon} A new separate Polygon cloned from the specified polygon, based on the same points. + */ +var Clone = function (polygon) { + return new Polygon(polygon.points); +}; + +export default Clone; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Contains.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Contains.js new file mode 100644 index 000000000..8c715eddd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Contains.js @@ -0,0 +1,43 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +// Checks whether the x and y coordinates are contained within this polygon. +// Adapted from http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html by Jonas Raoni Soares Silva + +/** + * Checks if a point is within the bounds of a Polygon. + * + * @function Phaser.Geom.Polygon.Contains + * @since 3.0.0 + * + * @param {Phaser.Geom.Polygon} polygon - The Polygon to check against. + * @param {number} x - The X coordinate of the point to check. + * @param {number} y - The Y coordinate of the point to check. + * + * @return {boolean} `true` if the point is within the bounds of the Polygon, otherwise `false`. + */ +var Contains = function (polygon, x, y) +{ + var inside = false; + + for (var i = -1, j = polygon.points.length - 1; ++i < polygon.points.length; j = i) + { + var ix = polygon.points[i].x; + var iy = polygon.points[i].y; + + var jx = polygon.points[j].x; + var jy = polygon.points[j].y; + + if (((iy <= y && y < jy) || (jy <= y && y < iy)) && (x < (jx - ix) * (y - iy) / (jy - iy) + ix)) + { + inside = !inside; + } + } + + return inside; +}; + +export default Contains; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/ContainsPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/ContainsPoint.js new file mode 100644 index 000000000..7590f5824 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/ContainsPoint.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Contains from './Contains.js'; + +/** + * [description] + * + * @function Phaser.Geom.Polygon.ContainsPoint + * @since 3.0.0 + * + * @param {Phaser.Geom.Polygon} polygon - [description] + * @param {Phaser.Geom.Point} point - [description] + * + * @return {boolean} [description] + */ +var ContainsPoint = function (polygon, point) { + return Contains(polygon, point.x, point.y); +}; + +export default ContainsPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Earcut.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Earcut.js new file mode 100644 index 000000000..247a8bdee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Earcut.js @@ -0,0 +1,759 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * This module implements a modified ear slicing algorithm, optimized by z-order curve hashing and extended to + * handle holes, twisted polygons, degeneracies and self-intersections in a way that doesn't guarantee correctness + * of triangulation, but attempts to always produce acceptable results for practical data. + * + * Example: + * + * ```javascript + * const triangles = Phaser.Geom.Polygon.Earcut([10,0, 0,50, 60,60, 70,10]); // returns [1,0,3, 3,2,1] + * ``` + * + * Each group of three vertex indices in the resulting array forms a triangle. + * + * ```javascript + * // triangulating a polygon with a hole + * earcut([0,0, 100,0, 100,100, 0,100, 20,20, 80,20, 80,80, 20,80], [4]); + * // [3,0,4, 5,4,0, 3,4,7, 5,0,1, 2,3,7, 6,5,1, 2,7,6, 6,1,2] + * + * // triangulating a polygon with 3d coords + * earcut([10,0,1, 0,50,2, 60,60,3, 70,10,4], null, 3); + * // [1,0,3, 3,2,1] + * ``` + * + * If you pass a single vertex as a hole, Earcut treats it as a Steiner point. + * + * If your input is a multi-dimensional array (e.g. GeoJSON Polygon), you can convert it to the format + * expected by Earcut with `Phaser.Geom.Polygon.Earcut.flatten`: + * + * ```javascript + * var data = earcut.flatten(geojson.geometry.coordinates); + * var triangles = earcut(data.vertices, data.holes, data.dimensions); + * ``` + * + * After getting a triangulation, you can verify its correctness with `Phaser.Geom.Polygon.Earcut.deviation`: + * + * ```javascript + * var deviation = earcut.deviation(vertices, holes, dimensions, triangles); + * ``` + * Returns the relative difference between the total area of triangles and the area of the input polygon. + * 0 means the triangulation is fully correct. + * + * For more information see https://github.com/mapbox/earcut + * + * @function Phaser.Geom.Polygon.Earcut + * @since 3.50.0 + * + * @param {number[]} data - A flat array of vertex coordinate, like [x0,y0, x1,y1, x2,y2, ...] + * @param {number[]} [holeIndices] - An array of hole indices if any (e.g. [5, 8] for a 12-vertex input would mean one hole with vertices 5–7 and another with 8–11). + * @param {number} [dimensions=2] - The number of coordinates per vertex in the input array (2 by default). + * + * @return {number[]} An array of triangulated data. + */ + + // Earcut 2.2.4 (July 5th 2022) + +/* + * ISC License + * + * Copyright (c) 2016, Mapbox + * + * Permission to use, copy, modify, and/or distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright notice + * and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +'use strict'; + +function earcut(data, holeIndices, dim) { + + dim = dim || 2; + + var hasHoles = holeIndices && holeIndices.length, + outerLen = hasHoles ? holeIndices[0] * dim : data.length, + outerNode = linkedList(data, 0, outerLen, dim, true), + triangles = []; + + if (!outerNode || outerNode.next === outerNode.prev) return triangles; + + var minX, minY, maxX, maxY, x, y, invSize; + + if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + if (data.length > 80 * dim) { + minX = maxX = data[0]; + minY = maxY = data[1]; + + for (var i = dim; i < outerLen; i += dim) { + x = data[i]; + y = data[i + 1]; + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + } + + // minX, minY and invSize are later used to transform coords into integers for z-order calculation + invSize = Math.max(maxX - minX, maxY - minY); + invSize = invSize !== 0 ? 32767 / invSize : 0; + } + + earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0); + + return triangles; +} + +// create a circular doubly linked list from polygon points in the specified winding order +function linkedList(data, start, end, dim, clockwise) { + var i, last; + + if (clockwise === (signedArea(data, start, end, dim) > 0)) { + for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last); + } else { + for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last); + } + + if (last && equals(last, last.next)) { + removeNode(last); + last = last.next; + } + + return last; +} + +// eliminate colinear or duplicate points +function filterPoints(start, end) { + if (!start) return start; + if (!end) end = start; + + var p = start, + again; + do { + again = false; + + if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { + removeNode(p); + p = end = p.prev; + if (p === p.next) break; + again = true; + + } else { + p = p.next; + } + } while (again || p !== end); + + return end; +} + +// main ear slicing loop which triangulates a polygon (given as a linked list) +function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { + if (!ear) return; + + // interlink polygon nodes in z-order + if (!pass && invSize) indexCurve(ear, minX, minY, invSize); + + var stop = ear, + prev, next; + + // iterate through ears, slicing them one by one + while (ear.prev !== ear.next) { + prev = ear.prev; + next = ear.next; + + if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) { + // cut off the triangle + triangles.push(prev.i / dim | 0); + triangles.push(ear.i / dim | 0); + triangles.push(next.i / dim | 0); + + removeNode(ear); + + // skipping the next vertex leads to less sliver triangles + ear = next.next; + stop = next.next; + + continue; + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + if (ear === stop) { + // try filtering points and slicing again + if (!pass) { + earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); + + // if this didn't work, try curing all small self-intersections locally + } else if (pass === 1) { + ear = cureLocalIntersections(filterPoints(ear), triangles, dim); + earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); + + // as a last resort, try splitting the remaining polygon into two + } else if (pass === 2) { + splitEarcut(ear, triangles, dim, minX, minY, invSize); + } + + break; + } + } +} + +// check whether a polygon node forms a valid ear with adjacent nodes +function isEar(ear) { + var a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + var ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox; min & max are calculated like this for speed + var x0 = ax < bx ? (ax < cx ? ax : cx) : (bx < cx ? bx : cx), + y0 = ay < by ? (ay < cy ? ay : cy) : (by < cy ? by : cy), + x1 = ax > bx ? (ax > cx ? ax : cx) : (bx > cx ? bx : cx), + y1 = ay > by ? (ay > cy ? ay : cy) : (by > cy ? by : cy); + + var p = c.next; + while (p !== a) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && + pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && + area(p.prev, p, p.next) >= 0) return false; + p = p.next; + } + + return true; +} + +function isEarHashed(ear, minX, minY, invSize) { + var a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + var ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox; min & max are calculated like this for speed + var x0 = ax < bx ? (ax < cx ? ax : cx) : (bx < cx ? bx : cx), + y0 = ay < by ? (ay < cy ? ay : cy) : (by < cy ? by : cy), + x1 = ax > bx ? (ax > cx ? ax : cx) : (bx > cx ? bx : cx), + y1 = ay > by ? (ay > cy ? ay : cy) : (by > cy ? by : cy); + + // z-order range for the current triangle bbox; + var minZ = zOrder(x0, y0, minX, minY, invSize), + maxZ = zOrder(x1, y1, minX, minY, invSize); + + var p = ear.prevZ, + n = ear.nextZ; + + // look for points inside the triangle in both directions + while (p && p.z >= minZ && n && n.z <= maxZ) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + } + + // look for remaining points in decreasing z-order + while (p && p.z >= minZ) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + } + + // look for remaining points in increasing z-order + while (n && n.z <= maxZ) { + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + } + + return true; +} + +// go through all polygon nodes and cure small local self-intersections +function cureLocalIntersections(start, triangles, dim) { + var p = start; + do { + var a = p.prev, + b = p.next.next; + + if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { + + triangles.push(a.i / dim | 0); + triangles.push(p.i / dim | 0); + triangles.push(b.i / dim | 0); + + // remove two nodes involved + removeNode(p); + removeNode(p.next); + + p = start = b; + } + p = p.next; + } while (p !== start); + + return filterPoints(p); +} + +// try splitting polygon into two and triangulate them independently +function splitEarcut(start, triangles, dim, minX, minY, invSize) { + // look for a valid diagonal that divides the polygon into two + var a = start; + do { + var b = a.next.next; + while (b !== a.prev) { + if (a.i !== b.i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + var c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a.next); + c = filterPoints(c, c.next); + + // run earcut on each half + earcutLinked(a, triangles, dim, minX, minY, invSize, 0); + earcutLinked(c, triangles, dim, minX, minY, invSize, 0); + return; + } + b = b.next; + } + a = a.next; + } while (a !== start); +} + +// link every hole into the outer loop, producing a single-ring polygon without holes +function eliminateHoles(data, holeIndices, outerNode, dim) { + var queue = [], + i, len, start, end, list; + + for (i = 0, len = holeIndices.length; i < len; i++) { + start = holeIndices[i] * dim; + end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; + list = linkedList(data, start, end, dim, false); + if (list === list.next) list.steiner = true; + queue.push(getLeftmost(list)); + } + + queue.sort(compareX); + + // process holes from left to right + for (i = 0; i < queue.length; i++) { + outerNode = eliminateHole(queue[i], outerNode); + } + + return outerNode; +} + +function compareX(a, b) { + return a.x - b.x; +} + +// find a bridge between vertices that connects hole with an outer ring and and link it +function eliminateHole(hole, outerNode) { + var bridge = findHoleBridge(hole, outerNode); + if (!bridge) { + return outerNode; + } + + var bridgeReverse = splitPolygon(bridge, hole); + + // filter collinear points around the cuts + filterPoints(bridgeReverse, bridgeReverse.next); + return filterPoints(bridge, bridge.next); +} + +// David Eberly's algorithm for finding a bridge between hole and outer polygon +function findHoleBridge(hole, outerNode) { + var p = outerNode, + hx = hole.x, + hy = hole.y, + qx = -Infinity, + m; + + // find a segment intersected by a ray from the hole's leftmost point to the left; + // segment's endpoint with lesser x will be potential connection point + do { + if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { + var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y); + if (x <= hx && x > qx) { + qx = x; + m = p.x < p.next.x ? p : p.next; + if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint + } + } + p = p.next; + } while (p !== outerNode); + + if (!m) return null; + + // look for points inside the triangle of hole point, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the point of the minimum angle with the ray as connection point + + var stop = m, + mx = m.x, + my = m.y, + tanMin = Infinity, + tan; + + p = m; + + do { + if (hx >= p.x && p.x >= mx && hx !== p.x && + pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) { + + tan = Math.abs(hy - p.y) / (hx - p.x); // tangential + + if (locallyInside(p, hole) && + (tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) { + m = p; + tanMin = tan; + } + } + + p = p.next; + } while (p !== stop); + + return m; +} + +// whether sector in vertex m contains sector in vertex p in the same coordinates +function sectorContainsSector(m, p) { + return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0; +} + +// interlink polygon nodes in z-order +function indexCurve(start, minX, minY, invSize) { + var p = start; + do { + if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize); + p.prevZ = p.prev; + p.nextZ = p.next; + p = p.next; + } while (p !== start); + + p.prevZ.nextZ = null; + p.prevZ = null; + + sortLinked(p); +} + +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html +function sortLinked(list) { + var i, p, q, e, tail, numMerges, pSize, qSize, + inSize = 1; + + do { + p = list; + list = null; + tail = null; + numMerges = 0; + + while (p) { + numMerges++; + q = p; + pSize = 0; + for (i = 0; i < inSize; i++) { + pSize++; + q = q.nextZ; + if (!q) break; + } + qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + + if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { + e = p; + p = p.nextZ; + pSize--; + } else { + e = q; + q = q.nextZ; + qSize--; + } + + if (tail) tail.nextZ = e; + else list = e; + + e.prevZ = tail; + tail = e; + } + + p = q; + } + + tail.nextZ = null; + inSize *= 2; + + } while (numMerges > 1); + + return list; +} + +// z-order of a point given coords and inverse of the longer side of data bbox +function zOrder(x, y, minX, minY, invSize) { + // coords are transformed into non-negative 15-bit integer range + x = (x - minX) * invSize | 0; + y = (y - minY) * invSize | 0; + + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + + return x | (y << 1); +} + +// find the leftmost node of a polygon ring +function getLeftmost(start) { + var p = start, + leftmost = start; + do { + if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p; + p = p.next; + } while (p !== start); + + return leftmost; +} + +// check if a point lies within a convex triangle +function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) { + return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && + (ax - px) * (by - py) >= (bx - px) * (ay - py) && + (bx - px) * (cy - py) >= (cx - px) * (by - py); +} + +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) +function isValidDiagonal(a, b) { + return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges + (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible + (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors + equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case +} + +// signed area of a triangle +function area(p, q, r) { + return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); +} + +// check if two points are equal +function equals(p1, p2) { + return p1.x === p2.x && p1.y === p2.y; +} + +// check if two segments intersect +function intersects(p1, q1, p2, q2) { + var o1 = sign(area(p1, q1, p2)); + var o2 = sign(area(p1, q1, q2)); + var o3 = sign(area(p2, q2, p1)); + var o4 = sign(area(p2, q2, q1)); + + if (o1 !== o2 && o3 !== o4) return true; // general case + + if (o1 === 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 + if (o2 === 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 + if (o3 === 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 + if (o4 === 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 + + return false; +} + +// for collinear points p, q, r, check if point q lies on segment pr +function onSegment(p, q, r) { + return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y); +} + +function sign(num) { + return num > 0 ? 1 : num < 0 ? -1 : 0; +} + +// check if a polygon diagonal intersects any polygon segments +function intersectsPolygon(a, b) { + var p = a; + do { + if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && + intersects(p, p.next, a, b)) return true; + p = p.next; + } while (p !== a); + + return false; +} + +// check if a polygon diagonal is locally inside the polygon +function locallyInside(a, b) { + return area(a.prev, a, a.next) < 0 ? + area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : + area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; +} + +// check if the middle point of a polygon diagonal is inside the polygon +function middleInside(a, b) { + var p = a, + inside = false, + px = (a.x + b.x) / 2, + py = (a.y + b.y) / 2; + do { + if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y && + (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)) + inside = !inside; + p = p.next; + } while (p !== a); + + return inside; +} + +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; +// if one belongs to the outer ring and another to a hole, it merges it into a single ring +function splitPolygon(a, b) { + var a2 = new Node(a.i, a.x, a.y), + b2 = new Node(b.i, b.x, b.y), + an = a.next, + bp = b.prev; + + a.next = b; + b.prev = a; + + a2.next = an; + an.prev = a2; + + b2.next = a2; + a2.prev = b2; + + bp.next = b2; + b2.prev = bp; + + return b2; +} + +// create a node and optionally link it with previous one (in a circular doubly linked list) +function insertNode(i, x, y, last) { + var p = new Node(i, x, y); + + if (!last) { + p.prev = p; + p.next = p; + + } else { + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; + } + return p; +} + +function removeNode(p) { + p.next.prev = p.prev; + p.prev.next = p.next; + + if (p.prevZ) p.prevZ.nextZ = p.nextZ; + if (p.nextZ) p.nextZ.prevZ = p.prevZ; +} + +function Node(i, x, y) { + // vertex index in coordinates array + this.i = i; + + // vertex coordinates + this.x = x; + this.y = y; + + // previous and next vertex nodes in a polygon ring + this.prev = null; + this.next = null; + + // z-order curve value + this.z = 0; + + // previous and next nodes in z-order + this.prevZ = null; + this.nextZ = null; + + // indicates whether this is a steiner point + this.steiner = false; +} + +// return a percentage difference between the polygon area and its triangulation area; +// used to verify correctness of triangulation +earcut.deviation = function (data, holeIndices, dim, triangles) { + var hasHoles = holeIndices && holeIndices.length; + var outerLen = hasHoles ? holeIndices[0] * dim : data.length; + + var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim)); + if (hasHoles) { + for (var i = 0, len = holeIndices.length; i < len; i++) { + var start = holeIndices[i] * dim; + var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; + polygonArea -= Math.abs(signedArea(data, start, end, dim)); + } + } + + var trianglesArea = 0; + for (i = 0; i < triangles.length; i += 3) { + var a = triangles[i] * dim; + var b = triangles[i + 1] * dim; + var c = triangles[i + 2] * dim; + trianglesArea += Math.abs( + (data[a] - data[c]) * (data[b + 1] - data[a + 1]) - + (data[a] - data[b]) * (data[c + 1] - data[a + 1])); + } + + return polygonArea === 0 && trianglesArea === 0 ? 0 : + Math.abs((trianglesArea - polygonArea) / polygonArea); +}; + +function signedArea(data, start, end, dim) { + var sum = 0; + for (var i = start, j = end - dim; i < end; i += dim) { + sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); + j = i; + } + return sum; +} + +// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts +earcut.flatten = function (data) { + var dim = data[0][0].length, + result = {vertices: [], holes: [], dimensions: dim}, + holeIndex = 0; + + for (var i = 0; i < data.length; i++) { + for (var j = 0; j < data[i].length; j++) { + for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]); + } + if (i > 0) { + holeIndex += data[i - 1].length; + result.holes.push(holeIndex); + } + } + return result; +}; + +module.exports = earcut; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/GetAABB.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/GetAABB.js new file mode 100644 index 000000000..b4b80a448 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/GetAABB.js @@ -0,0 +1,48 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Rectangle from '../rectangle/Rectangle.js'; + +/** + * Calculates the bounding AABB rectangle of a polygon. + * + * @function Phaser.Geom.Polygon.GetAABB + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [out,$return] + * + * @param {Phaser.Geom.Polygon} polygon - The polygon that should be calculated. + * @param {(Phaser.Geom.Rectangle|object)} [out] - The rectangle or object that has x, y, width, and height properties to store the result. Optional. + * + * @return {(Phaser.Geom.Rectangle|object)} The resulting rectangle or object that is passed in with position and dimensions of the polygon's AABB. + */ +var GetAABB = function (polygon, out) { + if (out === undefined) { out = new Rectangle(); } + + var minX = Infinity; + var minY = Infinity; + var maxX = -minX; + var maxY = -minY; + var p; + + for (var i = 0; i < polygon.points.length; i++) { + p = polygon.points[i]; + + minX = Math.min(minX, p.x); + minY = Math.min(minY, p.y); + maxX = Math.max(maxX, p.x); + maxY = Math.max(maxY, p.y); + } + + out.x = minX; + out.y = minY; + out.width = maxX - minX; + out.height = maxY - minY; + + return out; +}; + +export default GetAABB; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/GetNumberArray.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/GetNumberArray.js new file mode 100644 index 000000000..6dc07ef16 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/GetNumberArray.js @@ -0,0 +1,37 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +// Export the points as an array of flat numbers, following the sequence [ x,y, x,y, x,y ] + +/** + * Stores all of the points of a Polygon into a flat array of numbers following the sequence [ x,y, x,y, x,y ], + * i.e. each point of the Polygon, in the order it's defined, corresponds to two elements of the resultant + * array for the point's X and Y coordinate. + * + * @function Phaser.Geom.Polygon.GetNumberArray + * @since 3.0.0 + * + * @generic {number[]} O - [output,$return] + * + * @param {Phaser.Geom.Polygon} polygon - The Polygon whose points to export. + * @param {(array|number[])} [output] - An array to which the points' coordinates should be appended. + * + * @return {(array|number[])} The modified `output` array, or a new array if none was given. + */ +var GetNumberArray = function (polygon, output) +{ + if (output === undefined) { output = []; } + + for (var i = 0; i < polygon.points.length; i++) + { + output.push(polygon.points[i].x); + output.push(polygon.points[i].y); + } + + return output; +}; + +export default GetNumberArray; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/GetPoints.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/GetPoints.js new file mode 100644 index 000000000..e3280810f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/GetPoints.js @@ -0,0 +1,66 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Length from '../line/Length.js'; +import Line from '../line/Line.js'; +import Perimeter from './Perimeter.js'; + +/** + * Returns an array of Point objects containing the coordinates of the points around the perimeter of the Polygon, + * based on the given quantity or stepRate values. + * + * @function Phaser.Geom.Polygon.GetPoints + * @since 3.12.0 + * + * @param {Phaser.Geom.Polygon} polygon - The Polygon to get the points from. + * @param {integer} quantity - The amount of points to return. If a falsey value the quantity will be derived from the `stepRate` instead. + * @param {number} [stepRate] - Sets the quantity by getting the perimeter of the Polygon and dividing it by the stepRate. + * @param {array} [output] - An array to insert the points in to. If not provided a new array will be created. + * + * @return {Phaser.Geom.Point[]} An array of Point objects pertaining to the points around the perimeter of the Polygon. + */ +var GetPoints = function (polygon, quantity, stepRate, out) { + if (out === undefined) { out = []; } + + var points = polygon.points; + var perimeter = Perimeter(polygon); + + // If quantity is a falsey value (false, null, 0, undefined, etc) then we calculate it based on the stepRate instead. + if (!quantity) { + quantity = perimeter / stepRate; + } + + for (var i = 0; i < quantity; i++) { + var position = perimeter * (i / quantity); + var accumulatedPerimeter = 0; + + for (var j = 0; j < points.length; j++) { + var pointA = points[j]; + var pointB = points[(j + 1) % points.length]; + var line = new Line( + pointA.x, + pointA.y, + pointB.x, + pointB.y + ); + var length = Length(line); + + if (position < accumulatedPerimeter || position > accumulatedPerimeter + length) { + accumulatedPerimeter += length; + continue; + } + + var point = line.getPoint((position - accumulatedPerimeter) / length); + out.push(point); + + break; + } + } + + return out; +}; + +export default GetPoints; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Perimeter.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Perimeter.js new file mode 100644 index 000000000..054944209 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Perimeter.js @@ -0,0 +1,40 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Length from '../line/Length.js'; +import Line from '../line/Line.js'; + +/** + * Returns the perimeter of the given Polygon. + * + * @function Phaser.Geom.Polygon.Perimeter + * @since 3.12.0 + * + * @param {Phaser.Geom.Polygon} polygon - The Polygon to get the perimeter of. + * + * @return {number} The perimeter of the Polygon. + */ +var Perimeter = function (polygon) { + var points = polygon.points; + var perimeter = 0; + + for (var i = 0; i < points.length; i++) { + var pointA = points[i]; + var pointB = points[(i + 1) % points.length]; + var line = new Line( + pointA.x, + pointA.y, + pointB.x, + pointB.y + ); + + perimeter += Length(line); + } + + return perimeter; +}; + +export default Perimeter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Polygon.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Polygon.d.ts new file mode 100644 index 000000000..465bb38c4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Polygon.d.ts @@ -0,0 +1,213 @@ +import Point from '../point/Point'; +import Rectangle from '../rectangle/Rectangle'; +import { Vector2Like } from '../types'; + +/** + * A Polygon object + * + * The polygon is a closed shape consists of a series of connected straight lines defined by list of ordered points. + * Several formats are supported to define the list of points, check the setTo method for details. + * This is a geometry object allowing you to define and inspect the shape. + * It is not a Game Object, in that you cannot add it to the display list, and it has no texture. + * To render a Polygon you should look at the capabilities of the Graphics class. + */ +declare class Polygon { + /** + * + * @param points List of points defining the perimeter of this Polygon. Several formats are supported: + * - A string containing paired x y values separated by a single space: `'40 0 40 20 100 20 100 80 40 80 40 100 0 50'` + * - An array of Point objects: `[new Point(x1, y1), ...]` + * - An array of objects with public x y properties: `[obj1, obj2, ...]` + * - An array of paired numbers that represent point coordinates: `[x1,y1, x2,y2, ...]` + * - An array of arrays with two elements representing x/y coordinates: `[[x1, y1], [x2, y2], ...]` + */ + constructor(points?: string | number[] | Vector2Like[]); + + /** + * Create a new polygon which is a copy of the specified polygon + * @param polygon The polygon to create a clone of + */ + static Clone(polygon: Polygon): Polygon; + + /** + * Checks if a point is within the bounds of a Polygon. + * @param polygon The Polygon to check against. + * @param x The X coordinate of the point to check. + * @param y The Y coordinate of the point to check. + */ + static Contains(polygon: Polygon, x: number, y: number): boolean; + + /** + * Checks the given Point again the Polygon to see if the Point lays within its vertices. + * @param polygon The Polygon to check. + * @param point The Point to check if it's within the Polygon. + */ + static ContainsPoint(polygon: Polygon, point: Point): boolean; + + /** + * This module implements a modified ear slicing algorithm, optimized by z-order curve hashing and extended to + * handle holes, twisted polygons, degeneracies and self-intersections in a way that doesn't guarantee correctness + * of triangulation, but attempts to always produce acceptable results for practical data. + * + * Example: + * + * ```javascript + * const triangles = Polygon.Earcut([10,0, 0,50, 60,60, 70,10]); // returns [1,0,3, 3,2,1] + * ``` + * + * Each group of three vertex indices in the resulting array forms a triangle. + * + * ```javascript + * // triangulating a polygon with a hole + * earcut([0,0, 100,0, 100,100, 0,100, 20,20, 80,20, 80,80, 20,80], [4]); + * // [3,0,4, 5,4,0, 3,4,7, 5,0,1, 2,3,7, 6,5,1, 2,7,6, 6,1,2] + * + * // triangulating a polygon with 3d coords + * earcut([10,0,1, 0,50,2, 60,60,3, 70,10,4], null, 3); + * // [1,0,3, 3,2,1] + * ``` + * + * If you pass a single vertex as a hole, Earcut treats it as a Steiner point. + * + * If your input is a multi-dimensional array (e.g. GeoJSON Polygon), you can convert it to the format + * expected by Earcut with `Polygon.Earcut.flatten`: + * + * ```javascript + * var data = earcut.flatten(geojson.geometry.coordinates); + * var triangles = earcut(data.vertices, data.holes, data.dimensions); + * ``` + * + * After getting a triangulation, you can verify its correctness with `Polygon.Earcut.deviation`: + * + * ```javascript + * var deviation = earcut.deviation(vertices, holes, dimensions, triangles); + * ``` + * Returns the relative difference between the total area of triangles and the area of the input polygon. + * 0 means the triangulation is fully correct. + * + * For more information see https://github.com/mapbox/earcut + * @param data A flat array of vertex coordinate, like [x0,y0, x1,y1, x2,y2, ...] + * @param holeIndices An array of hole indices if any (e.g. [5, 8] for a 12-vertex input would mean one hole with vertices 5–7 and another with 8–11). + * @param dimensions The number of coordinates per vertex in the input array (2 by default). Default 2. + */ + static Earcut(data: number[], holeIndices?: number[], dimensions?: number): number[]; + + /** + * Calculates the bounding AABB rectangle of a polygon. + * @param polygon The polygon that should be calculated. + * @param out The rectangle or object that has x, y, width, and height properties to store the result. Optional. + */ + static GetAABB(polygon: Polygon, out?: O): O; + + /** + * Stores all of the points of a Polygon into a flat array of numbers following the sequence [ x,y, x,y, x,y ], + * i.e. each point of the Polygon, in the order it's defined, corresponds to two elements of the resultant + * array for the point's X and Y coordinate. + * @param polygon The Polygon whose points to export. + * @param output An array to which the points' coordinates should be appended. + */ + static GetNumberArray(polygon: Polygon, output?: O): O; + + /** + * Returns an array of Point objects containing the coordinates of the points around the perimeter of the Polygon, + * based on the given quantity or stepRate values. + * @param polygon The Polygon to get the points from. + * @param quantity The amount of points to return. If a falsey value the quantity will be derived from the `stepRate` instead. + * @param stepRate Sets the quantity by getting the perimeter of the Polygon and dividing it by the stepRate. + * @param output An array to insert the points in to. If not provided a new array will be created. + */ + static GetPoints(polygon: Polygon, quantity: number, stepRate?: number, output?: any[]): Point[]; + + /** + * Returns the perimeter of the given Polygon. + * @param polygon The Polygon to get the perimeter of. + */ + static Perimeter(polygon: Polygon): number; + + /** + * The geometry constant type of this object: `GEOM_CONST.POLYGON`. + * Used for fast type comparisons. + */ + readonly type: number; + + /** + * The area of this Polygon. + */ + area: number; + + /** + * An array of number pair objects that make up this polygon. I.e. [ {x,y}, {x,y}, {x,y} ] + */ + points: Point[]; + + /** + * Check to see if the Polygon contains the given x / y coordinates. + * @param x The x coordinate to check within the polygon. + * @param y The y coordinate to check within the polygon. + */ + contains(x: number, y: number): boolean; + + /** + * Sets this Polygon to the given points. + * + * The points can be set from a variety of formats: + * + * - A string containing paired values separated by a single space: `'40 0 40 20 100 20 100 80 40 80 40 100 0 50'` + * - An array of Point objects: `[new Point(x1, y1), ...]` + * - An array of objects with public x/y properties: `[obj1, obj2, ...]` + * - An array of paired numbers that represent point coordinates: `[x1,y1, x2,y2, ...]` + * - An array of arrays with two elements representing x/y coordinates: `[[x1, y1], [x2, y2], ...]` + * + * `setTo` may also be called without any arguments to remove all points. + * @param points Points defining the perimeter of this polygon. Please check function description above for the different supported formats. + */ + setTo(points?: string | number[] | Vector2Like[]): this; + + /** + * Calculates the area of the Polygon. This is available in the property Polygon.area + */ + calculateArea(): number; + + /** + * Returns an array of Point objects containing the coordinates of the points around the perimeter of the Polygon, + * based on the given quantity or stepRate values. + * @param quantity The amount of points to return. If a falsey value the quantity will be derived from the `stepRate` instead. + * @param stepRate Sets the quantity by getting the perimeter of the Polygon and dividing it by the stepRate. + * @param output An array to insert the points in to. If not provided a new array will be created. + */ + getPoints(quantity: number, stepRate?: number, output?: O): O; + + /** + * Reverses the order of the points of a Polygon. + * @param polygon The Polygon to modify. + */ + static Reverse(polygon: O): O; + + /** + * Takes a Polygon object and simplifies the points by running them through a combination of + * Douglas-Peucker and Radial Distance algorithms. Simplification dramatically reduces the number of + * points in a polygon while retaining its shape, giving a huge performance boost when processing + * it and also reducing visual noise. + * @param polygon The polygon to be simplified. The polygon will be modified in-place and returned. + * @param tolerance Affects the amount of simplification (in the same metric as the point coordinates). Default 1. + * @param highestQuality Excludes distance-based preprocessing step which leads to highest quality simplification but runs ~10-20 times slower. Default false. + */ + static Simplify(polygon: O, tolerance?: number, highestQuality?: boolean): O; + + /** + * Takes a Polygon object and applies Chaikin's smoothing algorithm on its points. + * @param polygon The polygon to be smoothed. The polygon will be modified in-place and returned. + */ + static Smooth(polygon: O): O; + + /** + * Tranlates the points of the given Polygon. + * @param polygon The Polygon to modify. + * @param x The amount to horizontally translate the points by. + * @param y The amount to vertically translate the points by. + */ + static Translate(polygon: O, x: number, y: number): O; + +} + +export default Polygon; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Polygon.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Polygon.js new file mode 100644 index 000000000..3fdf1ae5f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Polygon.js @@ -0,0 +1,215 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Class from'../../object/Class.js'; +import Contains from'./Contains.js'; +import GetPoints from'./GetPoints.js'; + +/** + * @classdesc + * A Polygon object + * + + * The polygon is a closed shape consists of a series of connected straight lines defined by list of ordered points. + * Several formats are supported to define the list of points, check the setTo method for details. + * This is a geometry object allowing you to define and inspect the shape. + * It is not a Game Object, in that you cannot add it to the display list, and it has no texture. + * To render a Polygon you should look at the capabilities of the Graphics class. + * + * @class Polygon + * @memberof Phaser.Geom + * @constructor + * @since 3.0.0 + * + * @param {Phaser.Geom.Point[]} [points] - List of points defining the perimeter of this Polygon. Several formats are supported: + * - A string containing paired x y values separated by a single space: `'40 0 40 20 100 20 100 80 40 80 40 100 0 50'` + * - An array of Point objects: `[new Phaser.Point(x1, y1), ...]` + * - An array of objects with public x y properties: `[obj1, obj2, ...]` + * - An array of paired numbers that represent point coordinates: `[x1,y1, x2,y2, ...]` + * - An array of arrays with two elements representing x/y coordinates: `[[x1, y1], [x2, y2], ...]` + */ +var Polygon = new Class({ + + initialize: + + function Polygon (points) + { + /** + * The area of this Polygon. + * + * @name Phaser.Geom.Polygon#area + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.area = 0; + + /** + * An array of number pair objects that make up this polygon. I.e. [ {x,y}, {x,y}, {x,y} ] + * + * @name Phaser.Geom.Polygon#points + * @type {Phaser.Geom.Point[]} + * @since 3.0.0 + */ + this.points = []; + + if (points) + { + this.setTo(points); + } + }, + + /** + * Check to see if the Polygon contains the given x / y coordinates. + * + * @method Phaser.Geom.Polygon#contains + * @since 3.0.0 + * + * @param {number} x - The x coordinate to check within the polygon. + * @param {number} y - The y coordinate to check within the polygon. + * + * @return {boolean} `true` if the coordinates are within the polygon, otherwise `false`. + */ + contains: function (x, y) + { + return Contains(this, x, y); + }, + + /** + * Sets this Polygon to the given points. + * + * The points can be set from a variety of formats: + * + * - A string containing paired values separated by a single space: `'40 0 40 20 100 20 100 80 40 80 40 100 0 50'` + * - An array of Point objects: `[new Phaser.Point(x1, y1), ...]` + * - An array of objects with public x/y properties: `[obj1, obj2, ...]` + * - An array of paired numbers that represent point coordinates: `[x1,y1, x2,y2, ...]` + * - An array of arrays with two elements representing x/y coordinates: `[[x1, y1], [x2, y2], ...]` + * + * `setTo` may also be called without any arguments to remove all points. + * + * @method Phaser.Geom.Polygon#setTo + * @since 3.0.0 + * + * @param {array} points - Points defining the perimeter of this polygon. Please check function description above for the different supported formats. + * + * @return {Phaser.Geom.Polygon} This Polygon object. + */ + setTo: function (points) + { + this.area = 0; + this.points = []; + + if (typeof points === 'string') + { + points = points.split(' '); + } + + if (!Array.isArray(points)) + { + return this; + } + + var p; + var y0 = Number.MAX_VALUE; + + // The points argument is an array, so iterate through it + for (var i = 0; i < points.length; i++) + { + p = { x: 0, y: 0 }; + + if (typeof points[i] === 'number' || typeof points[i] === 'string') + { + p.x = parseFloat(points[i]); + p.y = parseFloat(points[i + 1]); + i++; + } + else if (Array.isArray(points[i])) + { + // An array of arrays? + p.x = points[i][0]; + p.y = points[i][1]; + } + else + { + p.x = points[i].x; + p.y = points[i].y; + } + + this.points.push(p); + + // Lowest boundary + if (p.y < y0) + { + y0 = p.y; + } + } + + this.calculateArea(y0); + + return this; + }, + + /** + * Calculates the area of the Polygon. This is available in the property Polygon.area + * + * @method Phaser.Geom.Polygon#calculateArea + * @since 3.0.0 + * + * @return {number} The area of the polygon. + */ + calculateArea: function () + { + if (this.points.length < 3) + { + this.area = 0; + + return this.area; + } + + var sum = 0; + var p1; + var p2; + + for (var i = 0; i < this.points.length - 1; i++) + { + p1 = this.points[i]; + p2 = this.points[i + 1]; + + sum += (p2.x - p1.x) * (p1.y + p2.y); + } + + p1 = this.points[0]; + p2 = this.points[this.points.length - 1]; + + sum += (p1.x - p2.x) * (p2.y + p1.y); + + this.area = -sum * 0.5; + + return this.area; + }, + + /** + * Returns an array of Point objects containing the coordinates of the points around the perimeter of the Polygon, + * based on the given quantity or stepRate values. + * + * @method Phaser.Geom.Polygon#getPoints + * @since 3.12.0 + * + * @param {integer} quantity - The amount of points to return. If a falsey value the quantity will be derived from the `stepRate` instead. + * @param {number} [stepRate] - Sets the quantity by getting the perimeter of the Polygon and dividing it by the stepRate. + * @param {array} [output] - An array to insert the points in to. If not provided a new array will be created. + * + * @return {Phaser.Geom.Point[]} An array of Point objects pertaining to the points around the perimeter of the Polygon. + */ + getPoints: function (quantity, step, output) + { + return GetPoints(this, quantity, step, output); + } + +}); + +export default Polygon; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Reverse.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Reverse.js new file mode 100644 index 000000000..1595881c4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Reverse.js @@ -0,0 +1,26 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Reverses the order of the points of a Polygon. + * + * @function Phaser.Geom.Polygon.Reverse + * @since 3.0.0 + * + * @generic {Phaser.Geom.Polygon} O - [polygon,$return] + * + * @param {Phaser.Geom.Polygon} polygon - The Polygon to modify. + * + * @return {Phaser.Geom.Polygon} The modified Polygon. + */ +var Reverse = function (polygon) +{ + polygon.points.reverse(); + + return polygon; +}; + +export default Reverse; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Smooth.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Smooth.js new file mode 100644 index 000000000..c08ff6dc0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Smooth.js @@ -0,0 +1,70 @@ +/** + * @author Richard Davey + * @author Igor Ognichenko + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * @ignore + */ +var copy = function (out, a) +{ + out[0] = a[0]; + out[1] = a[1]; + + return out; +}; + +/** + * Takes a Polygon object and applies Chaikin's smoothing algorithm on its points. + * + * @function Phaser.Geom.Polygon.Smooth + * @since 3.13.0 + * + * @generic {Phaser.Geom.Polygon} O - [polygon,$return] + * + * @param {Phaser.Geom.Polygon} polygon - The polygon to be smoothed. The polygon will be modified in-place and returned. + * + * @return {Phaser.Geom.Polygon} The input polygon. + */ +var Smooth = function (polygon) +{ + var i; + var points = []; + var data = polygon.points; + + for (i = 0; i < data.length; i++) + { + points.push([ data[i].x, data[i].y ]); + } + + var output = []; + + if (points.length > 0) + { + output.push(copy([ 0, 0 ], points[0])); + } + + for (i = 0; i < points.length - 1; i++) + { + var p0 = points[i]; + var p1 = points[i + 1]; + var p0x = p0[0]; + var p0y = p0[1]; + var p1x = p1[0]; + var p1y = p1[1]; + + output.push([ 0.85 * p0x + 0.15 * p1x, 0.85 * p0y + 0.15 * p1y ]); + output.push([ 0.15 * p0x + 0.85 * p1x, 0.15 * p0y + 0.85 * p1y ]); + } + + if (points.length > 1) + { + output.push(copy([ 0, 0 ], points[points.length - 1])); + } + + return polygon.setTo(output); +}; + +export default Smooth; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/index.js new file mode 100644 index 000000000..5770a630c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/index.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Polygon from './Polygon.js'; +import Clone from './Clone.js'; +import Contains from './Contains.js'; +import ContainsPoint from './ContainsPoint.js'; +import GetAABB from './GetAABB.js'; +import GetNumberArray from './GetNumberArray.js'; +import GetPoints from './GetPoints.js'; +import Perimeter from './Perimeter.js'; +import Reverse from './Reverse.js'; +import Smooth from './Smooth.js'; + +Polygon.Clone = Clone; +Polygon.Contains = Contains; +Polygon.ContainsPoint = ContainsPoint; +Polygon.GetAABB = GetAABB; +Polygon.GetNumberArray = GetNumberArray; +Polygon.GetPoints = GetPoints; +Polygon.Perimeter = Perimeter; +Polygon.Reverse = Reverse; +Polygon.Smooth = Smooth; + +export default Polygon; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Area.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Area.js new file mode 100644 index 000000000..999c0674e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Area.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculates the area of the given Rectangle object. + * + * @function Phaser.Geom.Rectangle.Area + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rect - The rectangle to calculate the area of. + * + * @return {number} The area of the Rectangle object. + */ +var Area = function (rect) +{ + return rect.width * rect.height; +}; + +export default Area; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Ceil.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Ceil.js new file mode 100644 index 000000000..b92154f3b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Ceil.js @@ -0,0 +1,27 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Rounds a Rectangle's position up to the smallest integer greater than or equal to each current coordinate. + * + * @function Phaser.Geom.Rectangle.Ceil + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [rect,$return] + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle to adjust. + * + * @return {Phaser.Geom.Rectangle} The adjusted Rectangle. + */ +var Ceil = function (rect) +{ + rect.x = Math.ceil(rect.x); + rect.y = Math.ceil(rect.y); + + return rect; +}; + +export default Ceil; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/CeilAll.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/CeilAll.js new file mode 100644 index 000000000..47dcc224b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/CeilAll.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Rounds a Rectangle's position and size up to the smallest integer greater than or equal to each respective value. + * + * @function Phaser.Geom.Rectangle.CeilAll + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [rect,$return] + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle to modify. + * + * @return {Phaser.Geom.Rectangle} The modified Rectangle. + */ +var CeilAll = function (rect) +{ + rect.x = Math.ceil(rect.x); + rect.y = Math.ceil(rect.y); + rect.width = Math.ceil(rect.width); + rect.height = Math.ceil(rect.height); + + return rect; +}; + +export default CeilAll; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/CenterOn.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/CenterOn.js new file mode 100644 index 000000000..e4d89fde9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/CenterOn.js @@ -0,0 +1,31 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +// Centers this Rectangle so that the center coordinates match the given x and y values. + +/** + * Moves the top-left corner of a Rectangle so that its center is at the given coordinates. + * + * @function Phaser.Geom.Rectangle.CenterOn + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [rect,$return] + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle to be centered. + * @param {number} x - The X coordinate of the Rectangle's center. + * @param {number} y - The Y coordinate of the Rectangle's center. + * + * @return {Phaser.Geom.Rectangle} The centered rectangle. + */ +var CenterOn = function (rect, x, y) +{ + rect.x = x - (rect.width / 2); + rect.y = y - (rect.height / 2); + + return rect; +}; + +export default CenterOn; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Clone.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Clone.js new file mode 100644 index 000000000..e0ec11386 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Clone.js @@ -0,0 +1,23 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Rectangle from './Rectangle.js'; + +/** + * Creates a new Rectangle which is identical to the given one. + * + * @function Phaser.Geom.Rectangle.Clone + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} source - The Rectangle to clone. + * + * @return {Phaser.Geom.Rectangle} The newly created Rectangle, which is separate from the given one. + */ +var Clone = function (source) { + return new Rectangle(source.x, source.y, source.width, source.height); +}; + +export default Clone; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Contains.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Contains.js new file mode 100644 index 000000000..4b09eae1f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Contains.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Checks if a given point is inside a Rectangle's bounds. + * + * @function Phaser.Geom.Rectangle.Contains + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle to check. + * @param {number} x - The X coordinate of the point to check. + * @param {number} y - The Y coordinate of the point to check. + * + * @return {boolean} `true` if the point is within the Rectangle's bounds, otherwise `false`. + */ +var Contains = function (rect, x, y) +{ + if (rect.width <= 0 || rect.height <= 0) + { + return false; + } + + return (rect.x <= x && rect.x + rect.width >= x && rect.y <= y && rect.y + rect.height >= y); +}; + +export default Contains; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/ContainsPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/ContainsPoint.js new file mode 100644 index 000000000..1ebe1a62a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/ContainsPoint.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Contains from './Contains.js'; + +/** + * Determines whether the specified point is contained within the rectangular region defined by this Rectangle object. + * + * @function Phaser.Geom.Rectangle.ContainsPoint + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle object. + * @param {Phaser.Geom.Point} point - The point object to be checked. Can be a Phaser Point object or any object with x and y values. + * + * @return {boolean} A value of true if the Rectangle object contains the specified point, otherwise false. + */ +var ContainsPoint = function (rect, point) { + return Contains(rect, point.x, point.y); +}; + +export default ContainsPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/ContainsRect.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/ContainsRect.js new file mode 100644 index 000000000..8bfeb5787 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/ContainsRect.js @@ -0,0 +1,34 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Tests if one rectangle fully contains another. + * + * @function Phaser.Geom.Rectangle.ContainsRect + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rectA - The first rectangle. + * @param {Phaser.Geom.Rectangle} rectB - The second rectangle. + * + * @return {boolean} True only if rectA fully contains rectB. + */ +var ContainsRect = function (rectA, rectB) +{ + // Volume check (if rectB volume > rectA then rectA cannot contain it) + if ((rectB.width * rectB.height) > (rectA.width * rectA.height)) + { + return false; + } + + return ( + (rectB.x > rectA.x && rectB.x < rectA.right) && + (rectB.right > rectA.x && rectB.right < rectA.right) && + (rectB.y > rectA.y && rectB.y < rectA.bottom) && + (rectB.bottom > rectA.y && rectB.bottom < rectA.bottom) + ); +}; + +export default ContainsRect; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/CopyFrom.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/CopyFrom.js new file mode 100644 index 000000000..c1c0dfa4b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/CopyFrom.js @@ -0,0 +1,25 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Copy the values of one Rectangle to a destination Rectangle. + * + * @function Phaser.Geom.Rectangle.CopyFrom + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [dest,$return] + * + * @param {Phaser.Geom.Rectangle} source - The source Rectangle to copy the values from. + * @param {Phaser.Geom.Rectangle} dest - The destination Rectangle to copy the values to. + * + * @return {Phaser.Geom.Rectangle} The destination Rectangle. + */ +var CopyFrom = function (source, dest) +{ + return dest.setTo(source.x, source.y, source.width, source.height); +}; + +export default CopyFrom; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Decompose.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Decompose.js new file mode 100644 index 000000000..499122c41 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Decompose.js @@ -0,0 +1,31 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Create an array of points for each corner of a Rectangle + * If an array is specified, each point object will be added to the end of the array, otherwise a new array will be created. + * + * @function Phaser.Geom.Rectangle.Decompose + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle object to be decomposed. + * @param {array} [out] - If provided, each point will be added to this array. + * + * @return {array} Will return the array you specified or a new array containing the points of the Rectangle. + */ +var Decompose = function (rect, out) +{ + if (out === undefined) { out = []; } + + out.push({ x: rect.x, y: rect.y }); + out.push({ x: rect.right, y: rect.y }); + out.push({ x: rect.right, y: rect.bottom }); + out.push({ x: rect.x, y: rect.bottom }); + + return out; +}; + +export default Decompose; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Equals.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Equals.js new file mode 100644 index 000000000..1319aa5a1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Equals.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Compares the `x`, `y`, `width` and `height` properties of two rectangles. + * + * @function Phaser.Geom.Rectangle.Equals + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rect - Rectangle A + * @param {Phaser.Geom.Rectangle} toCompare - Rectangle B + * + * @return {boolean} `true` if the rectangles' properties are an exact match, otherwise `false`. + */ +var Equals = function (rect, toCompare) +{ + return ( + rect.x === toCompare.x && + rect.y === toCompare.y && + rect.width === toCompare.width && + rect.height === toCompare.height + ); +}; + +export default Equals; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FitInside.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FitInside.js new file mode 100644 index 000000000..22831d408 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FitInside.js @@ -0,0 +1,44 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import GetAspectRatio from './GetAspectRatio.js'; + +/** + * Adjusts the target rectangle, changing its width, height and position, + * so that it fits inside the area of the source rectangle, while maintaining its original + * aspect ratio. + * + * Unlike the `FitOutside` function, there may be some space inside the source area not covered. + * + * @function Phaser.Geom.Rectangle.FitInside + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [target,$return] + * + * @param {Phaser.Geom.Rectangle} target - The target rectangle to adjust. + * @param {Phaser.Geom.Rectangle} source - The source rectangle to envelop the target in. + * + * @return {Phaser.Geom.Rectangle} The modified target rectangle instance. + */ +var FitInside = function (target, source) { + var ratio = GetAspectRatio(target); + + if (ratio < GetAspectRatio(source)) { + // Taller than Wide + target.setSize(source.height * ratio, source.height); + } + else { + // Wider than Tall + target.setSize(source.width, source.width / ratio); + } + + return target.setPosition( + source.centerX - (target.width / 2), + source.centerY - (target.height / 2) + ); +}; + +export default FitInside; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FitOutside.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FitOutside.js new file mode 100644 index 000000000..73624e891 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FitOutside.js @@ -0,0 +1,44 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import GetAspectRatio from './GetAspectRatio.js'; + +/** + * Adjusts the target rectangle, changing its width, height and position, + * so that it fully covers the area of the source rectangle, while maintaining its original + * aspect ratio. + * + * Unlike the `FitInside` function, the target rectangle may extend further out than the source. + * + * @function Phaser.Geom.Rectangle.FitOutside + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [target,$return] + * + * @param {Phaser.Geom.Rectangle} target - The target rectangle to adjust. + * @param {Phaser.Geom.Rectangle} source - The source rectangle to envlope the target in. + * + * @return {Phaser.Geom.Rectangle} The modified target rectangle instance. + */ +var FitOutside = function (target, source) { + var ratio = GetAspectRatio(target); + + if (ratio > GetAspectRatio(source)) { + // Wider than Tall + target.setSize(source.height * ratio, source.height); + } + else { + // Taller than Wide + target.setSize(source.width, source.width / ratio); + } + + return target.setPosition( + source.centerX - target.width / 2, + source.centerY - target.height / 2 + ); +}; + +export default FitOutside; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Floor.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Floor.js new file mode 100644 index 000000000..0e417000b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Floor.js @@ -0,0 +1,27 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Rounds down (floors) the top left X and Y co-ordinates of the given Rectangle to the largest integer less than or equal to them + * + * @function Phaser.Geom.Rectangle.Floor + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [rect,$return] + * + * @param {Phaser.Geom.Rectangle} rect - The rectangle to floor the top left X and Y co-ordinates of + * + * @return {Phaser.Geom.Rectangle} The rectangle that was passed to this function with its co-ordinates floored. + */ +var Floor = function (rect) +{ + rect.x = Math.floor(rect.x); + rect.y = Math.floor(rect.y); + + return rect; +}; + +export default Floor; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FloorAll.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FloorAll.js new file mode 100644 index 000000000..8ecc9f788 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FloorAll.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Rounds a Rectangle's position and size down to the largest integer less than or equal to each current coordinate or dimension. + * + * @function Phaser.Geom.Rectangle.FloorAll + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [rect,$return] + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle to adjust. + * + * @return {Phaser.Geom.Rectangle} The adjusted Rectangle. + */ +var FloorAll = function (rect) +{ + rect.x = Math.floor(rect.x); + rect.y = Math.floor(rect.y); + rect.width = Math.floor(rect.width); + rect.height = Math.floor(rect.height); + + return rect; +}; + +export default FloorAll; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FromPoints.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FromPoints.js new file mode 100644 index 000000000..0ab56928d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FromPoints.js @@ -0,0 +1,74 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Rectangle from './Rectangle.js'; + +// points is an array of Point-like objects, +// either 2 dimensional arrays, or objects with public x/y properties: +// var points = [ +// [100, 200], +// [200, 400], +// { x: 30, y: 60 } +// ] + +/** + * Constructs new Rectangle or repositions and resizes an existing Rectangle so that all of the given points are on or within its bounds. + * + * @function Phaser.Geom.Rectangle.FromPoints + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [out,$return] + * + * @param {array} points - An array of points (either arrays with two elements corresponding to the X and Y coordinate or an object with public `x` and `y` properties) which should be surrounded by the Rectangle. + * @param {Phaser.Geom.Rectangle} [out] - Optional Rectangle to adjust. + * + * @return {Phaser.Geom.Rectangle} The adjusted `out` Rectangle, or a new Rectangle if none was provided. + */ +var FromPoints = function (points, out) { + if (out === undefined) { out = new Rectangle(); } + + if (points.length === 0) { + return out; + } + + var minX = Number.MAX_VALUE; + var minY = Number.MAX_VALUE; + + var maxX = Number.MIN_SAFE_INTEGER; + var maxY = Number.MIN_SAFE_INTEGER; + + var p; + var px; + var py; + + for (var i = 0; i < points.length; i++) { + p = points[i]; + + if (Array.isArray(p)) { + px = p[0]; + py = p[1]; + } + else { + px = p.x; + py = p.y; + } + + minX = Math.min(minX, px); + minY = Math.min(minY, py); + + maxX = Math.max(maxX, px); + maxY = Math.max(maxY, py); + } + + out.x = minX; + out.y = minY; + out.width = maxX - minX; + out.height = maxY - minY; + + return out; +}; + +export default FromPoints; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetAspectRatio.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetAspectRatio.js new file mode 100644 index 000000000..69fba4f8e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetAspectRatio.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculates the width/height ratio of a rectangle. + * + * @function Phaser.Geom.Rectangle.GetAspectRatio + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rect - The rectangle. + * + * @return {number} The width/height ratio of the rectangle. + */ +var GetAspectRatio = function (rect) +{ + return (rect.height === 0) ? NaN : rect.width / rect.height; +}; + +export default GetAspectRatio; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetCenter.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetCenter.js new file mode 100644 index 000000000..e478522e1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetCenter.js @@ -0,0 +1,31 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +/** + * Returns the center of a Rectangle as a Point. + * + * @function Phaser.Geom.Rectangle.GetCenter + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle to get the center of. + * @param {(Phaser.Geom.Point|object)} [out] - Optional point-like object to update with the center coordinates. + * + * @return {(Phaser.Geom.Point|object)} The modified `out` object, or a new Point if none was provided. + */ +var GetCenter = function (rect, out) { + if (out === undefined) { out = new Point(); } + + out.x = rect.centerX; + out.y = rect.centerY; + + return out; +}; + +export default GetCenter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetPoint.js new file mode 100644 index 000000000..3ccd11f2f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetPoint.js @@ -0,0 +1,64 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Perimeter from './Perimeter.js'; +import Point from '../point/Point'; + +/** + * Position is a value between 0 and 1 where 0 = the top-left of the rectangle and 0.5 = the bottom right. + * + * @function Phaser.Geom.Rectangle.GetPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Rectangle} rectangle - [description] + * @param {number} position - [description] + * @param {(Phaser.Geom.Point|object)} [out] - [description] + * + * @return {Phaser.Geom.Point} [description] + */ +var GetPoint = function (rectangle, position, out) { + if (out === undefined) { out = new Point(); } + + if (position <= 0 || position >= 1) { + out.x = rectangle.x; + out.y = rectangle.y; + + return out; + } + + var p = Perimeter(rectangle) * position; + + if (position > 0.5) { + p -= (rectangle.width + rectangle.height); + + if (p <= rectangle.width) { + // Face 3 + out.x = rectangle.right - p; + out.y = rectangle.bottom; + } + else { + // Face 4 + out.x = rectangle.x; + out.y = rectangle.bottom - (p - rectangle.width); + } + } + else if (p <= rectangle.width) { + // Face 1 + out.x = rectangle.x + p; + out.y = rectangle.y; + } + else { + // Face 2 + out.x = rectangle.right; + out.y = rectangle.y + (p - rectangle.width); + } + + return out; +}; + +export default GetPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetPoints.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetPoints.js new file mode 100644 index 000000000..9b37d2e81 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetPoints.js @@ -0,0 +1,45 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import GetPoint from './GetPoint.js'; +import Perimeter from './Perimeter.js'; + +// Return an array of points from the perimeter of the rectangle +// each spaced out based on the quantity or step required + +/** + * Return an array of points from the perimeter of the rectangle, each spaced out based on the quantity or step required. + * + * @function Phaser.Geom.Rectangle.GetPoints + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point[]} O - [out,$return] + * + * @param {Phaser.Geom.Rectangle} rectangle - The Rectangle object to get the points from. + * @param {number} step - Step between points. Used to calculate the number of points to return when quantity is falsy. Ignored if quantity is positive. + * @param {integer} quantity - The number of evenly spaced points from the rectangles perimeter to return. If falsy, step param will be used to calculate the number of points. + * @param {(array|Phaser.Geom.Point[])} [out] - An optional array to store the points in. + * + * @return {(array|Phaser.Geom.Point[])} An array of Points from the perimeter of the rectangle. + */ +var GetPoints = function (rectangle, quantity, stepRate, out) { + if (out === undefined) { out = []; } + + // If quantity is a falsey value (false, null, 0, undefined, etc) then we calculate it based on the stepRate instead. + if (!quantity) { + quantity = Perimeter(rectangle) / stepRate; + } + + for (var i = 0; i < quantity; i++) { + var position = i / quantity; + + out.push(GetPoint(rectangle, position)); + } + + return out; +}; + +export default GetPoints; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetSize.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetSize.js new file mode 100644 index 000000000..19afe6759 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetSize.js @@ -0,0 +1,33 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + + +/** + * The size of the Rectangle object, expressed as a Point object + * with the values of the width and height properties. + * + * @function Phaser.Geom.Rectangle.GetSize + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Rectangle} rect - [description] + * @param {(Phaser.Geom.Point|object)} [out] - [description] + * + * @return {(Phaser.Geom.Point|object)} [description] + */ +var GetSize = function (rect, out) { + if (out === undefined) { out = new Point(); } + + out.x = rect.width; + out.y = rect.height; + + return out; +}; + +export default GetSize; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Inflate.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Inflate.js new file mode 100644 index 000000000..063610236 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Inflate.js @@ -0,0 +1,35 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import CenterOn from './CenterOn.js'; + + +/** + * Increases the size of a Rectangle by a specified amount. + * + * The center of the Rectangle stays the same. The amounts are added to each side, so the actual increase in width or height is two times bigger than the respective argument. + * + * @function Phaser.Geom.Rectangle.Inflate + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [rect,$return] + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle to inflate. + * @param {number} x - How many pixels the left and the right side should be moved by horizontally. + * @param {number} y - How many pixels the top and the bottom side should be moved by vertically. + * + * @return {Phaser.Geom.Rectangle} The inflated Rectangle. + */ +var Inflate = function (rect, x, y) { + var cx = rect.centerX; + var cy = rect.centerY; + + rect.setSize(rect.width + (x * 2), rect.height + (y * 2)); + + return CenterOn(rect, cx, cy); +}; + +export default Inflate; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Intersection.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Intersection.js new file mode 100644 index 000000000..4b1503bbb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Intersection.js @@ -0,0 +1,42 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Rectangle from './Rectangle.js'; +import Intersects from '../intersects/RectangleToRectangle.js'; + +/** + * Takes two Rectangles and first checks to see if they intersect. + * If they intersect it will return the area of intersection in the `out` Rectangle. + * If they do not intersect, the `out` Rectangle will have a width and height of zero. + * + * @function Phaser.Geom.Rectangle.Intersection + * @since 3.11.0 + * + * @generic {Phaser.Geom.Rectangle} O - [rect,$return] + * + * @param {Phaser.Geom.Rectangle} rectA - The first Rectangle to get the intersection from. + * @param {Phaser.Geom.Rectangle} rectB - The second Rectangle to get the intersection from. + * @param {Phaser.Geom.Rectangle} [out] - A Rectangle to store the intersection results in. + * + * @return {Phaser.Geom.Rectangle} The intersection result. If the width and height are zero, no intersection occurred. + */ +var Intersection = function (rectA, rectB, out) { + if (out === undefined) { out = new Rectangle(); } + + if (Intersects(rectA, rectB)) { + out.x = Math.max(rectA.x, rectB.x); + out.y = Math.max(rectA.y, rectB.y); + out.width = Math.min(rectA.right, rectB.right) - out.x; + out.height = Math.min(rectA.bottom, rectB.bottom) - out.y; + } + else { + out.setEmpty(); + } + + return out; +}; + +export default Intersection; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MarchingAnts.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MarchingAnts.js new file mode 100644 index 000000000..7ff36ad0a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MarchingAnts.js @@ -0,0 +1,103 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Perimeter from './Perimeter.js'; +import Point from '../point/Point.js'; + + +/** + * Return an array of points from the perimeter of the rectangle + * each spaced out based on the quantity or step required + * + * @function Phaser.Geom.Rectangle.MarchingAnts + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point[]} O - [out,$return] + * + * @param {Phaser.Geom.Rectangle} rect - [description] + * @param {number} step - [description] + * @param {integer} quantity - [description] + * @param {(array|Phaser.Geom.Point[])} [out] - [description] + * + * @return {(array|Phaser.Geom.Point[])} [description] + */ +var MarchingAnts = function (rect, step, quantity, out) { + if (out === undefined) { out = []; } + + if (!step && !quantity) { + // Bail out + return out; + } + + // If step is a falsey value (false, null, 0, undefined, etc) then we calculate + // it based on the quantity instead, otherwise we always use the step value + if (!step) { + step = Perimeter(rect) / quantity; + } + else { + quantity = Math.round(Perimeter(rect) / step); + } + + var x = rect.x; + var y = rect.y; + var face = 0; + + // Loop across each face of the rectangle + + for (var i = 0; i < quantity; i++) { + out.push(new Point(x, y)); + + switch (face) { + + // Top face + case 0: + x += step; + + if (x >= rect.right) { + face = 1; + y += (x - rect.right); + x = rect.right; + } + break; + + // Right face + case 1: + y += step; + + if (y >= rect.bottom) { + face = 2; + x -= (y - rect.bottom); + y = rect.bottom; + } + break; + + // Bottom face + case 2: + x -= step; + + if (x <= rect.left) { + face = 3; + y -= (rect.left - x); + x = rect.left; + } + break; + + // Left face + case 3: + y -= step; + + if (y <= rect.top) { + face = 0; + y = rect.top; + } + break; + } + } + + return out; +}; + +export default MarchingAnts; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MergePoints.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MergePoints.js new file mode 100644 index 000000000..5952b0454 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MergePoints.js @@ -0,0 +1,43 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Merges a Rectangle with a list of points by repositioning and/or resizing it such that all points are located on or within its bounds. + * + * @function Phaser.Geom.Rectangle.MergePoints + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [target,$return] + * + * @param {Phaser.Geom.Rectangle} target - The Rectangle which should be merged. + * @param {Phaser.Geom.Point[]} points - An array of Points (or any object with public `x` and `y` properties) which should be merged with the Rectangle. + * + * @return {Phaser.Geom.Rectangle} The modified Rectangle. + */ +var MergePoints = function (target, points) +{ + var minX = target.x; + var maxX = target.right; + var minY = target.y; + var maxY = target.bottom; + + for (var i = 0; i < points.length; i++) + { + minX = Math.min(minX, points[i].x); + maxX = Math.max(maxX, points[i].x); + minY = Math.min(minY, points[i].y); + maxY = Math.max(maxY, points[i].y); + } + + target.x = minX; + target.y = minY; + target.width = maxX - minX; + target.height = maxY - minY; + + return target; +}; + +export default MergePoints; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MergeRect.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MergeRect.js new file mode 100644 index 000000000..48bdc1b4f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MergeRect.js @@ -0,0 +1,41 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +// Merges source rectangle into target rectangle and returns target +// Neither rect should have negative widths or heights + +/** + * Merges the source rectangle into the target rectangle and returns the target. + * Neither rectangle should have a negative width or height. + * + * @function Phaser.Geom.Rectangle.MergeRect + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [target,$return] + * + * @param {Phaser.Geom.Rectangle} target - Target rectangle. Will be modified to include source rectangle. + * @param {Phaser.Geom.Rectangle} source - Rectangle that will be merged into target rectangle. + * + * @return {Phaser.Geom.Rectangle} Modified target rectangle that contains source rectangle. + */ +var MergeRect = function (target, source) +{ + var minX = Math.min(target.x, source.x); + var maxX = Math.max(target.right, source.right); + + target.x = minX; + target.width = maxX - minX; + + var minY = Math.min(target.y, source.y); + var maxY = Math.max(target.bottom, source.bottom); + + target.y = minY; + target.height = maxY - minY; + + return target; +}; + +export default MergeRect; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MergeXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MergeXY.js new file mode 100644 index 000000000..6967563b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MergeXY.js @@ -0,0 +1,38 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Merges a Rectangle with a point by repositioning and/or resizing it so that the point is on or within its bounds. + * + * @function Phaser.Geom.Rectangle.MergeXY + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [target,$return] + * + * @param {Phaser.Geom.Rectangle} target - The Rectangle which should be merged and modified. + * @param {number} x - The X coordinate of the point which should be merged. + * @param {number} y - The Y coordinate of the point which should be merged. + * + * @return {Phaser.Geom.Rectangle} The modified `target` Rectangle. + */ +var MergeXY = function (target, x, y) +{ + var minX = Math.min(target.x, x); + var maxX = Math.max(target.right, x); + + target.x = minX; + target.width = maxX - minX; + + var minY = Math.min(target.y, y); + var maxY = Math.max(target.bottom, y); + + target.y = minY; + target.height = maxY - minY; + + return target; +}; + +export default MergeXY; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Offset.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Offset.js new file mode 100644 index 000000000..7d020434b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Offset.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Nudges (translates) the top left corner of a Rectangle by a given offset. + * + * @function Phaser.Geom.Rectangle.Offset + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [rect,$return] + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle to adjust. + * @param {number} x - The distance to move the Rectangle horizontally. + * @param {number} y - The distance to move the Rectangle vertically. + * + * @return {Phaser.Geom.Rectangle} The adjusted Rectangle. + */ +var Offset = function (rect, x, y) +{ + rect.x += x; + rect.y += y; + + return rect; +}; + +export default Offset; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/OffsetPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/OffsetPoint.js new file mode 100644 index 000000000..358e66c75 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/OffsetPoint.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Nudges (translates) the top-left corner of a Rectangle by the coordinates of a point (translation vector). + * + * @function Phaser.Geom.Rectangle.OffsetPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [rect,$return] + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle to adjust. + * @param {(Phaser.Geom.Point|Phaser.Math.Vector2)} point - The point whose coordinates should be used as an offset. + * + * @return {Phaser.Geom.Rectangle} The adjusted Rectangle. + */ +var OffsetPoint = function (rect, point) +{ + rect.x += point.x; + rect.y += point.y; + + return rect; +}; + +export default OffsetPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Overlaps.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Overlaps.js new file mode 100644 index 000000000..2215365c7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Overlaps.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Checks if two Rectangles overlap. If a Rectangle is within another Rectangle, the two will be considered overlapping. Thus, the Rectangles are treated as "solid". + * + * @function Phaser.Geom.Rectangle.Overlaps + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rectA - The first Rectangle to check. + * @param {Phaser.Geom.Rectangle} rectB - The second Rectangle to check. + * + * @return {boolean} `true` if the two Rectangles overlap, `false` otherwise. + */ +var Overlaps = function (rectA, rectB) +{ + return ( + rectA.x < rectB.right && + rectA.right > rectB.x && + rectA.y < rectB.bottom && + rectA.bottom > rectB.y + ); +}; + +export default Overlaps; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Perimeter.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Perimeter.js new file mode 100644 index 000000000..4fe86aead --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Perimeter.js @@ -0,0 +1,22 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculates the perimeter of a Rectangle. + * + * @function Phaser.Geom.Rectangle.Perimeter + * @since 3.0.0 + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle to use. + * + * @return {number} The perimeter of the Rectangle, equal to `(width * 2) + (height * 2)`. + */ +var Perimeter = function (rect) +{ + return 2 * (rect.width + rect.height); +}; + +export default Perimeter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/PerimeterPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/PerimeterPoint.js new file mode 100644 index 000000000..8ff69c4a2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/PerimeterPoint.js @@ -0,0 +1,48 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; +import DegToRad from '../../math/DegToRad.js'; + +/** + * [description] + * + * @function Phaser.Geom.Rectangle.PerimeterPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Rectangle} rectangle - [description] + * @param {integer} angle - [description] + * @param {Phaser.Geom.Point} [out] - [description] + * + * @return {Phaser.Geom.Point} [description] + */ +var PerimeterPoint = function (rectangle, angle, out) { + if (out === undefined) { out = new Point(); } + + angle = DegToRad(angle); + + var s = Math.sin(angle); + var c = Math.cos(angle); + + var dx = (c > 0) ? rectangle.width / 2 : rectangle.width / -2; + var dy = (s > 0) ? rectangle.height / 2 : rectangle.height / -2; + + if (Math.abs(dx * s) < Math.abs(dy * c)) { + dy = (dx * s) / c; + } + else { + dx = (dy * c) / s; + } + + out.x = dx + rectangle.centerX; + out.y = dy + rectangle.centerY; + + return out; +}; + +export default PerimeterPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Random.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Random.js new file mode 100644 index 000000000..306f911c9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Random.js @@ -0,0 +1,31 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +/** + * Returns a random point within a Rectangle. + * + * @function Phaser.Geom.Rectangle.Random + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Rectangle} rect - The Rectangle to return a point from. + * @param {Phaser.Geom.Point} out - The object to update with the point's coordinates. + * + * @return {Phaser.Geom.Point} The modified `out` object, or a new Point if none was provided. + */ +var Random = function (rect, out) { + if (out === undefined) { out = new Point(); } + + out.x = rect.x + (Math.random() * rect.width); + out.y = rect.y + (Math.random() * rect.height); + + return out; +}; + +export default Random; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/RandomOutside.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/RandomOutside.js new file mode 100644 index 000000000..d184fff32 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/RandomOutside.js @@ -0,0 +1,62 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Between from '../../math/Between.js'; +import ContainsRect from './ContainsRect.js'; +import Point from '../point/Point.js'; + +/** + * Calculates a random point that lies within the `outer` Rectangle, but outside of the `inner` Rectangle. + * The inner Rectangle must be fully contained within the outer rectangle. + * + * @function Phaser.Geom.Rectangle.RandomOutside + * @since 3.10.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Rectangle} outer - The outer Rectangle to get the random point within. + * @param {Phaser.Geom.Rectangle} inner - The inner Rectangle to exclude from the returned point. + * @param {Phaser.Geom.Point} [out] - A Point, or Point-like object to store the result in. If not specified, a new Point will be created. + * + * @return {Phaser.Geom.Point} A Point object containing the random values in its `x` and `y` properties. + */ +var RandomOutside = function (outer, inner, out) { + if (out === undefined) { out = new Point(); } + + if (ContainsRect(outer, inner)) { + // Pick a random quadrant + // + // The quadrants don't extend the full widths / heights of the outer rect to give + // us a better uniformed distribution, otherwise you get clumping in the corners where + // the 4 quads would overlap + + switch (Between(0, 3)) { + case 0: // Top + out.x = outer.x + (Math.random() * (inner.right - outer.x)); + out.y = outer.y + (Math.random() * (inner.top - outer.y)); + break; + + case 1: // Bottom + out.x = inner.x + (Math.random() * (outer.right - inner.x)); + out.y = inner.bottom + (Math.random() * (outer.bottom - inner.bottom)); + break; + + case 2: // Left + out.x = outer.x + (Math.random() * (inner.x - outer.x)); + out.y = inner.y + (Math.random() * (outer.bottom - inner.y)); + break; + + case 3: // Right + out.x = inner.right + (Math.random() * (outer.right - inner.right)); + out.y = outer.y + (Math.random() * (inner.bottom - outer.y)); + break; + } + } + + return out; +}; + +export default RandomOutside; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Rectangle.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Rectangle.d.ts new file mode 100644 index 000000000..e394e1719 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Rectangle.d.ts @@ -0,0 +1,465 @@ +import Point from '../point/Point'; +import Line from '../line/Line'; + +/** + * Encapsulates a 2D rectangle defined by its corner point in the top-left and its extends in x (width) and y (height) + */ +declare class Rectangle { + /** + * + * @param x The X coordinate of the top left corner of the Rectangle. Default 0. + * @param y The Y coordinate of the top left corner of the Rectangle. Default 0. + * @param width The width of the Rectangle. Default 0. + * @param height The height of the Rectangle. Default 0. + */ + constructor(x?: number, y?: number, width?: number, height?: number); + + /** + * Calculates the area of the given Rectangle object. + * @param rect The rectangle to calculate the area of. + */ + static Area(rect: Rectangle): number; + + /** + * Rounds a Rectangle's position up to the smallest integer greater than or equal to each current coordinate. + * @param rect The Rectangle to adjust. + */ + static Ceil(rect: O): O; + + /** + * Rounds a Rectangle's position and size up to the smallest integer greater than or equal to each respective value. + * @param rect The Rectangle to modify. + */ + static CeilAll(rect: O): O; + + /** + * Moves the top-left corner of a Rectangle so that its center is at the given coordinates. + * @param rect The Rectangle to be centered. + * @param x The X coordinate of the Rectangle's center. + * @param y The Y coordinate of the Rectangle's center. + */ + static CenterOn(rect: O, x: number, y: number): O; + + /** + * Creates a new Rectangle which is identical to the given one. + * @param source The Rectangle to clone. + */ + static Clone(source: Rectangle): Rectangle; + + /** + * Checks if a given point is inside a Rectangle's bounds. + * @param rect The Rectangle to check. + * @param x The X coordinate of the point to check. + * @param y The Y coordinate of the point to check. + */ + static Contains(rect: Rectangle, x: number, y: number): boolean; + + /** + * Determines whether the specified point is contained within the rectangular region defined by this Rectangle object. + * @param rect The Rectangle object. + * @param point The point object to be checked. Can be a Point object or any object with x and y values. + */ + static ContainsPoint(rect: Rectangle, point: Point): boolean; + + /** + * Tests if one rectangle fully contains another. + * @param rectA The first rectangle. + * @param rectB The second rectangle. + */ + static ContainsRect(rectA: Rectangle, rectB: Rectangle): boolean; + + /** + * Copy the values of one Rectangle to a destination Rectangle. + * @param source The source Rectangle to copy the values from. + * @param dest The destination Rectangle to copy the values to. + */ + static CopyFrom(source: Rectangle, dest: O): O; + + /** + * Create an array of points for each corner of a Rectangle + * If an array is specified, each point object will be added to the end of the array, otherwise a new array will be created. + * @param rect The Rectangle object to be decomposed. + * @param out If provided, each point will be added to this array. + */ + static Decompose(rect: Rectangle, out?: any[]): any[]; + + /** + * Compares the `x`, `y`, `width` and `height` properties of two rectangles. + * @param rect Rectangle A + * @param toCompare Rectangle B + */ + static Equals(rect: Rectangle, toCompare: Rectangle): boolean; + + /** + * Adjusts the target rectangle, changing its width, height and position, + * so that it fits inside the area of the source rectangle, while maintaining its original + * aspect ratio. + * + * Unlike the `FitOutside` function, there may be some space inside the source area not covered. + * @param target The target rectangle to adjust. + * @param source The source rectangle to envelop the target in. + */ + static FitInside(target: O, source: Rectangle): O; + + /** + * Adjusts the target rectangle, changing its width, height and position, + * so that it fully covers the area of the source rectangle, while maintaining its original + * aspect ratio. + * + * Unlike the `FitInside` function, the target rectangle may extend further out than the source. + * @param target The target rectangle to adjust. + * @param source The source rectangle to envelope the target in. + */ + static FitOutside(target: O, source: Rectangle): O; + + /** + * Rounds down (floors) the top left X and Y coordinates of the given Rectangle to the largest integer less than or equal to them + * @param rect The rectangle to floor the top left X and Y coordinates of + */ + static Floor(rect: O): O; + + /** + * Rounds a Rectangle's position and size down to the largest integer less than or equal to each current coordinate or dimension. + * @param rect The Rectangle to adjust. + */ + static FloorAll(rect: O): O; + + /** + * Constructs new Rectangle or repositions and resizes an existing Rectangle so that all of the given points are on or within its bounds. + * @param points An array of points (either arrays with two elements corresponding to the X and Y coordinate or an object with public `x` and `y` properties) which should be surrounded by the Rectangle. + * @param out Optional Rectangle to adjust. + */ + static FromPoints(points: any[], out?: O): O; + + /** + * Create the smallest Rectangle containing two coordinate pairs. + * @param x1 The X coordinate of the first point. + * @param y1 The Y coordinate of the first point. + * @param x2 The X coordinate of the second point. + * @param y2 The Y coordinate of the second point. + * @param out Optional Rectangle to adjust. + */ + static FromXY(x1: number, y1: number, x2: number, y2: number, out?: O): O; + + /** + * Calculates the width/height ratio of a rectangle. + * @param rect The rectangle. + */ + static GetAspectRatio(rect: Rectangle): number; + + /** + * Returns the center of a Rectangle as a Point. + * @param rect The Rectangle to get the center of. + * @param out Optional point-like object to update with the center coordinates. + */ + static GetCenter(rect: Rectangle, out?: O): O; + + /** + * Calculates the coordinates of a point at a certain `position` on the Rectangle's perimeter. + * + * The `position` is a fraction between 0 and 1 which defines how far into the perimeter the point is. + * + * A value of 0 or 1 returns the point at the top left corner of the rectangle, while a value of 0.5 returns the point at the bottom right corner of the rectangle. Values between 0 and 0.5 are on the top or the right side and values between 0.5 and 1 are on the bottom or the left side. + * @param rectangle The Rectangle to get the perimeter point from. + * @param position The normalized distance into the Rectangle's perimeter to return. + * @param out An object to update with the `x` and `y` coordinates of the point. + */ + static GetPoint(rectangle: Rectangle, position: number, out?: O): O; + + /** + * Return an array of points from the perimeter of the rectangle, each spaced out based on the quantity or step required. + * @param rectangle The Rectangle object to get the points from. + * @param step Step between points. Used to calculate the number of points to return when quantity is falsey. Ignored if quantity is positive. + * @param quantity The number of evenly spaced points from the rectangles perimeter to return. If falsey, step param will be used to calculate the number of points. + * @param out An optional array to store the points in. + */ + static GetPoints(rectangle: Rectangle, step: number, quantity: number, out?: O): O; + + /** + * Returns the size of the Rectangle, expressed as a Point object. + * With the value of the `width` as the `x` property and the `height` as the `y` property. + * @param rect The Rectangle to get the size from. + * @param out The Point object to store the size in. If not given, a new Point instance is created. + */ + static GetSize(rect: Rectangle, out?: O): O; + + /** + * Increases the size of a Rectangle by a specified amount. + * + * The center of the Rectangle stays the same. The amounts are added to each side, so the actual increase in width or height is two times bigger than the respective argument. + * @param rect The Rectangle to inflate. + * @param x How many pixels the left and the right side should be moved by horizontally. + * @param y How many pixels the top and the bottom side should be moved by vertically. + */ + static Inflate(rect: O, x: number, y: number): O; + + /** + * Takes two Rectangles and first checks to see if they intersect. + * If they intersect it will return the area of intersection in the `out` Rectangle. + * If they do not intersect, the `out` Rectangle will have a width and height of zero. + * @param rectA The first Rectangle to get the intersection from. + * @param rectB The second Rectangle to get the intersection from. + * @param out A Rectangle to store the intersection results in. + */ + static Intersection(rectA: Rectangle, rectB: Rectangle, out?: Rectangle): O; + + /** + * Returns an array of points from the perimeter of the Rectangle, where each point is spaced out based + * on either the `step` value, or the `quantity`. + * @param rect The Rectangle to get the perimeter points from. + * @param step The distance between each point of the perimeter. Set to `null` if you wish to use the `quantity` parameter instead. + * @param quantity The total number of points to return. The step is then calculated based on the length of the Rectangle, divided by this value. + * @param out An array in which the perimeter points will be stored. If not given, a new array instance is created. + */ + static MarchingAnts(rect: Rectangle, step?: number, quantity?: number, out?: O): O; + + /** + * Merges a Rectangle with a list of points by repositioning and/or resizing it such that all points are located on or within its bounds. + * @param target The Rectangle which should be merged. + * @param points An array of Points (or any object with public `x` and `y` properties) which should be merged with the Rectangle. + */ + static MergePoints(target: O, points: Point[]): O; + + /** + * Merges the source rectangle into the target rectangle and returns the target. + * Neither rectangle should have a negative width or height. + * @param target Target rectangle. Will be modified to include source rectangle. + * @param source Rectangle that will be merged into target rectangle. + */ + static MergeRect(target: O, source: Rectangle): O; + + /** + * Merges a Rectangle with a point by repositioning and/or resizing it so that the point is on or within its bounds. + * @param target The Rectangle which should be merged and modified. + * @param x The X coordinate of the point which should be merged. + * @param y The Y coordinate of the point which should be merged. + */ + static MergeXY(target: O, x: number, y: number): O; + + /** + * Nudges (translates) the top left corner of a Rectangle by a given offset. + * @param rect The Rectangle to adjust. + * @param x The distance to move the Rectangle horizontally. + * @param y The distance to move the Rectangle vertically. + */ + static Offset(rect: O, x: number, y: number): O; + + /** + * Nudges (translates) the top-left corner of a Rectangle by the coordinates of a point (translation vector). + * @param rect The Rectangle to adjust. + * @param point The point whose coordinates should be used as an offset. + */ + static OffsetPoint(rect: O, point: Point): O; + + /** + * Checks if two Rectangles overlap. If a Rectangle is within another Rectangle, the two will be considered overlapping. Thus, the Rectangles are treated as "solid". + * @param rectA The first Rectangle to check. + * @param rectB The second Rectangle to check. + */ + static Overlaps(rectA: Rectangle, rectB: Rectangle): boolean; + + /** + * Calculates the perimeter of a Rectangle. + * @param rect The Rectangle to use. + */ + static Perimeter(rect: Rectangle): number; + + /** + * Returns a Point from the perimeter of a Rectangle based on the given angle. + * @param rectangle The Rectangle to get the perimeter point from. + * @param angle The angle of the point, in degrees. + * @param out The Point object to store the position in. If not given, a new Point instance is created. + */ + static PerimeterPoint(rectangle: Rectangle, angle: number, out?: O): O; + + /** + * Returns a random point within a Rectangle. + * @param rect The Rectangle to return a point from. + * @param out The object to update with the point's coordinates. + */ + static Random(rect: Rectangle, out: O): O; + + /** + * Calculates a random point that lies within the `outer` Rectangle, but outside of the `inner` Rectangle. + * The inner Rectangle must be fully contained within the outer rectangle. + * @param outer The outer Rectangle to get the random point within. + * @param inner The inner Rectangle to exclude from the returned point. + * @param out A Point, or Point-like object to store the result in. If not specified, a new Point will be created. + */ + static RandomOutside(outer: Rectangle, inner: Rectangle, out?: O): O; + + /** + * The geometry constant type of this object: `GEOM_CONST.RECTANGLE`. + * Used for fast type comparisons. + */ + readonly type: number; + + /** + * The X coordinate of the top left corner of the Rectangle. + */ + x: number; + + /** + * The Y coordinate of the top left corner of the Rectangle. + */ + y: number; + + /** + * The width of the Rectangle, i.e. the distance between its left side (defined by `x`) and its right side. + */ + width: number; + + /** + * The height of the Rectangle, i.e. the distance between its top side (defined by `y`) and its bottom side. + */ + height: number; + + /** + * Checks if the given point is inside the Rectangle's bounds. + * @param x The X coordinate of the point to check. + * @param y The Y coordinate of the point to check. + */ + contains(x: number, y: number): boolean; + + /** + * Calculates the coordinates of a point at a certain `position` on the Rectangle's perimeter. + * + * The `position` is a fraction between 0 and 1 which defines how far into the perimeter the point is. + * + * A value of 0 or 1 returns the point at the top left corner of the rectangle, while a value of 0.5 returns the point at the bottom right corner of the rectangle. Values between 0 and 0.5 are on the top or the right side and values between 0.5 and 1 are on the bottom or the left side. + * @param position The normalized distance into the Rectangle's perimeter to return. + * @param output An object to update with the `x` and `y` coordinates of the point. + */ + getPoint(position: number, output?: O): O; + + /** + * Returns an array of points from the perimeter of the Rectangle, each spaced out based on the quantity or step required. + * @param quantity The number of points to return. Set to `false` or 0 to return an arbitrary number of points (`perimeter / stepRate`) evenly spaced around the Rectangle based on the `stepRate`. + * @param stepRate If `quantity` is 0, determines the normalized distance between each returned point. + * @param output An array to which to append the points. + */ + getPoints(quantity: number, stepRate?: number, output?: O): O; + + /** + * Returns a random point within the Rectangle's bounds. + * @param point The object in which to store the `x` and `y` coordinates of the point. + */ + getRandomPoint(point?: O): O; + + /** + * Sets the position, width, and height of the Rectangle. + * @param x The X coordinate of the top left corner of the Rectangle. + * @param y The Y coordinate of the top left corner of the Rectangle. + * @param width The width of the Rectangle. + * @param height The height of the Rectangle. + */ + setTo(x: number, y: number, width: number, height: number): this; + + /** + * Resets the position, width, and height of the Rectangle to 0. + */ + setEmpty(): this; + + /** + * Sets the position of the Rectangle. + * @param x The X coordinate of the top left corner of the Rectangle. + * @param y The Y coordinate of the top left corner of the Rectangle. Default x. + */ + setPosition(x: number, y?: number): this; + + /** + * Sets the width and height of the Rectangle. + * @param width The width to set the Rectangle to. + * @param height The height to set the Rectangle to. Default width. + */ + setSize(width: number, height?: number): this; + + /** + * Determines if the Rectangle is empty. A Rectangle is empty if its width or height is less than or equal to 0. + */ + isEmpty(): boolean; + + /** + * Returns a Line object that corresponds to the top of this Rectangle. + * @param line A Line object to set the results in. If `undefined` a new Line will be created. + */ + getLineA(line?: O): O; + + /** + * Returns a Line object that corresponds to the right of this Rectangle. + * @param line A Line object to set the results in. If `undefined` a new Line will be created. + */ + getLineB(line?: O): O; + + /** + * Returns a Line object that corresponds to the bottom of this Rectangle. + * @param line A Line object to set the results in. If `undefined` a new Line will be created. + */ + getLineC(line?: O): O; + + /** + * Returns a Line object that corresponds to the left of this Rectangle. + * @param line A Line object to set the results in. If `undefined` a new Line will be created. + */ + getLineD(line?: O): O; + + /** + * The x coordinate of the left of the Rectangle. + * Changing the left property of a Rectangle object has no effect on the y and height properties. However it does affect the width property, whereas changing the x value does not affect the width property. + */ + left: number; + + /** + * The sum of the x and width properties. + * Changing the right property of a Rectangle object has no effect on the x, y and height properties, however it does affect the width property. + */ + right: number; + + /** + * The y coordinate of the top of the Rectangle. Changing the top property of a Rectangle object has no effect on the x and width properties. + * However it does affect the height property, whereas changing the y value does not affect the height property. + */ + top: number; + + /** + * The sum of the y and height properties. + * Changing the bottom property of a Rectangle object has no effect on the x, y and width properties, but does change the height property. + */ + bottom: number; + + /** + * The x coordinate of the center of the Rectangle. + */ + centerX: number; + + /** + * The y coordinate of the center of the Rectangle. + */ + centerY: number; + + /** + * Determines if the two objects (either Rectangles or Rectangle-like) have the same width and height values under strict equality. + * @param rect The first Rectangle object. + * @param toCompare The second Rectangle object. + */ + static SameDimensions(rect: Rectangle, toCompare: Rectangle): boolean; + + /** + * Scales the width and height of this Rectangle by the given amounts. + * @param rect The `Rectangle` object that will be scaled by the specified amount(s). + * @param x The factor by which to scale the rectangle horizontally. + * @param y The amount by which to scale the rectangle vertically. If this is not specified, the rectangle will be scaled by the factor `x` in both directions. + */ + static Scale(rect: O, x: number, y: number): O; + + /** + * Creates a new Rectangle or repositions and/or resizes an existing Rectangle so that it encompasses the two given Rectangles, i.e. calculates their union. + * @param rectA The first Rectangle to use. + * @param rectB The second Rectangle to use. + * @param out The Rectangle to store the union in. + */ + static Union(rectA: Rectangle, rectB: Rectangle, out?: O): O; + +} + +export default Rectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Rectangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Rectangle.js new file mode 100644 index 000000000..444d5a2ae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Rectangle.js @@ -0,0 +1,459 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Class from '../../object/Class.js'; +import Contains from './Contains.js'; +import GetPoint from './GetPoint.js'; +import GetPoints from './GetPoints.js'; +import Line from '../line/Line.js'; +import Random from './Random.js'; + +/** + * @classdesc + * Encapsulates a 2D rectangle defined by its corner point in the top-left and its extends in x (width) and y (height) + * + * @class Rectangle + * @memberof Phaser.Geom + * @constructor + * @since 3.0.0 + * + * @param {number} [x=0] - The X coordinate of the top left corner of the Rectangle. + * @param {number} [y=0] - The Y coordinate of the top left corner of the Rectangle. + * @param {number} [width=0] - The width of the Rectangle. + * @param {number} [height=0] - The height of the Rectangle. + */ +var Rectangle = new Class({ + + initialize: + + function Rectangle(x, y, width, height) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (width === undefined) { width = 0; } + if (height === undefined) { height = 0; } + + /** + * The X coordinate of the top left corner of the Rectangle. + * + * @name Phaser.Geom.Rectangle#x + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.x = x; + + /** + * The Y coordinate of the top left corner of the Rectangle. + * + * @name Phaser.Geom.Rectangle#y + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.y = y; + + /** + * The width of the Rectangle, i.e. the distance between its left side (defined by `x`) and its right side. + * + * @name Phaser.Geom.Rectangle#width + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.width = width; + + /** + * The height of the Rectangle, i.e. the distance between its top side (defined by `y`) and its bottom side. + * + * @name Phaser.Geom.Rectangle#height + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.height = height; + }, + + /** + * Checks if the given point is inside the Rectangle's bounds. + * + * @method Phaser.Geom.Rectangle#contains + * @since 3.0.0 + * + * @param {number} x - The X coordinate of the point to check. + * @param {number} y - The Y coordinate of the point to check. + * + * @return {boolean} `true` if the point is within the Rectangle's bounds, otherwise `false`. + */ + contains: function (x, y) { + return Contains(this, x, y); + }, + + /** + * Calculates the coordinates of a point at a certain `position` on the Rectangle's perimeter. + * + * The `position` is a fraction between 0 and 1 which defines how far into the perimeter the point is. + * + * A value of 0 or 1 returns the point at the top left corner of the rectangle, while a value of 0.5 returns the point at the bottom right corner of the rectangle. Values between 0 and 0.5 are on the top or the right side and values between 0.5 and 1 are on the bottom or the left side. + * + * @method Phaser.Geom.Rectangle#getPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [output,$return] + * + * @param {number} position - The normalized distance into the Rectangle's perimeter to return. + * @param {(Phaser.Geom.Point|object)} [output] - An object to update with the `x` and `y` coordinates of the point. + * + * @return {(Phaser.Geom.Point|object)} The updated `output` object, or a new Point if no `output` object was given. + */ + getPoint: function (position, output) { + return GetPoint(this, position, output); + }, + + /** + * Returns an array of points from the perimeter of the Rectangle, each spaced out based on the quantity or step required. + * + * @method Phaser.Geom.Rectangle#getPoints + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point[]} O - [output,$return] + * + * @param {integer} quantity - The number of points to return. Set to `false` or 0 to return an arbitrary number of points (`perimeter / stepRate`) evenly spaced around the Rectangle based on the `stepRate`. + * @param {number} [stepRate] - If `quantity` is 0, determines the normalized distance between each returned point. + * @param {(array|Phaser.Geom.Point[])} [output] - An array to which to append the points. + * + * @return {(array|Phaser.Geom.Point[])} The modified `output` array, or a new array if none was provided. + */ + getPoints: function (quantity, stepRate, output) { + return GetPoints(this, quantity, stepRate, output); + }, + + /** + * Returns a random point within the Rectangle's bounds. + * + * @method Phaser.Geom.Rectangle#getRandomPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [point,$return] + * + * @param {Phaser.Geom.Point} [point] - The object in which to store the `x` and `y` coordinates of the point. + * + * @return {Phaser.Geom.Point} The updated `point`, or a new Point if none was provided. + */ + getRandomPoint: function (point) { + return Random(this, point); + }, + + /** + * Sets the position, width, and height of the Rectangle. + * + * @method Phaser.Geom.Rectangle#setTo + * @since 3.0.0 + * + * @param {number} x - The X coordinate of the top left corner of the Rectangle. + * @param {number} y - The Y coordinate of the top left corner of the Rectangle. + * @param {number} width - The width of the Rectangle. + * @param {number} height - The height of the Rectangle. + * + * @return {Phaser.Geom.Rectangle} This Rectangle object. + */ + setTo: function (x, y, width, height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + + return this; + }, + + /** + * Resets the position, width, and height of the Rectangle to 0. + * + * @method Phaser.Geom.Rectangle#setEmpty + * @since 3.0.0 + * + * @return {Phaser.Geom.Rectangle} This Rectangle object. + */ + setEmpty: function () { + return this.setTo(0, 0, 0, 0); + }, + + /** + * Sets the position of the Rectangle. + * + * @method Phaser.Geom.Rectangle#setPosition + * @since 3.0.0 + * + * @param {number} x - The X coordinate of the top left corner of the Rectangle. + * @param {number} [y=x] - The Y coordinate of the top left corner of the Rectangle. + * + * @return {Phaser.Geom.Rectangle} This Rectangle object. + */ + setPosition: function (x, y) { + if (y === undefined) { y = x; } + + this.x = x; + this.y = y; + + return this; + }, + + /** + * Sets the width and height of the Rectangle. + * + * @method Phaser.Geom.Rectangle#setSize + * @since 3.0.0 + * + * @param {number} width - The width to set the Rectangle to. + * @param {number} [height=width] - The height to set the Rectangle to. + * + * @return {Phaser.Geom.Rectangle} This Rectangle object. + */ + setSize: function (width, height) { + if (height === undefined) { height = width; } + + this.width = width; + this.height = height; + + return this; + }, + + /** + * Determines if the Rectangle is empty. A Rectangle is empty if its width or height is less than or equal to 0. + * + * @method Phaser.Geom.Rectangle#isEmpty + * @since 3.0.0 + * + * @return {boolean} `true` if the Rectangle is empty. A Rectangle object is empty if its width or height is less than or equal to 0. + */ + isEmpty: function () { + return (this.width <= 0 || this.height <= 0); + }, + + /** + * Returns a Line object that corresponds to the top of this Rectangle. + * + * @method Phaser.Geom.Rectangle#getLineA + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [line,$return] + * + * @param {Phaser.Geom.Line} [line] - A Line object to set the results in. If `undefined` a new Line will be created. + * + * @return {Phaser.Geom.Line} A Line object that corresponds to the top of this Rectangle. + */ + getLineA: function (line) { + if (line === undefined) { line = new Line(); } + + line.setTo(this.x, this.y, this.right, this.y); + + return line; + }, + + /** + * Returns a Line object that corresponds to the right of this Rectangle. + * + * @method Phaser.Geom.Rectangle#getLineB + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [line,$return] + * + * @param {Phaser.Geom.Line} [line] - A Line object to set the results in. If `undefined` a new Line will be created. + * + * @return {Phaser.Geom.Line} A Line object that corresponds to the right of this Rectangle. + */ + getLineB: function (line) { + if (line === undefined) { line = new Line(); } + + line.setTo(this.right, this.y, this.right, this.bottom); + + return line; + }, + + /** + * Returns a Line object that corresponds to the bottom of this Rectangle. + * + * @method Phaser.Geom.Rectangle#getLineC + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [line,$return] + * + * @param {Phaser.Geom.Line} [line] - A Line object to set the results in. If `undefined` a new Line will be created. + * + * @return {Phaser.Geom.Line} A Line object that corresponds to the bottom of this Rectangle. + */ + getLineC: function (line) { + if (line === undefined) { line = new Line(); } + + line.setTo(this.right, this.bottom, this.x, this.bottom); + + return line; + }, + + /** + * Returns a Line object that corresponds to the left of this Rectangle. + * + * @method Phaser.Geom.Rectangle#getLineD + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [line,$return] + * + * @param {Phaser.Geom.Line} [line] - A Line object to set the results in. If `undefined` a new Line will be created. + * + * @return {Phaser.Geom.Line} A Line object that corresponds to the left of this Rectangle. + */ + getLineD: function (line) { + if (line === undefined) { line = new Line(); } + + line.setTo(this.x, this.bottom, this.x, this.y); + + return line; + }, + + /** + * The x coordinate of the left of the Rectangle. + * Changing the left property of a Rectangle object has no effect on the y and height properties. However it does affect the width property, whereas changing the x value does not affect the width property. + * + * @name Phaser.Geom.Rectangle#left + * @type {number} + * @since 3.0.0 + */ + left: { + + get: function () { + return this.x; + }, + + set: function (value) { + if (value >= this.right) { + this.width = 0; + } + else { + this.width = this.right - value; + } + + this.x = value; + } + + }, + + /** + * The sum of the x and width properties. + * Changing the right property of a Rectangle object has no effect on the x, y and height properties, however it does affect the width property. + * + * @name Phaser.Geom.Rectangle#right + * @type {number} + * @since 3.0.0 + */ + right: { + + get: function () { + return this.x + this.width; + }, + + set: function (value) { + if (value <= this.x) { + this.width = 0; + } + else { + this.width = value - this.x; + } + } + + }, + + /** + * The y coordinate of the top of the Rectangle. Changing the top property of a Rectangle object has no effect on the x and width properties. + * However it does affect the height property, whereas changing the y value does not affect the height property. + * + * @name Phaser.Geom.Rectangle#top + * @type {number} + * @since 3.0.0 + */ + top: { + + get: function () { + return this.y; + }, + + set: function (value) { + if (value >= this.bottom) { + this.height = 0; + } + else { + this.height = (this.bottom - value); + } + + this.y = value; + } + + }, + + /** + * The sum of the y and height properties. + * Changing the bottom property of a Rectangle object has no effect on the x, y and width properties, but does change the height property. + * + * @name Phaser.Geom.Rectangle#bottom + * @type {number} + * @since 3.0.0 + */ + bottom: { + + get: function () { + return this.y + this.height; + }, + + set: function (value) { + if (value <= this.y) { + this.height = 0; + } + else { + this.height = value - this.y; + } + } + + }, + + /** + * The x coordinate of the center of the Rectangle. + * + * @name Phaser.Geom.Rectangle#centerX + * @type {number} + * @since 3.0.0 + */ + centerX: { + + get: function () { + return this.x + (this.width / 2); + }, + + set: function (value) { + this.x = value - (this.width / 2); + } + + }, + + /** + * The y coordinate of the center of the Rectangle. + * + * @name Phaser.Geom.Rectangle#centerY + * @type {number} + * @since 3.0.0 + */ + centerY: { + + get: function () { + return this.y + (this.height / 2); + }, + + set: function (value) { + this.y = value - (this.height / 2); + } + + } + +}); + +export default Rectangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/SameDimensions.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/SameDimensions.js new file mode 100644 index 000000000..96c71d89b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/SameDimensions.js @@ -0,0 +1,23 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Determines if the two objects (either Rectangles or Rectangle-like) have the same width and height values under strict equality. + * + * @function Phaser.Geom.Rectangle.SameDimensions + * @since 3.15.0 + * + * @param {Phaser.Geom.Rectangle} rect - The first Rectangle object. + * @param {Phaser.Geom.Rectangle} toCompare - The second Rectangle object. + * + * @return {boolean} `true` if the objects have equivalent values for the `width` and `height` properties, otherwise `false`. + */ +var SameDimensions = function (rect, toCompare) +{ + return (rect.width === toCompare.width && rect.height === toCompare.height); +}; + +export default SameDimensions; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Scale.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Scale.js new file mode 100644 index 000000000..71efdb7d0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Scale.js @@ -0,0 +1,33 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +// Scales the width and height of this Rectangle by the given amounts. + +/** + * Scales the width and height of this Rectangle by the given amounts. + * + * @function Phaser.Geom.Rectangle.Scale + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [rect,$return] + * + * @param {Phaser.Geom.Rectangle} rect - The `Rectangle` object that will be scaled by the specified amount(s). + * @param {number} x - The factor by which to scale the rectangle horizontally. + * @param {number} y - The amount by which to scale the rectangle vertically. If this is not specified, the rectangle will be scaled by the factor `x` in both directions. + * + * @return {Phaser.Geom.Rectangle} The rectangle object with updated `width` and `height` properties as calculated from the scaling factor(s). + */ +var Scale = function (rect, x, y) +{ + if (y === undefined) { y = x; } + + rect.width *= x; + rect.height *= y; + + return rect; +}; + +export default Scale; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Union.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Union.js new file mode 100644 index 000000000..12e66ee5e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Union.js @@ -0,0 +1,35 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Rectangle from './Rectangle.js'; + +/** + * Creates a new Rectangle or repositions and/or resizes an existing Rectangle so that it encompasses the two given Rectangles, i.e. calculates their union. + * + * @function Phaser.Geom.Rectangle.Union + * @since 3.0.0 + * + * @generic {Phaser.Geom.Rectangle} O - [out,$return] + * + * @param {Phaser.Geom.Rectangle} rectA - The first Rectangle to use. + * @param {Phaser.Geom.Rectangle} rectB - The second Rectangle to use. + * @param {Phaser.Geom.Rectangle} [out] - The Rectangle to store the union in. + * + * @return {Phaser.Geom.Rectangle} The modified `out` Rectangle, or a new Rectangle if none was provided. + */ +var Union = function (rectA, rectB, out) { + if (out === undefined) { out = new Rectangle(); } + + // Cache vars so we can use one of the input rects as the output rect + var x = Math.min(rectA.x, rectB.x); + var y = Math.min(rectA.y, rectB.y); + var w = Math.max(rectA.right, rectB.right) - x; + var h = Math.max(rectA.bottom, rectB.bottom) - y; + + return out.setTo(x, y, w, h); +}; + +export default Union; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/index.js new file mode 100644 index 000000000..fc2ddb99d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/index.js @@ -0,0 +1,84 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Rectangle from './Rectangle.js'; +import Area from './Area.js'; +import Ceil from './Ceil.js'; +import CeilAll from './CeilAll.js'; +import CenterOn from './CenterOn.js'; +import Clone from './Clone.js'; +import Contains from './Contains.js'; +import ContainsPoint from './ContainsPoint.js'; +import ContainsRect from './ContainsRect.js'; +import CopyFrom from './CopyFrom.js'; +import Decompose from './Decompose.js'; +import Equals from './Equals.js'; +import FitInside from './FitInside.js'; +import FitOutside from './FitOutside.js'; +import Floor from './Floor.js'; +import FloorAll from './FloorAll.js'; +import FromPoints from './FromPoints.js'; +import GetAspectRatio from './GetAspectRatio.js'; +import GetCenter from './GetCenter.js'; +import GetPoint from './GetPoint.js'; +import GetPoints from './GetPoints.js'; +import GetSize from './GetSize.js'; +import Inflate from './Inflate.js'; +import Intersection from './Intersection.js'; +import MarchingAnts from './MarchingAnts.js'; +import MergePoints from './MergePoints.js'; +import MergeRect from './MergeRect.js'; +import MergeXY from './MergeXY.js'; +import Offset from './Offset.js'; +import OffsetPoint from './OffsetPoint.js'; +import Overlaps from './Overlaps.js'; +import Perimeter from './Perimeter.js'; +import PerimeterPoint from './PerimeterPoint.js'; +import Random from './Random.js'; +import RandomOutside from './RandomOutside.js'; +import SameDimensions from './SameDimensions.js'; +import Scale from './Scale.js'; +import Union from './Union.js'; + +Rectangle.Area = Area; +Rectangle.Ceil = Ceil; +Rectangle.CeilAll = CeilAll; +Rectangle.CenterOn = CenterOn; +Rectangle.Clone = Clone; +Rectangle.Contains = Contains; +Rectangle.ContainsPoint = ContainsPoint; +Rectangle.ContainsRect = ContainsRect; +Rectangle.CopyFrom = CopyFrom; +Rectangle.Decompose = Decompose; +Rectangle.Equals = Equals; +Rectangle.FitInside = FitInside; +Rectangle.FitOutside = FitOutside; +Rectangle.Floor = Floor; +Rectangle.FloorAll = FloorAll; +Rectangle.FromPoints = FromPoints; +Rectangle.GetAspectRatio = GetAspectRatio; +Rectangle.GetCenter = GetCenter; +Rectangle.GetPoint = GetPoint; +Rectangle.GetPoints = GetPoints; +Rectangle.GetSize = GetSize; +Rectangle.Inflate = Inflate; +Rectangle.Intersection = Intersection; +Rectangle.MarchingAnts = MarchingAnts; +Rectangle.MergePoints = MergePoints; +Rectangle.MergeRect = MergeRect; +Rectangle.MergeXY = MergeXY; +Rectangle.Offset = Offset; +Rectangle.OffsetPoint = OffsetPoint; +Rectangle.Overlaps = Overlaps; +Rectangle.Perimeter = Perimeter; +Rectangle.PerimeterPoint = PerimeterPoint; +Rectangle.Random = Random; +Rectangle.RandomOutside = RandomOutside; +Rectangle.SameDimensions = SameDimensions; +Rectangle.Scale = Scale; +Rectangle.Union = Union; + +export default Rectangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Area.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Area.js new file mode 100644 index 000000000..c50e020d8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Area.js @@ -0,0 +1,33 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +// The 2D area of a triangle. The area value is always non-negative. + +/** + * Returns the area of a Triangle. + * + * @function Phaser.Geom.Triangle.Area + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to use. + * + * @return {number} The area of the Triangle, always non-negative. + */ +var Area = function (triangle) +{ + var x1 = triangle.x1; + var y1 = triangle.y1; + + var x2 = triangle.x2; + var y2 = triangle.y2; + + var x3 = triangle.x3; + var y3 = triangle.y3; + + return Math.abs(((x3 - x1) * (y2 - y1) - (x2 - x1) * (y3 - y1)) / 2); +}; + +export default Area; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/BuildEquilateral.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/BuildEquilateral.js new file mode 100644 index 000000000..bd8894453 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/BuildEquilateral.js @@ -0,0 +1,37 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Triangle from './Triangle.js'; + +/** + * Builds an equilateral triangle. In the equilateral triangle, all the sides are the same length (congruent) and all the angles are the same size (congruent). + * The x/y specifies the top-middle of the triangle (x1/y1) and length is the length of each side. + * + * @function Phaser.Geom.Triangle.BuildEquilateral + * @since 3.0.0 + * + * @param {number} x - x coordinate of the top point of the triangle. + * @param {number} y - y coordinate of the top point of the triangle. + * @param {number} length - Length of each side of the triangle. + * + * @return {Phaser.Geom.Triangle} The Triangle object of the given size. + */ +var BuildEquilateral = function (x, y, length) { + var height = length * (Math.sqrt(3) / 2); + + var x1 = x; + var y1 = y; + + var x2 = x + (length / 2); + var y2 = y + height; + + var x3 = x - (length / 2); + var y3 = y + height; + + return new Triangle(x1, y1, x2, y2, x3, y3); +}; + +export default BuildEquilateral; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/BuildFromPolygon.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/BuildFromPolygon.js new file mode 100644 index 000000000..8d28803ba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/BuildFromPolygon.js @@ -0,0 +1,67 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import EarCut from '../polygon/Earcut.js'; +import Triangle from './Triangle.js'; + +/** + * [description] + * + * @function Phaser.Geom.Triangle.BuildFromPolygon + * @since 3.0.0 + * + * @generic {Phaser.Geom.Triangle[]} O - [out,$return] + * + * @param {array} data - A flat array of vertice coordinates like [x0,y0, x1,y1, x2,y2, ...] + * @param {array} [holes=null] - An array of hole indices if any (e.g. [5, 8] for a 12-vertice input would mean one hole with vertices 5–7 and another with 8–11). + * @param {number} [scaleX=1] - [description] + * @param {number} [scaleY=1] - [description] + * @param {(array|Phaser.Geom.Triangle[])} [out] - [description] + * + * @return {(array|Phaser.Geom.Triangle[])} [description] + */ +var BuildFromPolygon = function (data, holes, scaleX, scaleY, out) { + if (holes === undefined) { holes = null; } + if (scaleX === undefined) { scaleX = 1; } + if (scaleY === undefined) { scaleY = 1; } + if (out === undefined) { out = []; } + + var tris = EarCut(data, holes); + + var a; + var b; + var c; + + var x1; + var y1; + + var x2; + var y2; + + var x3; + var y3; + + for (var i = 0; i < tris.length; i += 3) { + a = tris[i]; + b = tris[i + 1]; + c = tris[i + 2]; + + x1 = data[a * 2] * scaleX; + y1 = data[(a * 2) + 1] * scaleY; + + x2 = data[b * 2] * scaleX; + y2 = data[(b * 2) + 1] * scaleY; + + x3 = data[c * 2] * scaleX; + y3 = data[(c * 2) + 1] * scaleY; + + out.push(new Triangle(x1, y1, x2, y2, x3, y3)); + } + + return out; +}; + +export default BuildFromPolygon; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/BuildRight.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/BuildRight.js new file mode 100644 index 000000000..eb95c7925 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/BuildRight.js @@ -0,0 +1,42 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Triangle from './Triangle'; + +// Builds a right triangle, with one 90 degree angle and two acute angles +// The x/y is the coordinate of the 90 degree angle (and will map to x1/y1 in the resulting Triangle) +// w/h can be positive or negative and represent the length of each side + +/** + * Builds a right triangle, i.e. one which has a 90-degree angle and two acute angles. + * + * @function Phaser.Geom.Triangle.BuildRight + * @since 3.0.0 + * + * @param {number} x - The X coordinate of the right angle, which will also be the first X coordinate of the constructed Triangle. + * @param {number} y - The Y coordinate of the right angle, which will also be the first Y coordinate of the constructed Triangle. + * @param {number} width - The length of the side which is to the left or to the right of the right angle. + * @param {number} height - The length of the side which is above or below the right angle. + * + * @return {Phaser.Geom.Triangle} The constructed right Triangle. + */ +var BuildRight = function (x, y, width, height) { + if (height === undefined) { height = width; } + + // 90 degree angle + var x1 = x; + var y1 = y; + + var x2 = x; + var y2 = y - height; + + var x3 = x + width; + var y3 = y; + + return new Triangle(x1, y1, x2, y2, x3, y3); +}; + +export default BuildRight; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CenterOn.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CenterOn.js new file mode 100644 index 000000000..a26341618 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CenterOn.js @@ -0,0 +1,46 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Centroid from './Centroid.js'; +import Offset from './Offset.js'; + +/** + * @callback CenterFunction + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to return the center coordinates of. + * + * @return {Phaser.Math.Vector2} The center point of the Triangle according to the function. + */ + +/** + * Positions the Triangle so that it is centered on the given coordinates. + * + * @function Phaser.Geom.Triangle.CenterOn + * @since 3.0.0 + * + * @generic {Phaser.Geom.Triangle} O - [triangle,$return] + * + * @param {Phaser.Geom.Triangle} triangle - The triangle to be positioned. + * @param {number} x - The horizontal coordinate to center on. + * @param {number} y - The vertical coordinate to center on. + * @param {CenterFunction} [centerFunc] - The function used to center the triangle. Defaults to Centroid centering. + * + * @return {Phaser.Geom.Triangle} The Triangle that was centered. + */ +var CenterOn = function (triangle, x, y, centerFunc) { + if (centerFunc === undefined) { centerFunc = Centroid; } + + // Get the center of the triangle + var center = centerFunc(triangle); + + // Difference + var diffX = x - center.x; + var diffY = y - center.y; + + return Offset(triangle, diffX, diffY); +}; + +export default CenterOn; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Centroid.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Centroid.js new file mode 100644 index 000000000..312d470c7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Centroid.js @@ -0,0 +1,37 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +// The three medians (the lines drawn from the vertices to the bisectors of the opposite sides) +// meet in the centroid or center of mass (center of gravity). +// The centroid divides each median in a ratio of 2:1 + +/** + * Calculates the position of a Triangle's centroid, which is also its center of mass (center of gravity). + * + * The centroid is the point in a Triangle at which its three medians (the lines drawn from the vertices to the bisectors of the opposite sides) meet. It divides each one in a 2:1 ratio. + * + * @function Phaser.Geom.Triangle.Centroid + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to use. + * @param {(Phaser.Geom.Point|object)} [out] - An object to store the coordinates in. + * + * @return {(Phaser.Geom.Point|object)} The `out` object with modified `x` and `y` properties, or a new Point if none was provided. + */ +var Centroid = function (triangle, out) { + if (out === undefined) { out = new Point(); } + + out.x = (triangle.x1 + triangle.x2 + triangle.x3) / 3; + out.y = (triangle.y1 + triangle.y2 + triangle.y3) / 3; + + return out; +}; + +export default Centroid; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CircumCenter.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CircumCenter.js new file mode 100644 index 000000000..b43464a35 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CircumCenter.js @@ -0,0 +1,68 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Vector2 from '../../math/Vector2.js'; + +// Adapted from http://bjornharrtell.github.io/jsts/doc/api/jsts_geom_Triangle.js.html + +/** + * Computes the determinant of a 2x2 matrix. Uses standard double-precision arithmetic, so is susceptible to round-off error. + * + * @function det + * @private + * @since 3.0.0 + * + * @param {number} m00 - The [0,0] entry of the matrix. + * @param {number} m01 - The [0,1] entry of the matrix. + * @param {number} m10 - The [1,0] entry of the matrix. + * @param {number} m11 - The [1,1] entry of the matrix. + * + * @return {number} the determinant. + */ +function det(m00, m01, m10, m11) { + return (m00 * m11) - (m01 * m10); +} + +/** + * Computes the circumcentre of a triangle. The circumcentre is the centre of + * the circumcircle, the smallest circle which encloses the triangle. It is also + * the common intersection point of the perpendicular bisectors of the sides of + * the triangle, and is the only point which has equal distance to all three + * vertices of the triangle. + * + * @function Phaser.Geom.Triangle.CircumCenter + * @since 3.0.0 + * + * @generic {Phaser.Math.Vector2} O - [out,$return] + * + * @param {Phaser.Geom.Triangle} triangle - [description] + * @param {Phaser.Math.Vector2} [out] - [description] + * + * @return {Phaser.Math.Vector2} [description] + */ +var CircumCenter = function (triangle, out) { + if (out === undefined) { out = new Vector2(); } + + var cx = triangle.x3; + var cy = triangle.y3; + + var ax = triangle.x1 - cx; + var ay = triangle.y1 - cy; + + var bx = triangle.x2 - cx; + var by = triangle.y2 - cy; + + var denom = 2 * det(ax, ay, bx, by); + var numx = det(ay, ax * ax + ay * ay, by, bx * bx + by * by); + var numy = det(ax, ax * ax + ay * ay, bx, bx * bx + by * by); + + out.x = cx - numx / denom; + out.y = cy + numy / denom; + + return out; +}; + +export default CircumCenter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CircumCircle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CircumCircle.js new file mode 100644 index 000000000..43b29a038 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CircumCircle.js @@ -0,0 +1,74 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Circle from '../circle/Circle'; + +// Adapted from https://gist.github.com/mutoo/5617691 + +/** + * Finds the circumscribed circle (circumcircle) of a Triangle object. The circumcircle is the circle which touches all of the triangle's vertices. + * + * @function Phaser.Geom.Triangle.CircumCircle + * @since 3.0.0 + * + * @generic {Phaser.Geom.Circle} O - [out,$return] + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to use as input. + * @param {Phaser.Geom.Circle} [out] - An optional Circle to store the result in. + * + * @return {Phaser.Geom.Circle} The updated `out` Circle, or a new Circle if none was provided. + */ +var CircumCircle = function (triangle, out) { + if (out === undefined) { out = new Circle(); } + + // A + var x1 = triangle.x1; + var y1 = triangle.y1; + + // B + var x2 = triangle.x2; + var y2 = triangle.y2; + + // C + var x3 = triangle.x3; + var y3 = triangle.y3; + + var A = x2 - x1; + var B = y2 - y1; + var C = x3 - x1; + var D = y3 - y1; + var E = A * (x1 + x2) + B * (y1 + y2); + var F = C * (x1 + x3) + D * (y1 + y3); + var G = 2 * (A * (y3 - y2) - B * (x3 - x2)); + + var dx; + var dy; + + // If the points of the triangle are collinear, then just find the + // extremes and use the midpoint as the center of the circumcircle. + + if (Math.abs(G) < 0.000001) { + var minX = Math.min(x1, x2, x3); + var minY = Math.min(y1, y2, y3); + dx = (Math.max(x1, x2, x3) - minX) * 0.5; + dy = (Math.max(y1, y2, y3) - minY) * 0.5; + + out.x = minX + dx; + out.y = minY + dy; + out.radius = Math.sqrt(dx * dx + dy * dy); + } + else { + out.x = (D * E - B * F) / G; + out.y = (A * F - C * E) / G; + dx = out.x - x1; + dy = out.y - y1; + out.radius = Math.sqrt(dx * dx + dy * dy); + } + + return out; +}; + +export default CircumCircle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Clone.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Clone.js new file mode 100644 index 000000000..872e37c61 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Clone.js @@ -0,0 +1,23 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Triangle from './Triangle.js'; + +/** + * Clones a Triangle object. + * + * @function Phaser.Geom.Triangle.Clone + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} source - The Triangle to clone. + * + * @return {Phaser.Geom.Triangle} A new Triangle identical to the given one but separate from it. + */ +var Clone = function (source) { + return new Triangle(source.x1, source.y1, source.x2, source.y2, source.x3, source.y3); +}; + +export default Clone; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Contains.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Contains.js new file mode 100644 index 000000000..63e61d79a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Contains.js @@ -0,0 +1,47 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +// http://www.blackpawn.com/texts/pointinpoly/ + +/** + * Checks if a point (as a pair of coordinates) is inside a Triangle's bounds. + * + * @function Phaser.Geom.Triangle.Contains + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to check. + * @param {number} x - The X coordinate of the point to check. + * @param {number} y - The Y coordinate of the point to check. + * + * @return {boolean} `true` if the point is inside the Triangle, otherwise `false`. + */ +var Contains = function (triangle, x, y) +{ + var v0x = triangle.x3 - triangle.x1; + var v0y = triangle.y3 - triangle.y1; + + var v1x = triangle.x2 - triangle.x1; + var v1y = triangle.y2 - triangle.y1; + + var v2x = x - triangle.x1; + var v2y = y - triangle.y1; + + var dot00 = (v0x * v0x) + (v0y * v0y); + var dot01 = (v0x * v1x) + (v0y * v1y); + var dot02 = (v0x * v2x) + (v0y * v2y); + var dot11 = (v1x * v1x) + (v1y * v1y); + var dot12 = (v1x * v2x) + (v1y * v2y); + + // Compute barycentric coordinates + var b = ((dot00 * dot11) - (dot01 * dot01)); + var inv = (b === 0) ? 0 : (1 / b); + var u = ((dot11 * dot02) - (dot01 * dot12)) * inv; + var v = ((dot00 * dot12) - (dot01 * dot02)) * inv; + + return (u >= 0 && v >= 0 && (u + v < 1)); +}; + +export default Contains; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/ContainsArray.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/ContainsArray.js new file mode 100644 index 000000000..7268b93b0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/ContainsArray.js @@ -0,0 +1,81 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +// http://www.blackpawn.com/texts/pointinpoly/ + +// points is an array of Point-like objects with public x/y properties +// returns an array containing all points that are within the triangle, or an empty array if none +// if 'returnFirst' is true it will return after the first point within the triangle is found + +/** + * Filters an array of point-like objects to only those contained within a triangle. + * If `returnFirst` is true, will return an array containing only the first point in the provided array that is within the triangle (or an empty array if there are no such points). + * + * @function Phaser.Geom.Triangle.ContainsArray + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangle - The triangle that the points are being checked in. + * @param {Phaser.Geom.Point[]} points - An array of point-like objects (objects that have an `x` and `y` property) + * @param {boolean} [returnFirst=false] - If `true`, return an array containing only the first point found that is within the triangle. + * @param {array} [out] - If provided, the points that are within the triangle will be appended to this array instead of being added to a new array. If `returnFirst` is true, only the first point found within the triangle will be appended. This array will also be returned by this function. + * + * @return {Phaser.Geom.Point[]} An array containing all the points from `points` that are within the triangle, if an array was provided as `out`, points will be appended to that array and it will also be returned here. + */ +var ContainsArray = function (triangle, points, returnFirst, out) +{ + if (returnFirst === undefined) { returnFirst = false; } + if (out === undefined) { out = []; } + + var v0x = triangle.x3 - triangle.x1; + var v0y = triangle.y3 - triangle.y1; + + var v1x = triangle.x2 - triangle.x1; + var v1y = triangle.y2 - triangle.y1; + + var dot00 = (v0x * v0x) + (v0y * v0y); + var dot01 = (v0x * v1x) + (v0y * v1y); + var dot11 = (v1x * v1x) + (v1y * v1y); + + // Compute barycentric coordinates + var b = ((dot00 * dot11) - (dot01 * dot01)); + var inv = (b === 0) ? 0 : (1 / b); + + var u; + var v; + var v2x; + var v2y; + var dot02; + var dot12; + + var x1 = triangle.x1; + var y1 = triangle.y1; + + for (var i = 0; i < points.length; i++) + { + v2x = points[i].x - x1; + v2y = points[i].y - y1; + + dot02 = (v0x * v2x) + (v0y * v2y); + dot12 = (v1x * v2x) + (v1y * v2y); + + u = ((dot11 * dot02) - (dot01 * dot12)) * inv; + v = ((dot00 * dot12) - (dot01 * dot02)) * inv; + + if (u >= 0 && v >= 0 && (u + v < 1)) + { + out.push({ x: points[i].x, y: points[i].y }); + + if (returnFirst) + { + break; + } + } + } + + return out; +}; + +export default ContainsArray; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/ContainsPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/ContainsPoint.js new file mode 100644 index 000000000..87a4cfc56 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/ContainsPoint.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Contains from './Contains.js'; + +/** + * Tests if a triangle contains a point. + * + * @function Phaser.Geom.Triangle.ContainsPoint + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangle - The triangle. + * @param {(Phaser.Geom.Point|Phaser.Math.Vector2|any)} point - The point to test, or any point-like object with public `x` and `y` properties. + * + * @return {boolean} `true` if the point is within the triangle, otherwise `false`. + */ +var ContainsPoint = function (triangle, point) { + return Contains(triangle, point.x, point.y); +}; + +export default ContainsPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CopyFrom.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CopyFrom.js new file mode 100644 index 000000000..c080b01e2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CopyFrom.js @@ -0,0 +1,25 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Copy the values of one Triangle to a destination Triangle. + * + * @function Phaser.Geom.Triangle.CopyFrom + * @since 3.0.0 + * + * @generic {Phaser.Geom.Triangle} O - [dest,$return] + * + * @param {Phaser.Geom.Triangle} source - The source Triangle to copy the values from. + * @param {Phaser.Geom.Triangle} dest - The destination Triangle to copy the values to. + * + * @return {Phaser.Geom.Triangle} The destination Triangle. + */ +var CopyFrom = function (source, dest) +{ + return dest.setTo(source.x1, source.y1, source.x2, source.y2, source.x3, source.y3); +}; + +export default CopyFrom; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Decompose.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Decompose.js new file mode 100644 index 000000000..3b92fd406 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Decompose.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Decomposes a Triangle into an array of its points. + * + * @function Phaser.Geom.Triangle.Decompose + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to decompose. + * @param {array} [out] - An array to store the points into. + * + * @return {array} The provided `out` array, or a new array if none was provided, with three objects with `x` and `y` properties representing each point of the Triangle appended to it. + */ +var Decompose = function (triangle, out) +{ + if (out === undefined) { out = []; } + + out.push({ x: triangle.x1, y: triangle.y1 }); + out.push({ x: triangle.x2, y: triangle.y2 }); + out.push({ x: triangle.x3, y: triangle.y3 }); + + return out; +}; + +export default Decompose; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Equals.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Equals.js new file mode 100644 index 000000000..ca9e7183e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Equals.js @@ -0,0 +1,30 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Returns true if two triangles have the same coordinates. + * + * @function Phaser.Geom.Triangle.Equals + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangle - The first triangle to check. + * @param {Phaser.Geom.Triangle} toCompare - The second triangle to check. + * + * @return {boolean} `true` if the two given triangles have the exact same coordinates, otherwise `false`. + */ +var Equals = function (triangle, toCompare) +{ + return ( + triangle.x1 === toCompare.x1 && + triangle.y1 === toCompare.y1 && + triangle.x2 === toCompare.x2 && + triangle.y2 === toCompare.y2 && + triangle.x3 === toCompare.x3 && + triangle.y3 === toCompare.y3 + ); +}; + +export default Equals; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/GetPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/GetPoint.js new file mode 100644 index 000000000..3f54c1e33 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/GetPoint.js @@ -0,0 +1,76 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; +import Length from '../line/Length.js'; + +/** + * Returns a Point from around the perimeter of a Triangle. + * + * @function Phaser.Geom.Triangle.GetPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to get the point on its perimeter from. + * @param {number} position - The position along the perimeter of the triangle. A value between 0 and 1. + * @param {(Phaser.Geom.Point|object)} [out] - An option Point, or Point-like object to store the value in. If not given a new Point will be created. + * + * @return {(Phaser.Geom.Point|object)} A Point object containing the given position from the perimeter of the triangle. + */ +var GetPoint = function (triangle, position, out) { + if (out === undefined) { out = new Point(); } + + var line1 = triangle.getLineA(); + var line2 = triangle.getLineB(); + var line3 = triangle.getLineC(); + + if (position <= 0 || position >= 1) { + out.x = line1.x1; + out.y = line1.y1; + + return out; + } + + var length1 = Length(line1); + var length2 = Length(line2); + var length3 = Length(line3); + + var perimeter = length1 + length2 + length3; + + var p = perimeter * position; + var localPosition = 0; + + // Which line is it on? + + if (p < length1) { + // Line 1 + localPosition = p / length1; + + out.x = line1.x1 + (line1.x2 - line1.x1) * localPosition; + out.y = line1.y1 + (line1.y2 - line1.y1) * localPosition; + } + else if (p > length1 + length2) { + // Line 3 + p -= length1 + length2; + localPosition = p / length3; + + out.x = line3.x1 + (line3.x2 - line3.x1) * localPosition; + out.y = line3.y1 + (line3.y2 - line3.y1) * localPosition; + } + else { + // Line 2 + p -= length1; + localPosition = p / length2; + + out.x = line2.x1 + (line2.x2 - line2.x1) * localPosition; + out.y = line2.y1 + (line2.y2 - line2.y1) * localPosition; + } + + return out; +}; + +export default GetPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/GetPoints.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/GetPoints.js new file mode 100644 index 000000000..502001df5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/GetPoints.js @@ -0,0 +1,81 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Length from '../line/Length.js'; +import Point from '../point/Point.js'; + +/** + * Returns an array of evenly spaced points on the perimeter of a Triangle. + * + * @function Phaser.Geom.Triangle.GetPoints + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to get the points from. + * @param {integer} quantity - The number of evenly spaced points to return. Set to 0 to return an arbitrary number of points based on the `stepRate`. + * @param {number} stepRate - If `quantity` is 0, the distance between each returned point. + * @param {(array|Phaser.Geom.Point[])} [out] - An array to which the points should be appended. + * + * @return {(array|Phaser.Geom.Point[])} The modified `out` array, or a new array if none was provided. + */ +var GetPoints = function (triangle, quantity, stepRate, out) { + if (out === undefined) { out = []; } + + var line1 = triangle.getLineA(); + var line2 = triangle.getLineB(); + var line3 = triangle.getLineC(); + + var length1 = Length(line1); + var length2 = Length(line2); + var length3 = Length(line3); + + var perimeter = length1 + length2 + length3; + + // If quantity is a falsey value (false, null, 0, undefined, etc) then we calculate it based on the stepRate instead. + if (!quantity) { + quantity = perimeter / stepRate; + } + + for (var i = 0; i < quantity; i++) { + var p = perimeter * (i / quantity); + var localPosition = 0; + + var point = new Point(); + + // Which line is it on? + + if (p < length1) { + // Line 1 + localPosition = p / length1; + + point.x = line1.x1 + (line1.x2 - line1.x1) * localPosition; + point.y = line1.y1 + (line1.y2 - line1.y1) * localPosition; + } + else if (p > length1 + length2) { + // Line 3 + p -= length1 + length2; + localPosition = p / length3; + + point.x = line3.x1 + (line3.x2 - line3.x1) * localPosition; + point.y = line3.y1 + (line3.y2 - line3.y1) * localPosition; + } + else { + // Line 2 + p -= length1; + localPosition = p / length2; + + point.x = line2.x1 + (line2.x2 - line2.x1) * localPosition; + point.y = line2.y1 + (line2.y2 - line2.y1) * localPosition; + } + + out.push(point); + } + + return out; +}; + +export default GetPoints; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/InCenter.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/InCenter.js new file mode 100644 index 000000000..f5a5d06a9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/InCenter.js @@ -0,0 +1,57 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +// The three angle bisectors of a triangle meet in one point called the incenter. +// It is the center of the incircle, the circle inscribed in the triangle. + +function getLength(x1, y1, x2, y2) { + var x = x1 - x2; + var y = y1 - y2; + var magnitude = (x * x) + (y * y); + + return Math.sqrt(magnitude); +} + +/** + * Calculates the position of the incenter of a Triangle object. This is the point where its three angle bisectors meet and it's also the center of the incircle, which is the circle inscribed in the triangle. + * + * @function Phaser.Geom.Triangle.InCenter + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to find the incenter of. + * @param {Phaser.Geom.Point} [out] - An optional Point in which to store the coordinates. + * + * @return {Phaser.Geom.Point} Point (x, y) of the center pixel of the triangle. + */ +var InCenter = function (triangle, out) { + if (out === undefined) { out = new Point(); } + + var x1 = triangle.x1; + var y1 = triangle.y1; + + var x2 = triangle.x2; + var y2 = triangle.y2; + + var x3 = triangle.x3; + var y3 = triangle.y3; + + var d1 = getLength(x3, y3, x2, y2); + var d2 = getLength(x1, y1, x3, y3); + var d3 = getLength(x2, y2, x1, y1); + + var p = d1 + d2 + d3; + + out.x = (x1 * d1 + x2 * d2 + x3 * d3) / p; + out.y = (y1 * d1 + y2 * d2 + y3 * d3) / p; + + return out; +}; + +export default InCenter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Offset.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Offset.js new file mode 100644 index 000000000..0b0786c56 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Offset.js @@ -0,0 +1,35 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Moves each point (vertex) of a Triangle by a given offset, thus moving the entire Triangle by that offset. + * + * @function Phaser.Geom.Triangle.Offset + * @since 3.0.0 + * + * @generic {Phaser.Geom.Triangle} O - [triangle,$return] + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to move. + * @param {number} x - The horizontal offset (distance) by which to move each point. Can be positive or negative. + * @param {number} y - The vertical offset (distance) by which to move each point. Can be positive or negative. + * + * @return {Phaser.Geom.Triangle} The modified Triangle. + */ +var Offset = function (triangle, x, y) +{ + triangle.x1 += x; + triangle.y1 += y; + + triangle.x2 += x; + triangle.y2 += y; + + triangle.x3 += x; + triangle.y3 += y; + + return triangle; +}; + +export default Offset; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Perimeter.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Perimeter.js new file mode 100644 index 000000000..e66451fa3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Perimeter.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Length from '../line/Length.js'; + +// The 2D area of a triangle. The area value is always non-negative. + +/** + * Gets the length of the perimeter of the given triangle. + * + * @function Phaser.Geom.Triangle.Perimeter + * @since 3.0.0 + * + * @param {Phaser.Geom.Triangle} triangle - [description] + * + * @return {number} [description] + */ +var Perimeter = function (triangle) { + var line1 = triangle.getLineA(); + var line2 = triangle.getLineB(); + var line3 = triangle.getLineC(); + + return (Length(line1) + Length(line2) + Length(line3)); +}; + +export default Perimeter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Random.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Random.js new file mode 100644 index 000000000..fde97e06b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Random.js @@ -0,0 +1,48 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Point from '../point/Point.js'; + +/** + * [description] + * + * @function Phaser.Geom.Triangle.Random + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [out,$return] + * + * @param {Phaser.Geom.Triangle} triangle - [description] + * @param {Phaser.Geom.Point} [out] - [description] + * + * @return {Phaser.Geom.Point} [description] + */ +var Random = function (triangle, out) { + if (out === undefined) { out = new Point(); } + + // Basis vectors + var ux = triangle.x2 - triangle.x1; + var uy = triangle.y2 - triangle.y1; + + var vx = triangle.x3 - triangle.x1; + var vy = triangle.y3 - triangle.y1; + + // Random point within the unit square + var r = Math.random(); + var s = Math.random(); + + // Point outside the triangle? Remap it. + if (r + s >= 1) { + r = 1 - r; + s = 1 - s; + } + + out.x = triangle.x1 + ((ux * r) + (vx * s)); + out.y = triangle.y1 + ((uy * r) + (vy * s)); + + return out; +}; + +export default Random; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Rotate.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Rotate.js new file mode 100644 index 000000000..187a13310 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Rotate.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import RotateAroundXY from './RotateAroundXY.js'; +import InCenter from './InCenter.js'; + +/** + * Rotates a Triangle about its incenter, which is the point at which its three angle bisectors meet. + * + * @function Phaser.Geom.Triangle.Rotate + * @since 3.0.0 + * + * @generic {Phaser.Geom.Triangle} O - [triangle,$return] + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to rotate. + * @param {number} angle - The angle by which to rotate the Triangle, in radians. + * + * @return {Phaser.Geom.Triangle} The rotated Triangle. + */ +var Rotate = function (triangle, angle) { + var point = InCenter(triangle); + + return RotateAroundXY(triangle, point.x, point.y, angle); +}; + +export default Rotate; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/RotateAroundPoint.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/RotateAroundPoint.js new file mode 100644 index 000000000..abc1e400a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/RotateAroundPoint.js @@ -0,0 +1,27 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import RotateAroundXY from './RotateAroundXY.js'; + +/** + * Rotates a Triangle at a certain angle about a given Point or object with public `x` and `y` properties. + * + * @function Phaser.Geom.Triangle.RotateAroundPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Triangle} O - [triangle,$return] + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to rotate. + * @param {Phaser.Geom.Point} point - The Point to rotate the Triangle about. + * @param {number} angle - The angle by which to rotate the Triangle, in radians. + * + * @return {Phaser.Geom.Triangle} The rotated Triangle. + */ +var RotateAroundPoint = function (triangle, point, angle) { + return RotateAroundXY(triangle, point.x, point.y, angle); +}; + +export default RotateAroundPoint; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/RotateAroundXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/RotateAroundXY.js new file mode 100644 index 000000000..796497d02 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/RotateAroundXY.js @@ -0,0 +1,48 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Rotates an entire Triangle at a given angle about a specific point. + * + * @function Phaser.Geom.Triangle.RotateAroundXY + * @since 3.0.0 + * + * @generic {Phaser.Geom.Triangle} O - [triangle,$return] + * + * @param {Phaser.Geom.Triangle} triangle - The Triangle to rotate. + * @param {number} x - The X coordinate of the point to rotate the Triangle about. + * @param {number} y - The Y coordinate of the point to rotate the Triangle about. + * @param {number} angle - The angle by which to rotate the Triangle, in radians. + * + * @return {Phaser.Geom.Triangle} The rotated Triangle. + */ +var RotateAroundXY = function (triangle, x, y, angle) +{ + var c = Math.cos(angle); + var s = Math.sin(angle); + + var tx = triangle.x1 - x; + var ty = triangle.y1 - y; + + triangle.x1 = tx * c - ty * s + x; + triangle.y1 = tx * s + ty * c + y; + + tx = triangle.x2 - x; + ty = triangle.y2 - y; + + triangle.x2 = tx * c - ty * s + x; + triangle.y2 = tx * s + ty * c + y; + + tx = triangle.x3 - x; + ty = triangle.y3 - y; + + triangle.x3 = tx * c - ty * s + x; + triangle.y3 = tx * s + ty * c + y; + + return triangle; +}; + +export default RotateAroundXY; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Triangle.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Triangle.d.ts new file mode 100644 index 000000000..3bd28e829 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Triangle.d.ts @@ -0,0 +1,331 @@ +import Point from '../point/Point'; +import Circle from '../circle/Circle'; +import Line from '../line/Line'; +import { Vector2Like } from '../types'; + +/** + * A triangle is a plane created by connecting three points. + * The first two arguments specify the first point, the middle two arguments + * specify the second point, and the last two arguments specify the third point. + */ +declare class Triangle { + /** + * + * @param x1 `x` coordinate of the first point. Default 0. + * @param y1 `y` coordinate of the first point. Default 0. + * @param x2 `x` coordinate of the second point. Default 0. + * @param y2 `y` coordinate of the second point. Default 0. + * @param x3 `x` coordinate of the third point. Default 0. + * @param y3 `y` coordinate of the third point. Default 0. + */ + constructor(x1?: number, y1?: number, x2?: number, y2?: number, x3?: number, y3?: number); + + /** + * Returns the area of a Triangle. + * @param triangle The Triangle to use. + */ + static Area(triangle: Triangle): number; + + /** + * Builds an equilateral triangle. In the equilateral triangle, all the sides are the same length (congruent) and all the angles are the same size (congruent). + * The x/y specifies the top-middle of the triangle (x1/y1) and length is the length of each side. + * @param x x coordinate of the top point of the triangle. + * @param y y coordinate of the top point of the triangle. + * @param length Length of each side of the triangle. + */ + static BuildEquilateral(x: number, y: number, length: number): Triangle; + + /** + * Takes an array of vertex coordinates, and optionally an array of hole indices, then returns an array + * of Triangle instances, where the given vertices have been decomposed into a series of triangles. + * @param data A flat array of vertex coordinates like [x0,y0, x1,y1, x2,y2, ...] + * @param holes An array of hole indices if any (e.g. [5, 8] for a 12-vertex input would mean one hole with vertices 5–7 and another with 8–11). Default null. + * @param scaleX Horizontal scale factor to multiply the resulting points by. Default 1. + * @param scaleY Vertical scale factor to multiply the resulting points by. Default 1. + * @param out An array to store the resulting Triangle instances in. If not provided, a new array is created. + */ + static BuildFromPolygon(data: any[], holes?: any[], scaleX?: number, scaleY?: number, out?: O): O; + + /** + * Builds a right triangle, i.e. one which has a 90-degree angle and two acute angles. + * @param x The X coordinate of the right angle, which will also be the first X coordinate of the constructed Triangle. + * @param y The Y coordinate of the right angle, which will also be the first Y coordinate of the constructed Triangle. + * @param width The length of the side which is to the left or to the right of the right angle. + * @param height The length of the side which is above or below the right angle. + */ + static BuildRight(x: number, y: number, width: number, height: number): Triangle; + + /** + * Positions the Triangle so that it is centered on the given coordinates. + * @param triangle The triangle to be positioned. + * @param x The horizontal coordinate to center on. + * @param y The vertical coordinate to center on. + * @param centerFunc The function used to center the triangle. Defaults to Centroid centering. + */ + static CenterOn(triangle: O, x: number, y: number, centerFunc?: CenterFunction): O; + + /** + * Calculates the position of a Triangle's centroid, which is also its center of mass (center of gravity). + * + * The centroid is the point in a Triangle at which its three medians (the lines drawn from the vertices to the bisectors of the opposite sides) meet. It divides each one in a 2:1 ratio. + * @param triangle The Triangle to use. + * @param out An object to store the coordinates in. + */ + static Centroid(triangle: Triangle, out?: O): O; + + /** + * Computes the circumcentre of a triangle. The circumcentre is the centre of + * the circumcircle, the smallest circle which encloses the triangle. It is also + * the common intersection point of the perpendicular bisectors of the sides of + * the triangle, and is the only point which has equal distance to all three + * vertices of the triangle. + * @param triangle The Triangle to get the circumcenter of. + * @param out The Vector2 object to store the position in. If not given, a new Vector2 instance is created. + */ + static CircumCenter(triangle: Triangle, out?: Vector2Like): Vector2Like; + + /** + * Finds the circumscribed circle (circumcircle) of a Triangle object. The circumcircle is the circle which touches all of the triangle's vertices. + * @param triangle The Triangle to use as input. + * @param out An optional Circle to store the result in. + */ + static CircumCircle(triangle: Triangle, out?: Circle): Circle; + + /** + * Clones a Triangle object. + * @param source The Triangle to clone. + */ + static Clone(source: Triangle): Triangle; + + /** + * Checks if a point (as a pair of coordinates) is inside a Triangle's bounds. + * @param triangle The Triangle to check. + * @param x The X coordinate of the point to check. + * @param y The Y coordinate of the point to check. + */ + static Contains(triangle: Triangle, x: number, y: number): boolean; + + /** + * Filters an array of point-like objects to only those contained within a triangle. + * If `returnFirst` is true, will return an array containing only the first point in the provided array that is within the triangle (or an empty array if there are no such points). + * @param triangle The triangle that the points are being checked in. + * @param points An array of point-like objects (objects that have an `x` and `y` property) + * @param returnFirst If `true`, return an array containing only the first point found that is within the triangle. Default false. + * @param out If provided, the points that are within the triangle will be appended to this array instead of being added to a new array. If `returnFirst` is true, only the first point found within the triangle will be appended. This array will also be returned by this function. + */ + static ContainsArray(triangle: Triangle, points: Point[], returnFirst?: boolean, out?: any[]): Point[]; + + /** + * Tests if a triangle contains a point. + * @param triangle The triangle. + * @param point The point to test, or any point-like object with public `x` and `y` properties. + */ + static ContainsPoint(triangle: Triangle, point: Vector2Like): boolean; + + /** + * Copy the values of one Triangle to a destination Triangle. + * @param source The source Triangle to copy the values from. + * @param dest The destination Triangle to copy the values to. + */ + static CopyFrom(source: Triangle, dest: O): O; + + /** + * Decomposes a Triangle into an array of its points. + * @param triangle The Triangle to decompose. + * @param out An array to store the points into. + */ + static Decompose(triangle: Triangle, out?: any[]): any[]; + + /** + * Returns true if two triangles have the same coordinates. + * @param triangle The first triangle to check. + * @param toCompare The second triangle to check. + */ + static Equals(triangle: Triangle, toCompare: Triangle): boolean; + + /** + * Returns a Point from around the perimeter of a Triangle. + * @param triangle The Triangle to get the point on its perimeter from. + * @param position The position along the perimeter of the triangle. A value between 0 and 1. + * @param out An option Point, or Point-like object to store the value in. If not given a new Point will be created. + */ + static GetPoint(triangle: Triangle, position: number, out?: O): O; + + /** + * Returns an array of evenly spaced points on the perimeter of a Triangle. + * @param triangle The Triangle to get the points from. + * @param quantity The number of evenly spaced points to return. Set to 0 to return an arbitrary number of points based on the `stepRate`. + * @param stepRate If `quantity` is 0, the distance between each returned point. + * @param out An array to which the points should be appended. + */ + static GetPoints(triangle: Triangle, quantity: number, stepRate: number, out?: O): O; + + /** + * Calculates the position of the incenter of a Triangle object. This is the point where its three angle bisectors meet and it's also the center of the incircle, which is the circle inscribed in the triangle. + * @param triangle The Triangle to find the incenter of. + * @param out An optional Point in which to store the coordinates. + */ + static InCenter(triangle: Triangle, out?: O): O; + + /** + * Moves each point (vertex) of a Triangle by a given offset, thus moving the entire Triangle by that offset. + * @param triangle The Triangle to move. + * @param x The horizontal offset (distance) by which to move each point. Can be positive or negative. + * @param y The vertical offset (distance) by which to move each point. Can be positive or negative. + */ + static Offset(triangle: O, x: number, y: number): O; + + /** + * Gets the length of the perimeter of the given triangle. + * Calculated by adding together the length of each of the three sides. + * @param triangle The Triangle to get the length from. + */ + static Perimeter(triangle: Triangle): number; + + /** + * Returns a random Point from within the area of the given Triangle. + * @param triangle The Triangle to get a random point from. + * @param out The Point object to store the position in. If not given, a new Point instance is created. + */ + static Random(triangle: Triangle, out?: O): O; + + /** + * Rotates a Triangle about its incenter, which is the point at which its three angle bisectors meet. + * @param triangle The Triangle to rotate. + * @param angle The angle by which to rotate the Triangle, in radians. + */ + static Rotate(triangle: O, angle: number): O; + + /** + * Rotates a Triangle at a certain angle about a given Point or object with public `x` and `y` properties. + * @param triangle The Triangle to rotate. + * @param point The Point to rotate the Triangle about. + * @param angle The angle by which to rotate the Triangle, in radians. + */ + static RotateAroundPoint(triangle: O, point: Point, angle: number): O; + + /** + * Rotates an entire Triangle at a given angle about a specific point. + * @param triangle The Triangle to rotate. + * @param x The X coordinate of the point to rotate the Triangle about. + * @param y The Y coordinate of the point to rotate the Triangle about. + * @param angle The angle by which to rotate the Triangle, in radians. + */ + static RotateAroundXY(triangle: O, x: number, y: number, angle: number): O; + + /** + * The geometry constant type of this object: `GEOM_CONST.TRIANGLE`. + * Used for fast type comparisons. + */ + readonly type: number; + + /** + * `x` coordinate of the first point. + */ + x1: number; + + /** + * `y` coordinate of the first point. + */ + y1: number; + + /** + * `x` coordinate of the second point. + */ + x2: number; + + /** + * `y` coordinate of the second point. + */ + y2: number; + + /** + * `x` coordinate of the third point. + */ + x3: number; + + /** + * `y` coordinate of the third point. + */ + y3: number; + + /** + * Checks whether a given points lies within the triangle. + * @param x The x coordinate of the point to check. + * @param y The y coordinate of the point to check. + */ + contains(x: number, y: number): boolean; + + /** + * Returns a specific point on the triangle. + * @param position Position as float within `0` and `1`. `0` equals the first point. + * @param output Optional Point, or point-like object, that the calculated point will be written to. + */ + getPoint(position: number, output?: O): O; + + /** + * Calculates a list of evenly distributed points on the triangle. It is either possible to pass an amount of points to be generated (`quantity`) or the distance between two points (`stepRate`). + * @param quantity Number of points to be generated. Can be falsey when `stepRate` should be used. All points have the same distance along the triangle. + * @param stepRate Distance between two points. Will only be used when `quantity` is falsey. + * @param output Optional Array for writing the calculated points into. Otherwise a new array will be created. + */ + getPoints(quantity: number, stepRate?: number, output?: O): O; + + /** + * Returns a random point along the triangle. + * @param point Optional `Point` that should be modified. Otherwise a new one will be created. + */ + getRandomPoint(point?: O): O; + + /** + * Sets all three points of the triangle. Leaving out any coordinate sets it to be `0`. + * @param x1 `x` coordinate of the first point. Default 0. + * @param y1 `y` coordinate of the first point. Default 0. + * @param x2 `x` coordinate of the second point. Default 0. + * @param y2 `y` coordinate of the second point. Default 0. + * @param x3 `x` coordinate of the third point. Default 0. + * @param y3 `y` coordinate of the third point. Default 0. + */ + setTo(x1?: number, y1?: number, x2?: number, y2?: number, x3?: number, y3?: number): this; + + /** + * Returns a Line object that corresponds to Line A of this Triangle. + * @param line A Line object to set the results in. If `undefined` a new Line will be created. + */ + getLineA(line?: Line): Line; + + /** + * Returns a Line object that corresponds to Line B of this Triangle. + * @param line A Line object to set the results in. If `undefined` a new Line will be created. + */ + getLineB(line?: Line): Line; + + /** + * Returns a Line object that corresponds to Line C of this Triangle. + * @param line A Line object to set the results in. If `undefined` a new Line will be created. + */ + getLineC(line?: Line): Line; + + /** + * Left most X coordinate of the triangle. Setting it moves the triangle on the X axis accordingly. + */ + left: number; + + /** + * Right most X coordinate of the triangle. Setting it moves the triangle on the X axis accordingly. + */ + right: number; + + /** + * Top most Y coordinate of the triangle. Setting it moves the triangle on the Y axis accordingly. + */ + top: number; + + /** + * Bottom most Y coordinate of the triangle. Setting it moves the triangle on the Y axis accordingly. + */ + bottom: number; + +} + +export default Triangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Triangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Triangle.js new file mode 100644 index 000000000..9fb44847d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Triangle.js @@ -0,0 +1,400 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Class from '../../object/Class.js'; +import Contains from './Contains.js'; +import GetPoint from './GetPoint.js'; +import GetPoints from './GetPoints.js'; +import Line from '../line/Line.js'; +import Random from './Random.js'; + +/** + * @classdesc + * A triangle is a plane created by connecting three points. + * The first two arguments specify the first point, the middle two arguments + * specify the second point, and the last two arguments specify the third point. + * + * @class Triangle + * @memberof Phaser.Geom + * @constructor + * @since 3.0.0 + * + * @param {number} [x1=0] - `x` coordinate of the first point. + * @param {number} [y1=0] - `y` coordinate of the first point. + * @param {number} [x2=0] - `x` coordinate of the second point. + * @param {number} [y2=0] - `y` coordinate of the second point. + * @param {number} [x3=0] - `x` coordinate of the third point. + * @param {number} [y3=0] - `y` coordinate of the third point. + */ +var Triangle = new Class({ + + initialize: + + function Triangle(x1, y1, x2, y2, x3, y3) { + if (x1 === undefined) { x1 = 0; } + if (y1 === undefined) { y1 = 0; } + if (x2 === undefined) { x2 = 0; } + if (y2 === undefined) { y2 = 0; } + if (x3 === undefined) { x3 = 0; } + if (y3 === undefined) { y3 = 0; } + + /** + * `x` coordinate of the first point. + * + * @name Phaser.Geom.Triangle#x1 + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.x1 = x1; + + /** + * `y` coordinate of the first point. + * + * @name Phaser.Geom.Triangle#y1 + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.y1 = y1; + + /** + * `x` coordinate of the second point. + * + * @name Phaser.Geom.Triangle#x2 + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.x2 = x2; + + /** + * `y` coordinate of the second point. + * + * @name Phaser.Geom.Triangle#y2 + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.y2 = y2; + + /** + * `x` coordinate of the third point. + * + * @name Phaser.Geom.Triangle#x3 + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.x3 = x3; + + /** + * `y` coordinate of the third point. + * + * @name Phaser.Geom.Triangle#y3 + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.y3 = y3; + }, + + /** + * Checks whether a given points lies within the triangle. + * + * @method Phaser.Geom.Triangle#contains + * @since 3.0.0 + * + * @param {number} x - The x coordinate of the point to check. + * @param {number} y - The y coordinate of the point to check. + * + * @return {boolean} `true` if the coordinate pair is within the triangle, otherwise `false`. + */ + contains: function (x, y) { + return Contains(this, x, y); + }, + + /** + * Returns a specific point on the triangle. + * + * @method Phaser.Geom.Triangle#getPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [output,$return] + * + * @param {number} position - Position as float within `0` and `1`. `0` equals the first point. + * @param {(Phaser.Geom.Point|object)} [output] - Optional Point, or point-like object, that the calculated point will be written to. + * + * @return {(Phaser.Geom.Point|object)} Calculated `Point` that represents the requested position. It is the same as `output` when this parameter has been given. + */ + getPoint: function (position, output) { + return GetPoint(this, position, output); + }, + + /** + * Calculates a list of evenly distributed points on the triangle. It is either possible to pass an amount of points to be generated (`quantity`) or the distance between two points (`stepRate`). + * + * @method Phaser.Geom.Triangle#getPoints + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point[]} O - [output,$return] + * + * @param {integer} quantity - Number of points to be generated. Can be falsey when `stepRate` should be used. All points have the same distance along the triangle. + * @param {number} [stepRate] - Distance between two points. Will only be used when `quantity` is falsey. + * @param {(array|Phaser.Geom.Point[])} [output] - Optional Array for writing the calculated points into. Otherwise a new array will be created. + * + * @return {(array|Phaser.Geom.Point[])} Returns a list of calculated `Point` instances or the filled array passed as parameter `output`. + */ + getPoints: function (quantity, stepRate, output) { + return GetPoints(this, quantity, stepRate, output); + }, + + /** + * Returns a random point along the triangle. + * + * @method Phaser.Geom.Triangle#getRandomPoint + * @since 3.0.0 + * + * @generic {Phaser.Geom.Point} O - [point,$return] + * + * @param {Phaser.Geom.Point} [point] - Optional `Point` that should be modified. Otherwise a new one will be created. + * + * @return {Phaser.Geom.Point} Random `Point`. When parameter `point` has been provided it will be returned. + */ + getRandomPoint: function (point) { + return Random(this, point); + }, + + /** + * Sets all three points of the triangle. Leaving out any coordinate sets it to be `0`. + * + * @method Phaser.Geom.Triangle#setTo + * @since 3.0.0 + * + * @param {number} [x1=0] - `x` coordinate of the first point. + * @param {number} [y1=0] - `y` coordinate of the first point. + * @param {number} [x2=0] - `x` coordinate of the second point. + * @param {number} [y2=0] - `y` coordinate of the second point. + * @param {number} [x3=0] - `x` coordinate of the third point. + * @param {number} [y3=0] - `y` coordinate of the third point. + * + * @return {Phaser.Geom.Triangle} This Triangle object. + */ + setTo: function (x1, y1, x2, y2, x3, y3) { + if (x1 === undefined) { x1 = 0; } + if (y1 === undefined) { y1 = 0; } + if (x2 === undefined) { x2 = 0; } + if (y2 === undefined) { y2 = 0; } + if (x3 === undefined) { x3 = 0; } + if (y3 === undefined) { y3 = 0; } + + this.x1 = x1; + this.y1 = y1; + + this.x2 = x2; + this.y2 = y2; + + this.x3 = x3; + this.y3 = y3; + + return this; + }, + + /** + * Returns a Line object that corresponds to Line A of this Triangle. + * + * @method Phaser.Geom.Triangle#getLineA + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [line,$return] + * + * @param {Phaser.Geom.Line} [line] - A Line object to set the results in. If `undefined` a new Line will be created. + * + * @return {Phaser.Geom.Line} A Line object that corresponds to line A of this Triangle. + */ + getLineA: function (line) { + if (line === undefined) { line = new Line(); } + + line.setTo(this.x1, this.y1, this.x2, this.y2); + + return line; + }, + + /** + * Returns a Line object that corresponds to Line B of this Triangle. + * + * @method Phaser.Geom.Triangle#getLineB + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [line,$return] + * + * @param {Phaser.Geom.Line} [line] - A Line object to set the results in. If `undefined` a new Line will be created. + * + * @return {Phaser.Geom.Line} A Line object that corresponds to line B of this Triangle. + */ + getLineB: function (line) { + if (line === undefined) { line = new Line(); } + + line.setTo(this.x2, this.y2, this.x3, this.y3); + + return line; + }, + + /** + * Returns a Line object that corresponds to Line C of this Triangle. + * + * @method Phaser.Geom.Triangle#getLineC + * @since 3.0.0 + * + * @generic {Phaser.Geom.Line} O - [line,$return] + * + * @param {Phaser.Geom.Line} [line] - A Line object to set the results in. If `undefined` a new Line will be created. + * + * @return {Phaser.Geom.Line} A Line object that corresponds to line C of this Triangle. + */ + getLineC: function (line) { + if (line === undefined) { line = new Line(); } + + line.setTo(this.x3, this.y3, this.x1, this.y1); + + return line; + }, + + /** + * Left most X coordinate of the triangle. Setting it moves the triangle on the X axis accordingly. + * + * @name Phaser.Geom.Triangle#left + * @type {number} + * @since 3.0.0 + */ + left: { + + get: function () { + return Math.min(this.x1, this.x2, this.x3); + }, + + set: function (value) { + var diff = 0; + + if (this.x1 <= this.x2 && this.x1 <= this.x3) { + diff = this.x1 - value; + } + else if (this.x2 <= this.x1 && this.x2 <= this.x3) { + diff = this.x2 - value; + } + else { + diff = this.x3 - value; + } + + this.x1 -= diff; + this.x2 -= diff; + this.x3 -= diff; + } + + }, + + /** + * Right most X coordinate of the triangle. Setting it moves the triangle on the X axis accordingly. + * + * @name Phaser.Geom.Triangle#right + * @type {number} + * @since 3.0.0 + */ + right: { + + get: function () { + return Math.max(this.x1, this.x2, this.x3); + }, + + set: function (value) { + var diff = 0; + + if (this.x1 >= this.x2 && this.x1 >= this.x3) { + diff = this.x1 - value; + } + else if (this.x2 >= this.x1 && this.x2 >= this.x3) { + diff = this.x2 - value; + } + else { + diff = this.x3 - value; + } + + this.x1 -= diff; + this.x2 -= diff; + this.x3 -= diff; + } + + }, + + /** + * Top most Y coordinate of the triangle. Setting it moves the triangle on the Y axis accordingly. + * + * @name Phaser.Geom.Triangle#top + * @type {number} + * @since 3.0.0 + */ + top: { + + get: function () { + return Math.min(this.y1, this.y2, this.y3); + }, + + set: function (value) { + var diff = 0; + + if (this.y1 <= this.y2 && this.y1 <= this.y3) { + diff = this.y1 - value; + } + else if (this.y2 <= this.y1 && this.y2 <= this.y3) { + diff = this.y2 - value; + } + else { + diff = this.y3 - value; + } + + this.y1 -= diff; + this.y2 -= diff; + this.y3 -= diff; + } + + }, + + /** + * Bottom most Y coordinate of the triangle. Setting it moves the triangle on the Y axis accordingly. + * + * @name Phaser.Geom.Triangle#bottom + * @type {number} + * @since 3.0.0 + */ + bottom: { + + get: function () { + return Math.max(this.y1, this.y2, this.y3); + }, + + set: function (value) { + var diff = 0; + + if (this.y1 >= this.y2 && this.y1 >= this.y3) { + diff = this.y1 - value; + } + else if (this.y2 >= this.y1 && this.y2 >= this.y3) { + diff = this.y2 - value; + } + else { + diff = this.y3 - value; + } + + this.y1 -= diff; + this.y2 -= diff; + this.y3 -= diff; + } + + } + +}); + +export default Triangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/index.js new file mode 100644 index 000000000..757b523af --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/index.js @@ -0,0 +1,58 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Triangle from './Triangle.js'; +import Area from './Area.js'; +import BuildEquilateral from './BuildEquilateral.js'; +import BuildFromPolygon from './BuildFromPolygon.js'; +import BuildRight from './BuildRight.js'; +import CenterOn from './CenterOn.js'; +import Centroid from './Centroid.js'; +import CircumCenter from './CircumCenter.js'; +import CircumCircle from './CircumCircle.js'; +import Clone from './Clone.js'; +import Contains from './Contains.js'; +import ContainsArray from './ContainsArray.js'; +import ContainsPoint from './ContainsPoint.js'; +import CopyFrom from './CopyFrom.js'; +import Decompose from './Decompose.js'; +import Equals from './Equals.js'; +import GetPoint from './GetPoint.js'; +import GetPoints from './GetPoints.js'; +import InCenter from './InCenter.js'; +import Perimeter from './Perimeter.js'; +import Offset from './Offset.js'; +import Random from './Random.js'; +import Rotate from './Rotate.js'; +import RotateAroundPoint from './RotateAroundPoint.js'; +import RotateAroundXY from './RotateAroundXY.js'; + +Triangle.Area = Area; +Triangle.BuildEquilateral = BuildEquilateral; +Triangle.BuildFromPolygon = BuildFromPolygon; +Triangle.BuildRight = BuildRight; +Triangle.CenterOn = CenterOn; +Triangle.Centroid = Centroid; +Triangle.CircumCenter = CircumCenter; +Triangle.CircumCircle = CircumCircle; +Triangle.Clone = Clone; +Triangle.Contains = Contains; +Triangle.ContainsArray = ContainsArray; +Triangle.ContainsPoint = ContainsPoint; +Triangle.CopyFrom = CopyFrom; +Triangle.Decompose = Decompose; +Triangle.Equals = Equals; +Triangle.GetPoint = GetPoint; +Triangle.GetPoints = GetPoints; +Triangle.InCenter = InCenter; +Triangle.Perimeter = Perimeter; +Triangle.Offset = Offset; +Triangle.Random = Random; +Triangle.Rotate = Rotate; +Triangle.RotateAroundPoint = RotateAroundPoint; +Triangle.RotateAroundXY = RotateAroundXY; + +export default Triangle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/geom/types.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/geom/types.d.ts new file mode 100644 index 000000000..3cbbfa392 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/geom/types.d.ts @@ -0,0 +1,6 @@ +export type Vector2Like = { x?: number, y?: number }; + +export class Vector2 { + x: number; + y: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/CubeTransfer.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/CubeTransfer.js new file mode 100644 index 000000000..376e8016b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/CubeTransfer.js @@ -0,0 +1,134 @@ +import CONST from './const.js'; + +const ODD_R = CONST.ODD_R; +const EVEN_R = CONST.EVEN_R; +const ODD_Q = CONST.ODD_Q; +const EVEN_Q = CONST.EVEN_Q; + +var cr2cube = function (mode, col, row, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globCube; + } + switch (mode) { + case ODD_R: + out.x = col - (row - (row & 1)) / 2; + out.z = row; + break; + + case EVEN_R: + out.x = col - (row + (row & 1)) / 2; + out.z = row; + break; + + case ODD_Q: + out.x = col; + out.z = row - (col - (col & 1)) / 2; + break; + case EVEN_Q: + out.x = col; + out.z = row - (col + (col & 1)) / 2; + break; + } + out.y = -out.x - out.z; + return out; +} + +var roundcube = function (x, y, z, out) { + if (typeof (x) !== 'number') { + out = x; + x = out.x; + y = out.y; + z = out.z; + } + + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globCube; + } + var rx = Math.round(x); + var ry = Math.round(y); + var rz = Math.round(z); + + var dx = Math.abs(rx - x); + var dy = Math.abs(ry - y); + var dz = Math.abs(rz - z); + + if ((dx > dy) && (dx > dz)) { + rx = -ry - rz; + } else if (dy > dz) { + ry = -rx - rz; + } else { + rz = -rx - ry; + } + out.x = rx; + out.y = ry; + out.z = rz; + return out; +} + +var cube2cr = function (mode, x, y, z, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globCR; + } + switch (mode) { + case ODD_R: + out.x = x + (z - (z & 1)) / 2; + out.y = z; + break; + case EVEN_R: + out.x = x + (z + (z & 1)) / 2; + out.y = z; + break; + + case ODD_Q: + out.x = x; + out.y = z + (x - (x & 1)) / 2; + break; + case EVEN_Q: + out.x = x; + out.y = z + (x + (x & 1)) / 2; + break; + } + return out; +} + +var qr2cube = function (q, r, out) { + if (out === undefined) { + out = {} + } else if (out === true) { + out = globCube; + } + out.x = q; + out.y = -q - r; + out.z = r; + return out; +} + +var cube2qr = function (x, y, z, out) { + if (out === undefined) { + out = {} + } else if (out === true) { + out = globQR; + } + + out.q = x; + out.r = z; + return out; +} + +var globCube = {}; +var globCR = {}; +var globQR = {}; + +export { + cr2cube, + roundcube, + cube2cr, + qr2cube, + cube2qr, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/DeltaTileXYToDirection.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/DeltaTileXYToDirection.js new file mode 100644 index 000000000..8bd9c7304 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/DeltaTileXYToDirection.js @@ -0,0 +1,42 @@ +// Not included in Base Gird object. +// Delta tileXY to direction + +import DirectionToDeltaTileXY from './DirectionToDeltaTileXY.js'; + +var ReverseDirMap = function (dirMap) { + var out = {}, + entry, x, y; + for (var dir in dirMap) { + entry = dirMap[dir]; // [x, y] + x = entry[0]; + y = entry[1]; + if (!out.hasOwnProperty(x)) { + out[x] = {} + } + out[x][y] = parseInt(dir); + } + return out; +} + +const Neighbors = [ + [ + ReverseDirMap(DirectionToDeltaTileXY[0][0]), + ReverseDirMap(DirectionToDeltaTileXY[0][1]) + ], + [ + ReverseDirMap(DirectionToDeltaTileXY[1][0]), + ReverseDirMap(DirectionToDeltaTileXY[1][1]) + ], + [ + ReverseDirMap(DirectionToDeltaTileXY[2][0]), + ReverseDirMap(DirectionToDeltaTileXY[2][1]) + ], + [ + ReverseDirMap(DirectionToDeltaTileXY[3][0]), + ReverseDirMap(DirectionToDeltaTileXY[3][1]) + ] +]; + +// Neighbors[mode][parity][x][y]: dir + +export default Neighbors; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/DirectionBetween.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/DirectionBetween.js new file mode 100644 index 000000000..d303227d7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/DirectionBetween.js @@ -0,0 +1,44 @@ +import { + cr2cube +} from './CubeTransfer.js'; + +var DirectionBetween = function (tileA, tileB, round) { + if (round === undefined) { + round = true; + } + + var direction; + cr2cube(this.mode, tileA.x, tileA.y, globCubeA); + cr2cube(this.mode, tileB.x, tileB.y, globCubeB); + var dx = globCubeB.x - globCubeA.x; + var dy = globCubeB.y - globCubeA.y; + var dz = globCubeB.z - globCubeA.z; + if (dz === 0) { + direction = (dx > 0) ? 0 : 3; + } else if (dx === 0) { + direction = (dz > 0) ? 1 : 4; + } else if (dy === 0) { + direction = (dz > 0) ? 2 : 5; + } else if ((dx > 0) && (dy < 0) && (dz > 0)) { // 0~1 + direction = 0 + (dz / (-dy)); + } else if ((dx < 0) && (dy < 0) && (dz > 0)) { // 1~2 + direction = 1 + ((-dy) / dz); + } else if ((dx < 0) && (dy > 0) && (dz > 0)) { // 2~3 + direction = 2 + (dy / (-dx)); + } else if ((dx < 0) && (dy > 0) && (dz < 0)) { // 3~4 + direction = 3 + ((-dz) / dy); + } else if ((dx > 0) && (dy > 0) && (dz < 0)) { // 4~5 + direction = 4 + (dx / (-dz)); + } else { // ((dx > 0) && (dy < 0) && (dz < 0)) // 5~0 + direction = 5 + ((-dy) / dx); + } + + if (round) { + direction = Math.round(direction); + } + return direction; +} + +var globCubeA = {}; +var globCubeB = {}; +export default DirectionBetween; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/DirectionToDeltaTileXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/DirectionToDeltaTileXY.js new file mode 100644 index 000000000..eb9fc8f40 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/DirectionToDeltaTileXY.js @@ -0,0 +1,85 @@ +// Not included in Base Gird object. +// Direction to delta tileXY + +const ODD_R = [ + [ + [+1, 0], + [0, +1], + [-1, +1], + [-1, 0], + [-1, -1], + [0, -1] + ], + [ + [+1, 0], + [+1, +1], + [0, +1], + [-1, 0], + [0, -1], + [+1, -1] + ] +]; +const EVEN_R = [ + [ + [+1, 0], + [+1, +1], + [0, +1], + [-1, 0], + [0, -1], + [+1, -1] + ], + [ + [+1, 0], + [0, +1], + [-1, +1], + [-1, 0], + [-1, -1], + [0, -1] + ] +]; +const ODD_Q = [ + [ + [+1, 0], + [0, +1], + [-1, 0], + [-1, -1], + [0, -1], + [+1, -1] + ], + [ + [+1, +1], + [0, +1], + [-1, +1], + [-1, 0], + [0, -1], + [+1, 0] + ] +]; +const EVEN_Q = [ + [ + [+1, +1], + [0, +1], + [-1, +1], + [-1, 0], + [0, -1], + [+1, 0] + ], + [ + [+1, 0], + [0, +1], + [-1, 0], + [-1, -1], + [0, -1], + [+1, -1] + ] +]; +const Neighbors = [ + ODD_R, + EVEN_R, + ODD_Q, + EVEN_Q +]; + +// Neighbors[mode][parity][dir]: [x/y] + +export default Neighbors; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetDistance.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetDistance.js new file mode 100644 index 000000000..0b9654461 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetDistance.js @@ -0,0 +1,17 @@ +import { + cr2cube +} from './CubeTransfer.js'; + +var GetDistance = function (tileA, tileB, roughMode) { + cr2cube(this.mode, tileA.x, tileA.y, globCubeA); + cr2cube(this.mode, tileB.x, tileB.y, globCubeB); + var dx = globCubeB.x - globCubeA.x; + var dy = globCubeB.y - globCubeA.y; + var dz = globCubeB.z - globCubeA.z; + return (Math.abs(dx) + Math.abs(dy) + Math.abs(dz)) / 2; +} + +var globCubeA = {}; +var globCubeB = {}; + +export default GetDistance; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetNeighborTileDirection.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetNeighborTileDirection.js new file mode 100644 index 000000000..27980a48e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetNeighborTileDirection.js @@ -0,0 +1,18 @@ +import DeltaTileXYToDirection from './DeltaTileXYToDirection.js'; +import GetParity from './GetParity.js'; + +var GetNeighborTileDirection = function (srcTileXY, neighborTileXY) { + var parity = GetParity(this.mode, srcTileXY.x, srcTileXY.y); + var deltaTileXYToDirMap = DeltaTileXYToDirection[this.mode][parity]; + + var deltaTileX = neighborTileXY.x - srcTileXY.x; + var deltaTileY = neighborTileXY.y - srcTileXY.y; + if (deltaTileXYToDirMap.hasOwnProperty(deltaTileX)) { + var xEntry = deltaTileXYToDirMap[deltaTileX] + if (xEntry.hasOwnProperty(deltaTileY)) { + return xEntry[deltaTileY]; + } + } + return null; +} +export default GetNeighborTileDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetNeighborTileXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetNeighborTileXY.js new file mode 100644 index 000000000..764a485c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetNeighborTileXY.js @@ -0,0 +1,7 @@ +import GetTileXAtDirection from './GetTileXYAtDirection.js'; + +var GetNeighborTileXY = function (tileX, tileY, direction, out) { + return GetTileXAtDirection.call(this, tileX, tileY, direction, 1, out); +}; + +export default GetNeighborTileXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetOppositeDirection.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetOppositeDirection.js new file mode 100644 index 000000000..a0996ba27 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetOppositeDirection.js @@ -0,0 +1,4 @@ +var GetOppositeDirection = function (tileX, tileY, direction) { + return (direction + 3) % 6; +} +export default GetOppositeDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetParity.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetParity.js new file mode 100644 index 000000000..bfc43eff1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetParity.js @@ -0,0 +1,23 @@ +import CONST from './const.js'; + +const ODD_R = CONST.ODD_R; +const EVEN_R = CONST.EVEN_R; +const ODD_Q = CONST.ODD_Q; +const EVEN_Q = CONST.EVEN_Q; + +var GetParity = function(mode, tileX, tileY) { + var parity; + switch (mode) { + case ODD_R: + case EVEN_R: + parity = tileY & 1; + break; + + case ODD_Q: + case EVEN_Q: + parity = tileX & 1; + break; + } + return parity; +} +export default GetParity; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileX.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileX.js new file mode 100644 index 000000000..6a2b762d7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileX.js @@ -0,0 +1,5 @@ +var GetTileX = function (worldX, worldY) { + return this.getTileXY(worldX, worldY, true).x; +} + +export default GetTileX; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileXY.js new file mode 100644 index 000000000..38ae3e279 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileXY.js @@ -0,0 +1,49 @@ +import CONST from './const.js'; +import { + qr2cube, + roundcube, + cube2cr, +} from './CubeTransfer.js'; + +const ODD_R = CONST.ODD_R; +const EVEN_R = CONST.EVEN_R; +const ODD_Q = CONST.ODD_Q; +const EVEN_Q = CONST.EVEN_Q; + +const C4DIV3 = (4 / 3); +const C2DIV3 = (2 / 3); + +var GetTileXY = function (worldX, worldY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + worldX -= this.x; + worldY -= this.y; + var q, r; + switch (this.mode) { + case ODD_R: + case EVEN_R: + r = (worldY * C4DIV3) / this.height; + q = (worldX / this.width) - C2DIV3 * (worldY / this.height); + break; + + case ODD_Q: + case EVEN_Q: + r = (worldY / this.height) - C2DIV3 * (worldX / this.width); + q = (worldX * C4DIV3) / this.width; + break; + } + + var cube = qr2cube(q, r, globCube); + roundcube(cube); + cube2cr(this.mode, cube.x, cube.y, cube.z, out); + return out; +} + +var globCube = {}; +var globTileXY = {}; + +export default GetTileXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileXYAtDirection.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileXYAtDirection.js new file mode 100644 index 000000000..98d56142b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileXYAtDirection.js @@ -0,0 +1,65 @@ +import DirectionToDeltaTileXY from './DirectionToDeltaTileXY.js'; +import GetParity from './GetParity.js'; +import { + cr2cube, + cube2cr +} from './CubeTransfer.js'; + +var GetTileXAtDirection = function (tileX, tileY, direction, distance, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + if (distance === 1) { // Neighbor + var parity = GetParity(this.mode, tileX, tileY); + out.x = tileX + DirectionToDeltaTileXY[this.mode][parity][direction][0]; + out.y = tileY + DirectionToDeltaTileXY[this.mode][parity][direction][1]; + } else if (distance === 0) { + out.x = tileX; + out.y = tileY; + } else { + var cubeXYZ = cr2cube(this.mode, tileX, tileY, true); + var newCubeX, newCubeY, newCubeZ; + switch (direction) { + case 1: + newCubeX = cubeXYZ.x; + newCubeY = cubeXYZ.y - distance; + newCubeZ = cubeXYZ.z + distance; + break; + case 2: + newCubeX = cubeXYZ.x - distance; + newCubeY = cubeXYZ.y; + newCubeZ = cubeXYZ.z + distance; + break; + case 3: + newCubeX = cubeXYZ.x - distance; + newCubeY = cubeXYZ.y + distance; + newCubeZ = cubeXYZ.z; + break; + case 4: + newCubeX = cubeXYZ.x; + newCubeY = cubeXYZ.y + distance; + newCubeZ = cubeXYZ.z - distance; + break; + case 5: + newCubeX = cubeXYZ.x + distance; + newCubeY = cubeXYZ.y; + newCubeZ = cubeXYZ.z - distance; + break; + default: + newCubeX = cubeXYZ.x + distance; + newCubeY = cubeXYZ.y - distance; + newCubeZ = cubeXYZ.z; + break; + } + cube2cr(this.mode, newCubeX, newCubeY, newCubeZ, out); + } + + return out; +} + +var globTileXY = {}; + +export default GetTileXAtDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileY.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileY.js new file mode 100644 index 000000000..16c82e05f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileY.js @@ -0,0 +1,5 @@ +var GetTileY = function (worldX, worldY) { + return this.getTileXY(worldX, worldY, true).y; +} + +export default GetTileY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetWorldX.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetWorldX.js new file mode 100644 index 000000000..fb10ee7c6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetWorldX.js @@ -0,0 +1,5 @@ +var GetWorldX = function (tileX, tileY) { + return this.getWorldXY(tileX, tileY, true).x; +} + +export default GetWorldX; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetWorldXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetWorldXY.js new file mode 100644 index 000000000..c195d588f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetWorldXY.js @@ -0,0 +1,54 @@ +import CONST from './const.js'; + +const ODD_R = CONST.ODD_R; +const EVEN_R = CONST.EVEN_R; +const ODD_Q = CONST.ODD_Q; +const EVEN_Q = CONST.EVEN_Q; + +var GetWorldXY = function (tileX, tileY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globWorldXY; + } + + var worldX = (tileX * this.width); + var worldY = (tileY * this.height); + switch (this.mode) { + case ODD_R: + if (tileY & 1) { + worldX += this._halfWidth; + } + worldY *= 0.75; + break; + + case EVEN_R: + if (tileY & 1) { + worldX -= this._halfWidth; + } + worldY *= 0.75; + break; + + case ODD_Q: + worldX *= 0.75; + if (tileX & 1) { + worldY += this._halfHeight; + } + break; + case EVEN_Q: + worldX *= 0.75; + if (tileX & 1) { + worldY -= this._halfHeight; + } + break; + } + worldX += this.x; + worldY += this.y; + out.x = worldX; + out.y = worldY; + return out; +} + +var globWorldXY = {}; + +export default GetWorldXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetWorldY.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetWorldY.js new file mode 100644 index 000000000..2c72d8444 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetWorldY.js @@ -0,0 +1,5 @@ +var GetWorldY = function (tileX, tileY) { + return this.getWorldXY(tileX, tileY, true).y; +} + +export default GetWorldY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Hexagon.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Hexagon.js new file mode 100644 index 000000000..e0dbee649 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Hexagon.js @@ -0,0 +1,141 @@ +// https://www.redblobgames.com/grids/hexagons/ + +import GetCellWidth from '../../../geom/hexagon/Width.js'; +import GetCellHeight from '../../../geom/hexagon/Height.js'; +import CONST from './const.js'; +import GetWorldXY from './GetWorldXY.js'; +import GetWorldX from './GetWorldX.js'; +import GetWorldY from './GetWorldY.js'; +import GetTileXY from './GetTileXY.js'; +import GetTileX from './GetTileX.js'; +import GetTileY from './GetTileY.js'; +import GetValue from '../../object/GetValue.js'; + +const ODD_R = CONST.ODD_R; +const EVEN_R = CONST.EVEN_R; +const ODD_Q = CONST.ODD_Q; +const EVEN_Q = CONST.EVEN_Q; + +class Hexagon { + constructor(config) { + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.setType(GetValue(o, 'staggeraxis', 1), GetValue(o, 'staggerindex', 1)); + this.setDirectionMode(); + this.setOriginPosition(GetValue(o, 'x', 0), GetValue(o, 'y', 0)); + var size = GetValue(o, 'size', undefined); + if (size !== undefined) { + this.setCellRadius(size); + } else { + this.setCellSize(GetValue(o, 'cellWidth', 0), GetValue(o, 'cellHeight', 0)); + } + } + + setType(staggeraxis, staggerindex) { + if (typeof (staggeraxis) === 'string') { + staggeraxis = STAGGERAXIS[staggeraxis] + } + if (typeof (staggerindex) === 'string') { + staggerindex = STAGGERINDEX[staggerindex] + } + this.staggeraxis = staggeraxis; // 0|y(flat), or 1|x(pointy) + this.staggerindex = staggerindex; // even, or odd + + if (staggeraxis === 0) { // flat + this.mode = (staggerindex === 0) ? EVEN_Q : ODD_Q; + } else { // pointy + this.mode = (staggerindex === 0) ? EVEN_R : ODD_R; + } + return this; + } + + setDirectionMode() { + this.directions = 6; + return this; + } + + setOriginPosition(x, y) { + this.x = x; + this.y = y; + return this; + } + + get width() { + return this._width; + } + + set width(value) { + this._width = value; + this._halfWidth = value / 2; + } + + get height() { + return this._height; + } + + set height(value) { + this._height = value; + this._halfHeight = value / 2; + } + + setCellSize(width, height) { + this.width = width; + this.height = height; + return this; + } + + setCellRadius(size) { + this.size = size; + var hexagon = { + size: this.size, + type: this.staggeraxis + } + var cellWidth = GetCellWidth(hexagon); + var cellHeight = GetCellHeight(hexagon); + this.setCellSize(cellWidth, cellHeight); + return this; + } + + get cellWidth() { + return this.width; + } + + set cellWidth(value) { + this.width = value; + } + + get cellHeight() { + return this.height; + } + + set cellHeight(value) { + this.height = value; + } +} + +var methods = { + getWorldXY: GetWorldXY, + getWorldX: GetWorldX, + getWorldY: GetWorldY, + getTileXY: GetTileXY, + getTileX: GetTileX, + getTileY: GetTileY, +} +Object.assign( + Hexagon.prototype, + methods +); + +const STAGGERAXIS = { + 'y': 0, + 'x': 1 +}; + +const STAGGERINDEX = { + 'even': 0, + 'odd': 1 +} + +export default Hexagon; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Mirror.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Mirror.js new file mode 100644 index 000000000..15b51abbc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Mirror.js @@ -0,0 +1,50 @@ +import { + cr2cube, + cube2cr +} from './CubeTransfer.js'; + +import CONST from './const.js'; + +const ODD_R = CONST.ODD_R; +const EVEN_R = CONST.EVEN_R; + +var Mirror = function (src, mode, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + var cubeXYZ = cr2cube(this.mode, src.x, src.y, true); + var isRMode = (this.mode === ODD_R) || (this.mode === EVEN_R); + var newCubeX, newCubeY, newCubeZ; + if (mode & 1) { // Mirror x + if (isRMode) { + newCubeX = cubeXYZ.y; + newCubeY = cubeXYZ.x; + newCubeZ = cubeXYZ.z; + } else { + newCubeX = -cubeXYZ.x; + newCubeY = -cubeXYZ.z; + newCubeZ = -cubeXYZ.y; + } + cubeXYZ.x = newCubeX; + cubeXYZ.y = newCubeY; + cubeXYZ.z = newCubeZ; + } + if (mode & 2) { // Mirror y + if (isRMode) { + newCubeX = -cubeXYZ.y; + newCubeY = -cubeXYZ.x; + newCubeZ = -cubeXYZ.z; + } else { + newCubeX = cubeXYZ.x; + newCubeY = cubeXYZ.z; + newCubeZ = cubeXYZ.y; + } + } + cube2cr(this.mode, newCubeX, newCubeY, newCubeZ, out); + return out; +} + +var globTileXY = {}; +export default Mirror; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Offset.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Offset.js new file mode 100644 index 000000000..88f656787 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Offset.js @@ -0,0 +1,55 @@ +import CONST from './const.js'; + +const ODD_R = CONST.ODD_R; +const EVEN_R = CONST.EVEN_R; +const ODD_Q = CONST.ODD_Q; +const EVEN_Q = CONST.EVEN_Q; + +var Offset = function (src, offsetX, offsetY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + var newX = src.x + offsetX; + var newY = src.y + offsetY; + switch (this.mode) { + case ODD_R: + if ((offsetY & 1) !== 0) { + if ((newY & 1) === 0) { + newX += 1; + } + } + break; + + case EVEN_R: + if ((offsetY & 1) !== 0) { + if ((newY & 1) === 0) { + newX -= 1; + } + } + break; + + case ODD_Q: + if ((offsetX & 1) !== 0) { + if ((newX & 1) == 0) { + newY += 1; + } + } + break; + case EVEN_Q: + if ((offsetX & 1) !== 0) { + if ((newX & 1) == 0) { + newY -= 1; + } + } + break; + } + out.x = newX; + out.y = newY; + return out; +} + +var globTileXY = {}; +export default Offset; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/RingToTileXYArray.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/RingToTileXYArray.js new file mode 100644 index 000000000..c64aa3b10 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/RingToTileXYArray.js @@ -0,0 +1,55 @@ +import { + cr2cube, + cube2cr +} from './CubeTransfer.js'; + +var RingToTileXYArray = function (centerTileXY, radius, out) { + if (out === undefined) { + out = []; + } + + var centerCube = cr2cube(this.mode, centerTileXY.x, centerTileXY.y, true); + var cx = centerCube.x, + cy = centerCube.y, + cz = centerCube.z; + var i, j, k; + + k = radius; + for (i = 0; i >= -radius; i--) { + j = -i - k; + out.push(cube2cr(this.mode, cx + i, cy + j, cz + k)); + } + + i = -radius; + for (j = 1; j <= radius; j++) { + k = -i - j; + out.push(cube2cr(this.mode, cx + i, cy + j, cz + k)); + } + + j = radius; + for (k = -1; k >= -radius; k--) { + i = -j - k; + out.push(cube2cr(this.mode, cx + i, cy + j, cz + k)); + } + + k = -radius; + for (i = 1; i <= radius; i++) { + j = -i - k; + out.push(cube2cr(this.mode, cx + i, cy + j, cz + k)); + } + + i = radius; + for (j = -1; j >= -radius; j--) { + k = -i - j; + out.push(cube2cr(this.mode, cx + i, cy + j, cz + k)); + } + + j = -radius; + for (k = 1; k <= radius - 1; k++) { + i = -j - k; + out.push(cube2cr(this.mode, cx + i, cy + j, cz + k)); + } + return out; +} + +export default RingToTileXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Rotate.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Rotate.js new file mode 100644 index 000000000..83f18b507 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Rotate.js @@ -0,0 +1,56 @@ +import { + cr2cube, + cube2cr +} from './CubeTransfer.js'; + +import Wrap from '../../math/Wrap.js'; + +var Rotate = function (src, dir, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + dir = Wrap(dir, 0, 5); + var cubeXYZ = cr2cube(this.mode, src.x, src.y, true); + var newCubeX, newCubeY, newCubeZ; + switch (dir) { + case 1: + newCubeX = -cubeXYZ.z; + newCubeY = -cubeXYZ.x; + newCubeZ = -cubeXYZ.y; + break; + case 2: + newCubeX = cubeXYZ.y; + newCubeY = cubeXYZ.z; + newCubeZ = cubeXYZ.x; + break; + case 3: + newCubeX = -cubeXYZ.x; + newCubeY = -cubeXYZ.y; + newCubeZ = -cubeXYZ.z; + break; + case 4: + newCubeX = cubeXYZ.z; + newCubeY = cubeXYZ.x; + newCubeZ = cubeXYZ.y; + break; + case 5: + newCubeX = -cubeXYZ.y; + newCubeY = -cubeXYZ.z; + newCubeZ = -cubeXYZ.x; + break; + default: + newCubeX = cubeXYZ.x; + newCubeY = cubeXYZ.y; + newCubeZ = cubeXYZ.z; + break; + } + + cube2cr(this.mode, newCubeX, newCubeY, newCubeZ, out); + return out; +} + +var globTileXY = {}; +export default Rotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/const.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/const.js new file mode 100644 index 000000000..b44e88e4b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/const.js @@ -0,0 +1,6 @@ +export default { + ODD_R: 0, + EVEN_R: 1, + ODD_Q: 2, + EVEN_Q: 3 +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/DeltaTileXYToDirection.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/DeltaTileXYToDirection.js new file mode 100644 index 000000000..0a63d8687 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/DeltaTileXYToDirection.js @@ -0,0 +1,29 @@ +// Not included in Base Gird object. +// Delta tileXY to direction + +import { + OrthogonalMap +} from './DistanceToDeltaTileXY.js'; + +var ReverseDirMap = function (dirMap) { + var out = {}, + entry, x, y; + for (var dir in dirMap) { + entry = dirMap[dir]; // [x, y] + x = entry[0]; + y = entry[1]; + if (!out.hasOwnProperty(x)) { + out[x] = {} + } + out[x][y] = parseInt(dir); + } + return out; +} + +const OrthogonalMapOut = ReverseDirMap(OrthogonalMap); +const IsometricMapOut = OrthogonalMapOut; + +export { + OrthogonalMapOut as OrthogonalMap, + IsometricMapOut as IsometricMap +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/DirectionBetween.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/DirectionBetween.js new file mode 100644 index 000000000..a24db6cee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/DirectionBetween.js @@ -0,0 +1,74 @@ +import GetAngle from '../../math/angle/Between.js'; +import RadToDegree from '../../math/RadToDeg.js'; + +var DirectionBetween = function (tileA, tileB, round) { + if (round === undefined) { + round = true; + } + + var direction; + switch (this.mode) { + case 0: // orthogonal + case 1: // isometric + if (tileA.y === tileB.y) { + direction = (tileB.x >= tileA.x) ? 0 : 2; + } else if (tileA.x === tileB.x) { + direction = (tileB.y >= tileA.y) ? 1 : 3; + } else if (this.directions === 4) { + var angle = RadToDegree(GetAngle(tileA.x, tileA.y, tileB.x, tileB.y)); // -180~180 + if (angle < 0) { + angle += 360; + } + direction = angle / 90; + if (round) { + direction = Math.round(direction); + } + } else { // this.directions === 8 + var dx = tileB.x - tileA.x; + var dy = tileB.y - tileA.y; + if (dx === dy) { + direction = (dx > 0) ? 4 : 6; + } else if (dx === -dy) { + direction = (dx > 0) ? 7 : 5; + } else { + var angle = RadToDegree(Math.atan2(dy, dx)); + if (angle < 0) { + angle += 360; + } + var steps = angle / 45; + if (round) { + steps = Math.round(steps); + } + + if ((steps >= 0) && (steps < 1)) { + direction = steps; // (steps - 0) + 0 + } else if ((steps >= 1) && (steps < 2)) { + direction = (steps + 3); // (steps - 1) + 4 + } else if ((steps >= 2) && (steps < 3)) { + direction = (steps - 1); // (steps - 2) + 1 + } else if ((steps >= 3) && (steps < 4)) { + direction = (steps + 2); // (steps - 3) + 5 + } else if ((steps >= 4) && (steps < 5)) { + direction = (steps - 2); // (steps - 4) + 2 + } else if ((steps >= 5) && (steps < 6)) { + direction = (steps + 1); // (steps - 5) + 6 + } else if ((steps >= 6) && (steps < 7)) { + direction = (steps - 3); // (steps - 6) + 3 + } else { // if ((steps >= 7) && (steps < 8)) + direction = steps; // (steps - 7) + 7 + } + } + } + break; + case 2: // staggered + // TODO + break; + } + + if (direction === this.directions) { + direction = 0; + } + return direction; +} + +export default DirectionBetween; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/DistanceToDeltaTileXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/DistanceToDeltaTileXY.js new file mode 100644 index 000000000..a012a0478 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/DistanceToDeltaTileXY.js @@ -0,0 +1,17 @@ +// orthogonal or isometric +const OrthogonalMap = [ + [1, 0], + [0, 1], + [-1, 0], + [0, -1], + [1, 1], + [-1, 1], + [-1, -1], + [1, -1] +]; +const IsometricMap = OrthogonalMap; + +export { + OrthogonalMap, + IsometricMap +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetDistance.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetDistance.js new file mode 100644 index 000000000..48eada68d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetDistance.js @@ -0,0 +1,12 @@ +var GetDistance = function (tileA, tileB, roughMode) { + var dx = tileB.x - tileA.x; + var dy = tileB.y - tileA.y; + var dist; + if (roughMode) { + dist = Math.abs(dx) + Math.abs(dy); + } else { + dist = Math.sqrt(dx * dx + dy * dy); + } + return dist; +} +export default GetDistance; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetNeighborTileDirection.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetNeighborTileDirection.js new file mode 100644 index 000000000..b6fedd6bf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetNeighborTileDirection.js @@ -0,0 +1,29 @@ +import { + OrthogonalMap, + IsometricMap +} from './DeltaTileXYToDirection.js'; + +var GetNeighborTileDirection = function (srcTileXY, neighborTileXY) { + var deltaTileXYToDirMap + switch (this.mode) { + case 0: // orthogonal + deltaTileXYToDirMap = OrthogonalMap; + break; + case 1: // isometric + deltaTileXYToDirMap = IsometricMap; + break; + case 2: // staggered + break; + } + + var deltaTileX = neighborTileXY.x - srcTileXY.x; + var deltaTileY = neighborTileXY.y - srcTileXY.y; + if (deltaTileXYToDirMap.hasOwnProperty(deltaTileX)) { + var xEntry = deltaTileXYToDirMap[deltaTileX] + if (xEntry.hasOwnProperty(deltaTileY)) { + return xEntry[deltaTileY]; + } + } + return null; +} +export default GetNeighborTileDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetNeighborTileXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetNeighborTileXY.js new file mode 100644 index 000000000..764a485c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetNeighborTileXY.js @@ -0,0 +1,7 @@ +import GetTileXAtDirection from './GetTileXYAtDirection.js'; + +var GetNeighborTileXY = function (tileX, tileY, direction, out) { + return GetTileXAtDirection.call(this, tileX, tileY, direction, 1, out); +}; + +export default GetNeighborTileXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetOppositeDirection.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetOppositeDirection.js new file mode 100644 index 000000000..998bbbe18 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetOppositeDirection.js @@ -0,0 +1,14 @@ +var GetOppositeDirection = function (tileX, tileY, direction) { + return oppositeDirectionMap[direction]; +} +const oppositeDirectionMap = { + 0: 2, // Left + 1: 3, // Down + 2: 0, // Right + 3: 1, // Up + 4: 6, // Left-down + 5: 7, // Down-right + 6: 4, // Right-up + 7: 5 // Up-left +} +export default GetOppositeDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileX.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileX.js new file mode 100644 index 000000000..6a2b762d7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileX.js @@ -0,0 +1,5 @@ +var GetTileX = function (worldX, worldY) { + return this.getTileXY(worldX, worldY, true).x; +} + +export default GetTileX; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileXY.js new file mode 100644 index 000000000..b556684f8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileXY.js @@ -0,0 +1,27 @@ +var GetTileXY = function (worldX, worldY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + worldX -= this.x; + worldY -= this.y; + var tmpx = worldX / this.width; + var tmpy = worldY / this.height; + switch (this.mode) { + case 0: // orthogonal + out.x = Math.round(tmpx); + out.y = Math.round(tmpy); + break; + case 1: // isometric + out.x = Math.round(+tmpx + tmpy); + out.y = Math.round(-tmpx + tmpy); + break; + } + return out; +} + +var globTileXY = {}; + +export default GetTileXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileXYAtDirection.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileXYAtDirection.js new file mode 100644 index 000000000..aaf00b8a7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileXYAtDirection.js @@ -0,0 +1,33 @@ +import { + OrthogonalMap, + IsometricMap +} from './DistanceToDeltaTileXY.js'; + +var GetTileXAtDirection = function (tileX, tileY, direction, distance, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + var deltaTileX, deltaTileY; + switch (this.mode) { + case 0: // orthogonal + deltaTileX = OrthogonalMap[direction][0]; + deltaTileY = OrthogonalMap[direction][1]; + break; + case 1: // isometric + deltaTileX = IsometricMap[direction][0]; + deltaTileY = IsometricMap[direction][1]; + break; + } + + out.x = tileX + (distance * deltaTileX); + out.y = tileY + (distance * deltaTileY); + + return out; +} + +var globTileXY = {}; + +export default GetTileXAtDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileY.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileY.js new file mode 100644 index 000000000..16c82e05f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileY.js @@ -0,0 +1,5 @@ +var GetTileY = function (worldX, worldY) { + return this.getTileXY(worldX, worldY, true).y; +} + +export default GetTileY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetWorldX.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetWorldX.js new file mode 100644 index 000000000..fb10ee7c6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetWorldX.js @@ -0,0 +1,5 @@ +var GetWorldX = function (tileX, tileY) { + return this.getWorldXY(tileX, tileY, true).x; +} + +export default GetWorldX; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetWorldXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetWorldXY.js new file mode 100644 index 000000000..1e21dd6b1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetWorldXY.js @@ -0,0 +1,28 @@ +var GetWorldX = function (tileX, tileY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globWorldXY; + } + + var worldX, worldY; + switch (this.mode) { + case 0: // orthogonal + worldX = tileX * this.width; + worldY = tileY * this.height; + break; + case 1: // isometric + worldX = (tileX - tileY) * this._halfWidth; + worldY = (tileX + tileY) * this._halfHeight; + break; + } + worldX += this.x; + worldY += this.y; + out.x = worldX; + out.y = worldY; + return out; +} + +var globWorldXY = {}; + +export default GetWorldX; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetWorldY.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetWorldY.js new file mode 100644 index 000000000..2c72d8444 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetWorldY.js @@ -0,0 +1,5 @@ +var GetWorldY = function (tileX, tileY) { + return this.getWorldXY(tileX, tileY, true).y; +} + +export default GetWorldY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Mirror.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Mirror.js new file mode 100644 index 000000000..5a7560f79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Mirror.js @@ -0,0 +1,14 @@ +var Mirror = function (src, mode, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + out.x = (mode & 1) ? -src.x : src.x; + out.y = (mode & 2) ? -src.y : src.y; + return out; +} + +var globTileXY = {}; +export default Mirror; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Offset.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Offset.js new file mode 100644 index 000000000..58eedb1e4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Offset.js @@ -0,0 +1,17 @@ +var Offset = function (srcTile, offsetTileX, offsetTileY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + var newTileX = srcTile.x + offsetTileX; + var newTileY = srcTile.y + offsetTileY; + // TODO: staggered? + out.x = newTileX; + out.y = newTileY; + return out; +} + +var globTileXY = {}; +export default Offset; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Quad.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Quad.js new file mode 100644 index 000000000..edafb3fd8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Quad.js @@ -0,0 +1,109 @@ +import GetWorldXY from './GetWorldXY.js'; +import GetWorldX from './GetWorldX.js'; +import GetWorldY from './GetWorldY.js'; +import GetTileXY from './GetTileXY.js'; +import GetTileX from './GetTileX.js'; +import GetTileY from './GetTileY.js'; +import GetValue from '../../object/GetValue.js'; + +class Quad { + constructor(config) { + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.setType(GetValue(o, 'type', 0)); + this.setDirectionMode(GetValue(o, 'dir', 4)); + this.setOriginPosition(GetValue(o, 'x', 0), GetValue(o, 'y', 0)); + this.setCellSize(GetValue(o, 'cellWidth', 0), GetValue(o, 'cellHeight', 0)); + } + + setType(type) { + if (typeof (type) === 'string') { + type = ORIENTATIONTYPE[type] + } + this.mode = type; // orthogonal, isometric, or staggered + return this; + } + + setDirectionMode(mode) { + if (typeof (mode) === 'string') { + mode = DIRMODE[mode]; + } + + this.directions = mode; + return this; + } + + setOriginPosition(x, y) { + this.x = x; + this.y = y; + return this; + } + + get width() { + return this._width; + } + + set width(value) { + this._width = value; + this._halfWidth = value / 2; + } + + get height() { + return this._height; + } + + set height(value) { + this._height = value; + this._halfHeight = value / 2; + } + + setCellSize(width, height) { + this.width = width; + this.height = height; + return this; + } + + get cellWidth() { + return this.width; + } + + set cellWidth(value) { + this.width = value; + } + + get cellHeight() { + return this.height; + } + + set cellHeight(value) { + this.height = value; + } +} + +var methods = { + getWorldXY: GetWorldXY, + getWorldX: GetWorldX, + getWorldY: GetWorldY, + getTileXY: GetTileXY, + getTileX: GetTileX, + getTileY: GetTileY, +} +Object.assign( + Quad.prototype, + methods +); + +const ORIENTATIONTYPE = { + 'orthogonal': 0, + 'isometric': 1, + 'staggered': 2 +}; + +const DIRMODE = { + '4dir': 4, + '8dir': 8 +} + +export default Quad; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/RingToTileXYArray.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/RingToTileXYArray.js new file mode 100644 index 000000000..574eee9f4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/RingToTileXYArray.js @@ -0,0 +1,32 @@ +import Offset from './Offset.js'; + +var RingToTileXYArray = function (centerTileXY, radius, out) { + if (out === undefined) { + out = []; + } + + var i, j; + // Top-right to bottom-right + i = radius; + for (j = -radius; j <= radius; j++) { + out.push(Offset(centerTileXY, i, j)); + } + // Bottom-right to bottom-left + j = radius; + for (i = radius - 1; i >= -radius; i--) { + out.push(Offset(centerTileXY, i, j)); + } + // Bottom-left to top-left + i = -radius; + for (j = radius - 1; j >= -radius; j--) { + out.push(Offset(centerTileXY, i, j)); + } + // Top-left to top-right + j = -radius; + for (i = -radius + 1; i <= radius - 1; i++) { + out.push(Offset(centerTileXY, i, j)); + } + + return out; +} +export default RingToTileXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Rotate.js b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Rotate.js new file mode 100644 index 000000000..aaf64e68a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Rotate.js @@ -0,0 +1,38 @@ +import Wrap from '../../math/Wrap.js'; + +var Rotate = function (src, dir, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + dir = Wrap(dir, 0, 3); + var newTileX; + var newTileY; + switch (dir) { + case 1: + newTileX = -src.y; + newTileY = src.x; + break; + case 2: + newTileX = -src.x; + newTileY = -src.y; + break; + case 3: + newTileX = src.y; + newTileY = -src.x; + break; + default: + newTileX = src.x; + newTileY = src.y; + break; + } + // TODO: staggered? + out.x = newTileX; + out.y = newTileY; + return out; +} + +var globTileXY = {}; +export default Rotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/input/CursorKeys.js b/ui/src/phaser3-rex-plugins/plugins/utils/input/CursorKeys.js new file mode 100644 index 000000000..584e1efbf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/input/CursorKeys.js @@ -0,0 +1,97 @@ +const Key = Phaser.Input.Keyboard.Key; +const KeyCodes = Phaser.Input.Keyboard.KeyCodes; + +class CursorKeys { + constructor(scene) { + // scene: scene instance, or undefined + this.cursorKeys = { + up: new Key(scene, KeyCodes.UP), + down: new Key(scene, KeyCodes.DOWN), + left: new Key(scene, KeyCodes.LEFT), + right: new Key(scene, KeyCodes.RIGHT) + } + this.noKeyDown = true; + } + + shutdown(fromScene) { + for (var key in this.cursorKeys) { + this.cursorKeys[key].destroy(); + } + this.cursorKeys = undefined; + } + + destroy(fromScene) { + shutdown(fromScene); + } + + createCursorKeys() { + return this.cursorKeys; + } + + setKeyState(keyName, isDown) { + var key = this.cursorKeys[keyName]; + + if (!key.enabled) { + return this; + } + if (isDown) { + this.noKeyDown = false; + } + + if (key.isDown !== isDown) { + FakeEvent.timeStamp = Date.now(); + FakeEvent.keyCode = key.keyCode; + if (isDown) { + key.onDown(FakeEvent); + } else { + key.onUp(FakeEvent); + } + } + + return this; + } + + clearAllKeysState() { + this.noKeyDown = true; + for (var keyName in this.cursorKeys) { + this.setKeyState(keyName, false); + } + return this; + } + + getKeyState(keyName) { + return this.cursorKeys[keyName]; + } + + get upKeyDown() { + return this.cursorKeys.up.isDown; + } + + get downKeyDown() { + return this.cursorKeys.down.isDown; + } + + get leftKeyDown() { + return this.cursorKeys.left.isDown; + } + + get rightKeyDown() { + return this.cursorKeys.right.isDown; + } + + get anyKeyDown() { + return !this.noKeyDown; + } +} + +var FakeEvent = { + timeStamp: 0, + keyCode: 0, + altKey: false, + ctrlKey: false, + shiftKey: false, + metaKey: false, + location: 0, +}; + +export default CursorKeys; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/input/HitTest.js b/ui/src/phaser3-rex-plugins/plugins/utils/input/HitTest.js new file mode 100644 index 000000000..c6e953b76 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/input/HitTest.js @@ -0,0 +1,43 @@ +var HitTest = function (scene, topOnly, gameObjects, pointers, out) { + var inputPlugin = scene.input; + var inputManager = inputPlugin.manager; + + if (topOnly === undefined) { + topOnly = inputPlugin.topOnly; + } + if (out === undefined) { + out = []; + } + if (gameObjects === undefined) { + gameObjects = inputPlugin._list; + } + + var pointersTotal, pointer; + if (pointers === undefined) { + pointers = inputManager.pointers; + pointersTotal = inputManager.pointersTotal; + } + if (pointersTotal === undefined) { + pointersTotal = pointers.length; + } + + var cameras; + for (var pointerIdx = 0; pointerIdx < pointersTotal; pointerIdx++) { + pointer = pointers[pointerIdx]; + cameras = inputPlugin.cameras.getCamerasBelowPointer(pointer); + for (var cameraIdx = 0, camerasTotal = cameras.length; cameraIdx < camerasTotal; cameraIdx++) { + out.push(...inputManager.hitTest(pointer, gameObjects, cameras[cameraIdx])); + } + } + + inputPlugin.sortGameObjects(out); + if (topOnly){ + if (out.length) { + out.splice(1); + } + } + + return out; +} + +export default HitTest; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/input/InputCandidate.js b/ui/src/phaser3-rex-plugins/plugins/utils/input/InputCandidate.js new file mode 100644 index 000000000..0f7638ffb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/input/InputCandidate.js @@ -0,0 +1,26 @@ +const RENDER_MASK = Phaser.GameObjects.GameObject.RENDER_MASK; + +var InputCandidate = function (gameObject) { + if (gameObject.renderFlags !== RENDER_MASK) { + return false; + } + + var visible = true; + var parent = gameObject.parentContainer; + + if (parent) { + do { + if (parent.renderFlags !== RENDER_MASK) { + visible = false; + break; + } + + parent = parent.parentContainer; + + } while (parent); + } + + return visible; +} + +export default InputCandidate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/input/IsPointerInBounds.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/input/IsPointerInBounds.d.ts new file mode 100644 index 000000000..322bac956 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/input/IsPointerInBounds.d.ts @@ -0,0 +1,8 @@ +// import * as Phaser from 'phaser'; + +export default function IsPointerInBounds( + gameObject: Phaser.GameObjects.GameObject, + pointer?: Phaser.Input.Pointer, + preTest?: (gameObject: Phaser.GameObjects.GameObject, x: number, y: number) => boolean, + postTest?: (gameObject: Phaser.GameObjects.GameObject, x: number, y: number) => boolean, +): boolean; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/input/IsPointerInBounds.js b/ui/src/phaser3-rex-plugins/plugins/utils/input/IsPointerInBounds.js new file mode 100644 index 000000000..8ef995705 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/input/IsPointerInBounds.js @@ -0,0 +1,23 @@ +import IsPointInBounds from '../bounds/IsPointInBounds.js'; + +var IsPointerInBounds = function (gameObject, pointer, preTest, postTest) { + if (pointer) { + return IsPointInBounds(gameObject, pointer.worldX, pointer.worldY, preTest, postTest); + + } else { + var inputManager = gameObject.scene.input.manager; + var pointersTotal = inputManager.pointersTotal; + var pointers = inputManager.pointers; + for (var i = 0; i < pointersTotal; i++) { + pointer = pointers[i]; + if (IsPointInBounds(gameObject, pointer.worldX, pointer.worldY, preTest, postTest)) { + return true; + } + } + return false; + + } + +} + +export default IsPointerInBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/input/IsPointerInHitArea.js b/ui/src/phaser3-rex-plugins/plugins/utils/input/IsPointerInHitArea.js new file mode 100644 index 000000000..7b958d048 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/input/IsPointerInHitArea.js @@ -0,0 +1,54 @@ +var IsPointerInHitArea = function (gameObject, pointer, preTest, postTest) { + if (pointer) { + if (preTest && !preTest(gameObject, pointer)) { + return false; + } + if (!HitTest(gameObject, pointer)) { + return false; + } + if (postTest && !postTest(gameObject, pointer)) { + return false; + } + return true; + + } else { + var inputManager = gameObject.scene.input.manager; + var pointersTotal = inputManager.pointersTotal; + var pointers = inputManager.pointers, + pointer; + for (var i = 0; i < pointersTotal; i++) { + pointer = pointers[i]; + if (preTest && !preTest(gameObject, pointer)) { + continue; + } + if (!HitTest(gameObject, pointer)) { + continue; + } + if (postTest && !postTest(gameObject, pointer)){ + continue; + } + return true; + } + + return false; + } +} + +var HitTest = function (gameObject, pointer) { + var scene = gameObject.scene; + var cameras = scene.input.cameras.getCamerasBelowPointer(pointer); + var inputManager = scene.input.manager; + var gameObjects = [gameObject]; + var output; + + for (var i = 0, len = cameras.length; i < len; i++) { + output = inputManager.hitTest(pointer, gameObjects, cameras[i]); + if (output.length > 0) { + return true; + } + } + + return false; +} + +export default IsPointerInHitArea; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/input/KeyMap.js b/ui/src/phaser3-rex-plugins/plugins/utils/input/KeyMap.js new file mode 100644 index 000000000..de2f0e96d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/input/KeyMap.js @@ -0,0 +1,8 @@ +const KeyCodes = Phaser.Input.Keyboard.KeyCodes; + +var KeyMap = {}; +for (var key in KeyCodes) { + KeyMap[KeyCodes[key]] = key; +} + +export default KeyMap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/input/RequestDrag.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/input/RequestDrag.d.ts new file mode 100644 index 000000000..5719b73dc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/input/RequestDrag.d.ts @@ -0,0 +1,3 @@ +export default function RequestDrag( + gameObject: Phaser.GameObjects.GameObject, +): boolean; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/input/RequestDrag.js b/ui/src/phaser3-rex-plugins/plugins/utils/input/RequestDrag.js new file mode 100644 index 000000000..40c036d33 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/input/RequestDrag.js @@ -0,0 +1,38 @@ +import IsPointerInHitArea from './IsPointerInHitArea.js'; + +var RequestDrag = function (gameObject) { + var inputPlugin = gameObject.scene.input; + var inputManager = inputPlugin.manager; + var pointersTotal = inputManager.pointersTotal; + var pointers = inputManager.pointers, + pointer; + for (var i = 0; i < pointersTotal; i++) { + pointer = pointers[i]; + if ( + (!pointer.primaryDown) || + (inputPlugin.getDragState(pointer) !== 0) || + (!IsPointerInHitArea(gameObject, pointer)) + ) { + continue; + } + + // For 3.18.0 + inputPlugin.setDragState(pointer, 1); + inputPlugin._drag[pointer.id] = [gameObject]; + if ((inputPlugin.dragDistanceThreshold === 0) || (inputPlugin.dragTimeThreshold === 0)) { + // No drag criteria, so snap immediately to mode 3 + inputPlugin.setDragState(pointer, 3); + inputPlugin.processDragStartList(pointer); + } else { + // Check the distance / time on the next event + inputPlugin.setDragState(pointer, 2); + } + // For 3.18.0 + + return true; + } + + return false; +} + +export default RequestDrag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/input/SetCursorStyle.js b/ui/src/phaser3-rex-plugins/plugins/utils/input/SetCursorStyle.js new file mode 100644 index 000000000..9ec728193 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/input/SetCursorStyle.js @@ -0,0 +1,8 @@ +var SetCursorStyle = function (scene, cursor) { + if (cursor === undefined) { + cursor = ''; + } + scene.input.manager.canvas.style.cursor = cursor; +} + +export default SetCursorStyle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/input/VectorToCursorKeys.js b/ui/src/phaser3-rex-plugins/plugins/utils/input/VectorToCursorKeys.js new file mode 100644 index 000000000..4650d1284 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/input/VectorToCursorKeys.js @@ -0,0 +1,183 @@ +import CursorKeys from './CursorKeys.js'; +import RadToDeg from '../math/RadToDeg.js'; +import DIRMODE from '../math/angle/angletodirections/Const.js'; +import AngleToDirections from '../math/angle/angletodirections/AngleToDirections.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const GetDist = Phaser.Math.Distance.Between; +const GetAngle = Phaser.Math.Angle.Between; + +class VectorToCursorKeys extends CursorKeys { + constructor(scene, config) { + super(scene); + this.resetFromJSON(config); + } + + resetFromJSON(o) { + if (this.start == undefined) { + this.start = { x: 0, y: 0 }; + } + if (this.end == undefined) { + this.end = { x: 0, y: 0 }; + } + this._enable = undefined; + this.setEnable(GetValue(o, 'enable', true)); + this.setMode(GetValue(o, 'dir', '8dir')); + this.setDistanceThreshold(GetValue(o, 'forceMin', 16)); + + var startX = GetValue(o, "start.x", null); + var startY = GetValue(o, "start.y", null); + var endX = GetValue(o, "end.x", null); + var endY = GetValue(o, "end.y", null); + this.setVector(startX, startY, endX, endY); + return this; + } + + toJSON() { + return { + enable: this.enable, + dir: this.dirMode, + forceMin: this.forceMin, + + start: { + x: this.start.x, + y: this.start.y + }, + end: { + x: this.end.x, + y: this.end.y + } + }; + } + + setMode(m) { + if (typeof (m) === 'string') { + m = DIRMODE[m]; + } + this.dirMode = m; + return this; + } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + if (!e) { + this.clearVector(); + } + this._enable = e; + return this; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + setDistanceThreshold(d) { + if (d < 0) { + d = 0; + } + this.forceMin = d; + return this; + } + + clearVector() { + this.start.x = 0; + this.start.y = 0; + this.end.x = 0; + this.end.y = 0; + this.clearAllKeysState(); + return this; + } + + setVector(x0, y0, x1, y1) { + if (!this.enable) { + // Do nothing + return this; + } + + if (x0 === null) { + // Clear all keys' state + this.clearVector(); + return this; + } + + // (0,0) -> (x0, y0) + if (x1 === undefined) { + x1 = x0; + x0 = 0; + y1 = y0; + y0 = 0; + } + + this.start.x = x0; + this.start.y = y0; + this.end.x = x1; + this.end.y = y1; + + if ((this.forceMin > 0) && (this.force < this.forceMin)) { + // No key pressed + this.clearVector(); + return this; + } + + // Update keys' state + this.noKeyDown = true; + var dirStates = AngleToDirections(this.angle, this.dirMode, true); + for (var dir in dirStates) { + this.setKeyState(dir, dirStates[dir]); + } + + return this; + } + + get forceX() { + return this.end.x - this.start.x; + } + + get forceY() { + return this.end.y - this.start.y; + } + + get force() { + return GetDist(this.start.x, this.start.y, this.end.x, this.end.y); + } + + get rotation() { + return GetAngle(this.start.x, this.start.y, this.end.x, this.end.y); + } + + get angle() { + return RadToDeg(this.rotation); // -180 ~ 180 + } + + get octant() { + var octant = 0; + if (this.rightKeyDown) { + octant = (this.downKeyDown) ? 45 : 0; + } else if (this.downKeyDown) { + octant = (this.leftKeyDown) ? 135 : 90; + } else if (this.leftKeyDown) { + octant = (this.upKeyDown) ? 225 : 180; + } else if (this.upKeyDown) { + octant = (this.rightKeyDown) ? 315 : 270; + } + return octant; + } +} + +export default VectorToCursorKeys; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/js-interpreter/js-interpreter.js b/ui/src/phaser3-rex-plugins/plugins/utils/js-interpreter/js-interpreter.js new file mode 100644 index 000000000..944878a0a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/js-interpreter/js-interpreter.js @@ -0,0 +1,8 @@ +/** + * Minified by jsDelivr using UglifyJS v3.3.20. + * Original file: /npm/js-interpreter@1.4.6/lib/index.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports["js-interpreter"]=e():t["js-interpreter"]=e()}(this,function(){return function(i){var r={};function s(t){if(r[t])return r[t].exports;var e=r[t]={i:t,l:!1,exports:{}};return i[t].call(e.exports,e,e.exports,s),e.l=!0,e.exports}return s.m=i,s.c=r,s.i=function(t){return t},s.d=function(t,e,i){s.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:i})},s.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return s.d(e,"a",e),e},s.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},s.p="",s(s.s=8)}([function(t,e,i){"use strict";var u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},l=i(1),r=i(4),f=function t(e,i){"string"==typeof e&&(e=l.parse(e,t.PARSE_OPTIONS)),this.ast=e,this.initFunc_=i,this.paused_=!1,this.polyfills_=[],this.UNDEFINED=new t.Primitive(void 0,this),this.NULL=new t.Primitive(null,this),this.NAN=new t.Primitive(NaN,this),this.TRUE=new t.Primitive(!0,this),this.FALSE=new t.Primitive(!1,this),this.NUMBER_ZERO=new t.Primitive(0,this),this.NUMBER_ONE=new t.Primitive(1,this),this.STRING_EMPTY=new t.Primitive("",this),this.global=this.createScope(this.ast,null),this.NAN.parent=this.NUMBER,this.TRUE.parent=this.BOOLEAN,this.FALSE.parent=this.BOOLEAN,this.NUMBER_ZERO.parent=this.NUMBER,this.NUMBER_ONE.parent=this.NUMBER,this.STRING_EMPTY.parent=this.STRING,this.ast=l.parse(this.polyfills_.join("\n"),t.PARSE_OPTIONS),this.polyfills_=void 0,this.stripLocations_(this.ast,void 0,void 0),this.stateStack=[{node:this.ast,scope:this.global,thisExpression:this.global,done:!1}],this.run(),this.value=this.UNDEFINED,this.ast=e,this.stateStack=[{node:this.ast,scope:this.global,thisExpression:this.global,done:!1}]};f.PARSE_OPTIONS={ecmaVersion:5},f.READONLY_DESCRIPTOR={configurable:!0,enumerable:!0,writable:!1},f.NONENUMERABLE_DESCRIPTOR={configurable:!0,enumerable:!1,writable:!0},f.READONLY_NONENUMERABLE_DESCRIPTOR={configurable:!0,enumerable:!1,writable:!1},f.prototype.appendCode=function(t){var e=this.stateStack[0];if(!e||"Program"!=e.node.type)throw Error("Expecting original AST to start with a Program node.");if("string"==typeof t&&(t=l.parse(t,f.PARSE_OPTIONS)),!t||"Program"!=t.type)throw Error("Expecting new AST to start with a Program node.");this.populateScope_(t,e.scope);for(var i,r=0;i=t.body[r];r++)e.node.body.push(i);e.done=!1},f.prototype.step=function(){var t=this.stateStack[this.stateStack.length-1];return!(!t||"Program"==t.node.type&&t.done)&&(!!this.paused_||(this["step"+t.node.type](),!!t.node.end||this.step()))},f.prototype.run=function(){for(;!this.paused_&&this.step(););return this.paused_},f.prototype.initGlobalScope=function(t){this.setProperty(t,"Infinity",this.createPrimitive(1/0),f.READONLY_DESCRIPTOR),this.setProperty(t,"NaN",this.NAN,f.READONLY_DESCRIPTOR),this.setProperty(t,"undefined",this.UNDEFINED,f.READONLY_DESCRIPTOR),this.setProperty(t,"window",t,f.READONLY_DESCRIPTOR),this.setProperty(t,"self",t),this.initFunction(t),this.initObject(t),t.parent=this.OBJECT,this.initArray(t),this.initNumber(t),this.initString(t),this.initBoolean(t),this.initDate(t),this.initMath(t),this.initRegExp(t),this.initJSON(t),this.initError(t);var e,i=this;e=function(t){return t=t||i.UNDEFINED,i.createPrimitive(isNaN(t.toNumber()))},this.setProperty(t,"isNaN",this.createNativeFunction(e)),e=function(t){return t=t||i.UNDEFINED,i.createPrimitive(isFinite(t.toNumber()))},this.setProperty(t,"isFinite",this.createNativeFunction(e)),this.setProperty(t,"parseFloat",this.getProperty(this.NUMBER,"parseFloat")),this.setProperty(t,"parseInt",this.getProperty(this.NUMBER,"parseInt"));var r=this.createObject(this.FUNCTION);r.eval=!0,this.setProperty(r,"length",this.NUMBER_ONE,f.READONLY_DESCRIPTOR),this.setProperty(t,"eval",r);for(var s=[[escape,"escape"],[unescape,"unescape"],[decodeURI,"decodeURI"],[decodeURIComponent,"decodeURIComponent"],[encodeURI,"encodeURI"],[encodeURIComponent,"encodeURIComponent"]],n=0;n>> 0;","if (arguments.length > 1) T = thisArg;","k = 0;","while (k < len) {","if (k in O && !callbackfn.call(T, O[k], k, O)) return false;","k++;","}","return true;","}","});","Object.defineProperty(Array.prototype, 'filter', {configurable: true, value:","function(fun/*, thisArg*/) {","if (this === void 0 || this === null || typeof fun !== 'function') throw new TypeError;","var t = Object(this);","var len = t.length >>> 0;","var res = [];","var thisArg = arguments.length >= 2 ? arguments[1] : void 0;","for (var i = 0; i < len; i++) {","if (i in t) {","var val = t[i];","if (fun.call(thisArg, val, i, t)) res.push(val);","}","}","return res;","}","});","Object.defineProperty(Array.prototype, 'forEach', {configurable: true, value:","function(callback, thisArg) {","if (this == null || typeof callback !== 'function') throw new TypeError;","var T, k;","var O = Object(this);","var len = O.length >>> 0;","if (arguments.length > 1) T = thisArg;","k = 0;","while (k < len) {","if (k in O) callback.call(T, O[k], k, O);","k++;","}","}","});","Object.defineProperty(Array.prototype, 'map', {configurable: true, value:","function(callback, thisArg) {","if (this == null || typeof callback !== 'function') new TypeError;","var T, A, k;","var O = Object(this);","var len = O.length >>> 0;","if (arguments.length > 1) T = thisArg;","A = new Array(len);","k = 0;","while (k < len) {","if (k in O) A[k] = callback.call(T, O[k], k, O);","k++;","}","return A;","}","});","Object.defineProperty(Array.prototype, 'reduce', {configurable: true, value:","function(callback /*, initialValue*/) {","if (this == null || typeof callback !== 'function') throw new TypeError;","var t = Object(this), len = t.length >>> 0, k = 0, value;","if (arguments.length == 2) {","value = arguments[1];","} else {","while (k < len && !(k in t)) k++;","if (k >= len) {","throw new TypeError('Reduce of empty array with no initial value');","}","value = t[k++];","}","for (; k < len; k++) {","if (k in t) value = callback(value, t[k], k, t);","}","return value;","}","});","Object.defineProperty(Array.prototype, 'reduceRight', {configurable: true, value:","function(callback /*, initialValue*/) {","if (null === this || 'undefined' === typeof this || 'function' !== typeof callback) throw new TypeError;","var t = Object(this), len = t.length >>> 0, k = len - 1, value;","if (arguments.length >= 2) {","value = arguments[1];","} else {","while (k >= 0 && !(k in t)) k--;","if (k < 0) {","throw new TypeError('Reduce of empty array with no initial value');","}","value = t[k--];","}","for (; k >= 0; k--) {","if (k in t) value = callback(value, t[k], k, t);","}","return value;","}","});","Object.defineProperty(Array.prototype, 'some', {configurable: true, value:","function(fun/*, thisArg*/) {","if (this == null || typeof fun !== 'function') throw new TypeError;","var t = Object(this);","var len = t.length >>> 0;","var thisArg = arguments.length >= 2 ? arguments[1] : void 0;","for (var i = 0; i < len; i++) {","if (i in t && fun.call(thisArg, t[i], i, t)) {","return true;","}","}","return false;","}","});","Object.defineProperty(Array.prototype, 'sort', {configurable: true, value:","function(opt_comp) {","for (var i = 0; i < this.length; i++) {","var changes = 0;","for (var j = 0; j < this.length - i - 1; j++) {","if (opt_comp ?opt_comp(this[j], this[j + 1]) > 0 : this[j] > this[j + 1]) {","var swap = this[j];","this[j] = this[j + 1];","this[j + 1] = swap;","changes++;","}","}","if (changes <= 1) break;","}","return this;","}","});","Object.defineProperty(Array.prototype, 'toLocaleString', {configurable: true, value:","function() {","var out = [];","for (var i = 0; i < this.length; i++) {","out[i] = (this[i] === null || this[i] === undefined) ? '' : this[i].toLocaleString();","}","return out.join(',');","}","});","")},f.prototype.initNumber=function(t){var e,i=this;e=function(t){return t=t?t.toNumber():0,this.parent!=i.NUMBER?i.createPrimitive(t):(this.data=t,this)},this.NUMBER=this.createNativeFunction(e),this.setProperty(t,"Number",this.NUMBER);for(var r=["MAX_VALUE","MIN_VALUE","NaN","NEGATIVE_INFINITY","POSITIVE_INFINITY"],s=0;s=Math.pow(2,32)?NaN:t},(f.Primitive=function(t,e){var i=void 0===t?"undefined":u(t);this.data=t,"number"==(this.type=i)?this.parent=e.NUMBER:"string"==i?this.parent=e.STRING:"boolean"==i&&(this.parent=e.BOOLEAN)}).prototype.data=void 0,f.Primitive.prototype.type="undefined",f.Primitive.prototype.parent=null,f.Primitive.prototype.isPrimitive=!0,f.Primitive.prototype.toBoolean=function(){return Boolean(this.data)},f.Primitive.prototype.toNumber=function(){return Number(this.data)},f.Primitive.prototype.toString=function(){return String(this.data)},f.Primitive.prototype.valueOf=function(){return this.data},f.prototype.createPrimitive=function(t){return void 0===t?this.UNDEFINED:null===t?this.NULL:!0===t?this.TRUE:!1===t?this.FALSE:0===t?this.NUMBER_ZERO:1===t?this.NUMBER_ONE:""===t?this.STRING_EMPTY:t instanceof RegExp?this.populateRegExp_(this.createObject(this.REGEXP),t):new f.Primitive(t,this)},(f.Object=function(t){this.notConfigurable=Object.create(null),this.notEnumerable=Object.create(null),this.notWritable=Object.create(null),this.getter=Object.create(null),this.setter=Object.create(null),this.properties=Object.create(null),this.parent=t}).prototype.type="object",f.Object.prototype.parent=null,f.Object.prototype.isPrimitive=!1,f.Object.prototype.data=void 0,f.Object.prototype.toBoolean=function(){return!0},f.Object.prototype.toNumber=function(){return Number(void 0===this.data?this.toString():this.data)},f.Object.prototype.toString=function(){return void 0===this.data?"["+this.type+"]":String(this.data)},f.Object.prototype.valueOf=function(){return void 0===this.data?this:this.data},f.prototype.createObject=function(t){var e=new f.Object(t);if(this.isa(e,this.FUNCTION)&&(e.type="function",this.setProperty(e,"prototype",this.createObject(this.OBJECT||null))),this.isa(e,this.ARRAY)&&(e.length=0,e.toString=function(){for(var t=[],e=0;e>="==e.operator)r=h>>p;else if(">>>="==e.operator)r=h>>>p;else if("&="==e.operator)r=h&p;else if("^="==e.operator)r=h^p;else{if("|="!=e.operator)throw SyntaxError("Unknown assignment expression: "+e.operator);r=h|p}r=this.createPrimitive(r)}var c=this.setValue(t.leftSide_,r);if(c)return t.doneSetter_=r,void this.pushSetter_(c,t.leftSide_,r);this.stateStack.pop(),this.stateStack[this.stateStack.length-1].value=r},f.prototype.stepBinaryExpression=function(){var t=this.stateStack[this.stateStack.length-1],e=t.node;if(!t.doneLeft_)return t.doneLeft_=!0,void this.stateStack.push({node:e.left});if(!t.doneRight_)return t.doneRight_=!0,t.leftValue_=t.value,void this.stateStack.push({node:e.right});this.stateStack.pop();var i,r=t.leftValue_,s=t.value,n=this.comp(r,s);if("=="==e.operator||"!="==e.operator)i=r.isPrimitive&&s.isPrimitive?r.data==s.data:0===n,"!="==e.operator&&(i=!i);else if("==="==e.operator||"!=="==e.operator)i=r.isPrimitive&&s.isPrimitive?r.data===s.data:r===s,"!=="==e.operator&&(i=!i);else if(">"==e.operator)i=1==n;else if(">="==e.operator)i=1==n||0===n;else if("<"==e.operator)i=-1==n;else if("<="==e.operator)i=-1==n||0===n;else if("+"==e.operator){i=(o=r.isPrimitive?r.data:r.toString())+(a=s.isPrimitive?s.data:s.toString())}else if("in"==e.operator)i=this.hasProperty(s,r);else if("instanceof"==e.operator)this.isa(s,this.FUNCTION)||this.throwException(this.TYPE_ERROR,"Expecting a function in instanceof check"),i=this.isa(r,s);else{var o=r.toNumber(),a=s.toNumber();if("-"==e.operator)i=o-a;else if("*"==e.operator)i=o*a;else if("/"==e.operator)i=o/a;else if("%"==e.operator)i=o%a;else if("&"==e.operator)i=o&a;else if("|"==e.operator)i=o|a;else if("^"==e.operator)i=o^a;else if("<<"==e.operator)i=o<>"==e.operator)i=o>>a;else{if(">>>"!=e.operator)throw SyntaxError("Unknown binary operator: "+e.operator);i=o>>>a}}this.stateStack[this.stateStack.length-1].value=this.createPrimitive(i)},f.prototype.stepBlockStatement=function(){var t=this.stateStack[this.stateStack.length-1],e=t.node,i=t.n_||0;e.body[i]?(t.n_=i+1,this.stateStack.push({node:e.body[i]})):this.stateStack.pop()},f.prototype.stepBreakStatement=function(){var t=this.stateStack.pop(),e=t.node,i=null;for(e.label&&(i=e.label.name),t=this.stateStack.pop();t&&"CallExpression"!=t.node.type&&"NewExpression"!=t.node.type;){if(i?i==t.label:t.isLoop||t.isSwitch)return;t=this.stateStack.pop()}throw SyntaxError("Illegal break statement")},f.prototype.stepCallExpression=function(){var e=this.stateStack[this.stateStack.length-1],t=e.node;if(!e.doneCallee_)return e.doneCallee_=!0,void this.stateStack.push({node:t.callee,components:!0});if(!e.func_){if("function"==e.value.type)e.func_=e.value;else{if(e.func_=this.getValue(e.value),e.func_.isGetter)return e.func_.isGetter=!1,this.pushGetter_(e.func_,e.value),void(e.func_=null);if(!e.func_)return;if("function"!=e.func_.type)return void this.throwException(this.TYPE_ERROR,(e.func_&&e.func_.type)+" is not a function")}"NewExpression"==e.node.type?(e.funcThis_=this.createObject(e.func_),e.isConstructor_=!0):e.value.length?e.funcThis_=e.value[0]:e.funcThis_=this.getScope().strict?this.UNDEFINED:this.global,e.arguments_=[],e.n_=0}if(!e.doneArgs_){if(0!=e.n_&&e.arguments_.push(e.value),t.arguments[e.n_])return this.stateStack.push({node:t.arguments[e.n_]}),void e.n_++;e.doneArgs_=!0}if(e.doneExec_)this.stateStack.pop(),e.isConstructor_&&"object"!==e.value.type?this.stateStack[this.stateStack.length-1].value=e.funcThis_:this.stateStack[this.stateStack.length-1].value=e.value;else if(e.doneExec_=!0,e.func_.node){for(var i=this.createScope(e.func_.node.body,e.func_.parentScope),r=0;rr?e.arguments_[r]:this.UNDEFINED;this.setProperty(i,s,n)}var o=this.createObject(this.ARRAY);for(r=0;r",y),template:new f("template"),ellipsis:new f("...",y),backQuote:new f("`",v),dollarBraceL:new f("${",{beforeExpr:!0,startsExpr:!0}),eq:new f("=",{beforeExpr:!0,isAssign:!0}),assign:new f("_=",{beforeExpr:!0,isAssign:!0}),incDec:new f("++/--",{prefix:!0,postfix:!0,startsExpr:!0}),prefix:new f("prefix",{beforeExpr:!0,prefix:!0,startsExpr:!0}),logicalOR:d("||",1),logicalAND:d("&&",2),bitwiseOR:d("|",3),bitwiseXOR:d("^",4),bitwiseAND:d("&",5),equality:d("==/!=",6),relational:d("",7),bitShift:d("<>",8),plusMin:new f("+/-",{beforeExpr:!0,binop:9,prefix:!0,startsExpr:!0}),modulo:d("%",10),star:d("*",10),slash:d("/",10),starstar:new f("**",{beforeExpr:!0}),_break:m("break"),_case:m("case",y),_catch:m("catch"),_continue:m("continue"),_debugger:m("debugger"),_default:m("default",y),_do:m("do",{isLoop:!0,beforeExpr:!0}),_else:m("else",y),_finally:m("finally"),_for:m("for",{isLoop:!0}),_function:m("function",v),_if:m("if"),_return:m("return",y),_switch:m("switch"),_throw:m("throw",y),_try:m("try"),_var:m("var"),_const:m("const"),_while:m("while",{isLoop:!0}),_with:m("with"),_new:m("new",{beforeExpr:!0,startsExpr:!0}),_this:m("this",v),_super:m("super",v),_class:m("class"),_extends:m("extends",y),_export:m("export"),_import:m("import"),_null:m("null",v),_true:m("true",v),_false:m("false",v),_in:m("in",{beforeExpr:!0,binop:7}),_instanceof:m("instanceof",{beforeExpr:!0,binop:7}),_typeof:m("typeof",{beforeExpr:!0,prefix:!0,startsExpr:!0}),_void:m("void",{beforeExpr:!0,prefix:!0,startsExpr:!0}),_delete:m("delete",{beforeExpr:!0,prefix:!0,startsExpr:!0})},b=/\r\n?|\n|\u2028|\u2029/,S=new RegExp(b.source,"g");function N(t){return 10===t||13===t||8232===t||8233===t}var P=/[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/,x=/(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g;function R(t){return"[object Array]"===Object.prototype.toString.call(t)}function _(t,e){return Object.prototype.hasOwnProperty.call(t,e)}var k=function(t,e){this.line=t,this.column=e};k.prototype.offset=function(t){return new k(this.line,this.column+t)};var w=function(t,e,i){this.start=e,this.end=i,null!==t.sourceFile&&(this.source=t.sourceFile)};function A(t,e){for(var i=1,r=0;;){S.lastIndex=r;var s=S.exec(t);if(!(s&&s.index=this.input.length?this.finishToken(E.eof):t.override?t.override(this):void this.readToken(this.fullCharCodeAtPos())},K.readToken=function(t){return u(t,6<=this.options.ecmaVersion)||92===t?this.readWord():this.getTokenFromCode(t)},K.fullCharCodeAtPos=function(){var t=this.input.charCodeAt(this.pos);return t<=55295||57344<=t?t:(t<<10)+this.input.charCodeAt(this.pos+1)-56613888},K.skipBlockComment=function(){var t,e=this.options.onComment&&this.curPosition(),i=this.pos,r=this.input.indexOf("*/",this.pos+=2);if(-1===r&&this.raise(this.pos-2,"Unterminated comment"),this.pos=r+2,this.options.locations)for(S.lastIndex=i;(t=S.exec(this.input))&&t.index>10),56320+(1023&t)))}K.readRegexp=function(){for(var t,e,r=this,s=this.pos;;){r.pos>=r.input.length&&r.raise(s,"Unterminated regular expression");var i=r.input.charAt(r.pos);if(b.test(i)&&r.raise(s,"Unterminated regular expression"),t)t=!1;else{if("["===i)e=!0;else if("]"===i&&e)e=!1;else if("/"===i&&!e)break;t="\\"===i}++r.pos}var n=this.input.slice(s,this.pos);++this.pos;var o=this.readWord1(),a=n,h="";if(o){var p=/^[gim]*$/;6<=this.options.ecmaVersion&&(p=/^[gimuy]*$/),p.test(o)||this.raise(s,"Invalid regular expression flag"),0<=o.indexOf("u")&&(it?h="u":(a=(a=a.replace(/\\u\{([0-9a-fA-F]+)\}/g,function(t,e,i){return 1114111<(e=Number("0x"+e))&&r.raise(s+i+3,"Code point out of bounds"),"x"})).replace(/\\u([a-fA-F0-9]{4})|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,"x"),h=h.replace("u","")))}var c=null;return tt||(et(a,h,s,this),c=et(n,o)),this.finishToken(E.regexp,{pattern:n,flags:o,value:c})},K.readInt=function(t,e){for(var i=this.pos,r=0,s=0,n=null==e?1/0:e;s=e.input.length&&e.raise(e.start,"Unterminated string constant");var s=e.input.charCodeAt(e.pos);if(s===t)break;92===s?(i+=e.input.slice(r,e.pos),i+=e.readEscapedChar(!1),r=e.pos):(N(s)&&e.raise(e.start,"Unterminated string constant"),++e.pos)}return i+=this.input.slice(r,this.pos++),this.finishToken(E.string,i)},K.readTmplToken=function(){for(var t=this,e="",i=this.pos;;){t.pos>=t.input.length&&t.raise(t.start,"Unterminated template");var r=t.input.charCodeAt(t.pos);if(96===r||36===r&&123===t.input.charCodeAt(t.pos+1))return t.pos===t.start&&t.type===E.template?36===r?(t.pos+=2,t.finishToken(E.dollarBraceL)):(++t.pos,t.finishToken(E.backQuote)):(e+=t.input.slice(i,t.pos),t.finishToken(E.template,e));if(92===r)e+=t.input.slice(i,t.pos),e+=t.readEscapedChar(!0),i=t.pos;else if(N(r)){switch(e+=t.input.slice(i,t.pos),++t.pos,r){case 13:10===t.input.charCodeAt(t.pos)&&++t.pos;case 10:e+="\n";break;default:e+=String.fromCharCode(r)}t.options.locations&&(++t.curLine,t.lineStart=t.pos),i=t.pos}else++t.pos}},K.readEscapedChar=function(t){var e=this.input.charCodeAt(++this.pos);switch(++this.pos,e){case 110:return"\n";case 114:return"\r";case 120:return String.fromCharCode(this.readHexChar(2));case 117:return rt(this.readCodePoint());case 116:return"\t";case 98:return"\b";case 118:return"\v";case 102:return"\f";case 13:10===this.input.charCodeAt(this.pos)&&++this.pos;case 10:return this.options.locations&&(this.lineStart=this.pos,++this.curLine),"";default:if(48<=e&&e<=55){var i=this.input.substr(this.pos-1,3).match(/^[0-7]+/)[0],r=parseInt(i,8);return 255>16&255,o[h++]=s>>8&255,o[h++]=255&s;2===n?(s=p[t.charCodeAt(e)]<<2|p[t.charCodeAt(e+1)]>>4,o[h++]=255&s):1===n&&(s=p[t.charCodeAt(e)]<<10|p[t.charCodeAt(e+1)]<<4|p[t.charCodeAt(e+2)]>>2,o[h++]=s>>8&255,o[h++]=255&s);return o},e.fromByteArray=function(t){for(var e,i=t.length,r=i%3,s="",n=[],o=0,a=i-r;o>2],s+=h[e<<4&63],s+="=="):2===r&&(e=(t[i-2]<<8)+t[i-1],s+=h[e>>10],s+=h[e>>4&63],s+=h[e<<2&63],s+="=");return n.push(s),n.join("")};for(var h=[],p=[],c="undefined"!=typeof Uint8Array?Uint8Array:Array,r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s=0,n=r.length;s>18&63]+h[s>>12&63]+h[s>>6&63]+h[63&s]);return n.join("")}p["-".charCodeAt(0)]=62,p["_".charCodeAt(0)]=63},function(t,B,e){"use strict";(function(t){var r=e(2),n=e(5),o=e(6);function i(){return u.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function a(t,e){if(i()=i())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+i().toString(16)+" bytes");return 0|t}function f(t,e){if(u.isBuffer(t))return t.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(t)||t instanceof ArrayBuffer))return t.byteLength;"string"!=typeof t&&(t=""+t);var i=t.length;if(0===i)return 0;for(var r=!1;;)switch(e){case"ascii":case"latin1":case"binary":return i;case"utf8":case"utf-8":case void 0:return F(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*i;case"hex":return i>>>1;case"base64":return D(t).length;default:if(r)return F(t).length;e=(""+e).toLowerCase(),r=!0}}function d(t,e,i){var r=t[e];t[e]=t[i],t[i]=r}function y(t,e,i,r,s){if(0===t.length)return-1;if("string"==typeof i?(r=i,i=0):2147483647=t.length){if(s)return-1;i=t.length-1}else if(i<0){if(!s)return-1;i=0}if("string"==typeof e&&(e=u.from(e,r)),u.isBuffer(e))return 0===e.length?-1:v(t,e,i,r,s);if("number"==typeof e)return e&=255,u.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?s?Uint8Array.prototype.indexOf.call(t,e,i):Uint8Array.prototype.lastIndexOf.call(t,e,i):v(t,[e],i,r,s);throw new TypeError("val must be string, number or Buffer")}function v(t,e,i,r,s){var n,o=1,a=t.length,h=e.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(t.length<2||e.length<2)return-1;a/=o=2,h/=2,i/=2}function p(t,e){return 1===o?t[e]:t.readUInt16BE(e*o)}if(s){var c=-1;for(n=i;n>>10&1023|55296),c=56320|1023&c),r.push(c),s+=u}return function(t){var e=t.length;if(e<=S)return String.fromCharCode.apply(String,t);var i="",r=0;for(;rthis.length)return"";if((void 0===i||i>this.length)&&(i=this.length),i<=0)return"";if((i>>>=0)<=(e>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return x(this,e,i);case"utf8":case"utf-8":return b(this,e,i);case"ascii":return N(this,e,i);case"latin1":case"binary":return P(this,e,i);case"base64":return E(this,e,i);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return R(this,e,i);default:if(r)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),r=!0}}.apply(this,arguments)},u.prototype.equals=function(t){if(!u.isBuffer(t))throw new TypeError("Argument must be a Buffer");return this===t||0===u.compare(this,t)},u.prototype.inspect=function(){var t="",e=B.INSPECT_MAX_BYTES;return 0e&&(t+=" ... ")),""},u.prototype.compare=function(t,e,i,r,s){if(!u.isBuffer(t))throw new TypeError("Argument must be a Buffer");if(void 0===e&&(e=0),void 0===i&&(i=t?t.length:0),void 0===r&&(r=0),void 0===s&&(s=this.length),e<0||i>t.length||r<0||s>this.length)throw new RangeError("out of range index");if(s<=r&&i<=e)return 0;if(s<=r)return-1;if(i<=e)return 1;if(this===t)return 0;for(var n=(s>>>=0)-(r>>>=0),o=(i>>>=0)-(e>>>=0),a=Math.min(n,o),h=this.slice(r,s),p=t.slice(e,i),c=0;cthis.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var n,o,a,h,p,c,u,l,f,d=!1;;)switch(r){case"hex":return g(this,t,e,i);case"utf8":case"utf-8":return l=e,f=i,U(F(t,(u=this).length-l),u,l,f);case"ascii":return m(this,t,e,i);case"latin1":case"binary":return m(this,t,e,i);case"base64":return h=this,p=e,c=i,U(D(t),h,p,c);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return o=e,a=i,U(function(t,e){for(var i,r,s,n=[],o=0;o>8,s=i%256,n.push(s),n.push(r);return n}(t,(n=this).length-o),n,o,a);default:if(d)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),d=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var S=4096;function N(t,e,i){var r="";i=Math.min(t.length,i);for(var s=e;st.length)throw new RangeError("Index out of range")}function w(t,e,i,r){e<0&&(e=65535+e+1);for(var s=0,n=Math.min(t.length-i,2);s>>8*(r?s:1-s)}function A(t,e,i,r){e<0&&(e=4294967295+e+1);for(var s=0,n=Math.min(t.length-i,4);s>>8*(r?s:3-s)&255}function O(t,e,i,r,s,n){if(i+r>t.length)throw new RangeError("Index out of range");if(i<0)throw new RangeError("Index out of range")}function T(t,e,i,r,s){return s||O(t,0,i,4),n.write(t,e,i,r,23,4),i+4}function C(t,e,i,r,s){return s||O(t,0,i,8),n.write(t,e,i,r,52,8),i+8}u.prototype.slice=function(t,e){var i,r=this.length;if((t=~~t)<0?(t+=r)<0&&(t=0):r>>8):w(this,t,e,!0),e+2},u.prototype.writeUInt16BE=function(t,e,i){return t=+t,e|=0,i||k(this,t,e,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):w(this,t,e,!1),e+2},u.prototype.writeUInt32LE=function(t,e,i){return t=+t,e|=0,i||k(this,t,e,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[e+3]=t>>>24,this[e+2]=t>>>16,this[e+1]=t>>>8,this[e]=255&t):A(this,t,e,!0),e+4},u.prototype.writeUInt32BE=function(t,e,i){return t=+t,e|=0,i||k(this,t,e,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):A(this,t,e,!1),e+4},u.prototype.writeIntLE=function(t,e,i,r){if(t=+t,e|=0,!r){var s=Math.pow(2,8*i-1);k(this,t,e,i,s-1,-s)}var n=0,o=1,a=0;for(this[e]=255&t;++n>0)-a&255;return e+i},u.prototype.writeIntBE=function(t,e,i,r){if(t=+t,e|=0,!r){var s=Math.pow(2,8*i-1);k(this,t,e,i,s-1,-s)}var n=i-1,o=1,a=0;for(this[e+n]=255&t;0<=--n&&(o*=256);)t<0&&0===a&&0!==this[e+n+1]&&(a=1),this[e+n]=(t/o>>0)-a&255;return e+i},u.prototype.writeInt8=function(t,e,i){return t=+t,e|=0,i||k(this,t,e,1,127,-128),u.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),t<0&&(t=255+t+1),this[e]=255&t,e+1},u.prototype.writeInt16LE=function(t,e,i){return t=+t,e|=0,i||k(this,t,e,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):w(this,t,e,!0),e+2},u.prototype.writeInt16BE=function(t,e,i){return t=+t,e|=0,i||k(this,t,e,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):w(this,t,e,!1),e+2},u.prototype.writeInt32LE=function(t,e,i){return t=+t,e|=0,i||k(this,t,e,4,2147483647,-2147483648),u.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8,this[e+2]=t>>>16,this[e+3]=t>>>24):A(this,t,e,!0),e+4},u.prototype.writeInt32BE=function(t,e,i){return t=+t,e|=0,i||k(this,t,e,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),u.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):A(this,t,e,!1),e+4},u.prototype.writeFloatLE=function(t,e,i){return T(this,t,e,!0,i)},u.prototype.writeFloatBE=function(t,e,i){return T(this,t,e,!1,i)},u.prototype.writeDoubleLE=function(t,e,i){return C(this,t,e,!0,i)},u.prototype.writeDoubleBE=function(t,e,i){return C(this,t,e,!1,i)},u.prototype.copy=function(t,e,i,r){if(i||(i=0),r||0===r||(r=this.length),e>=t.length&&(e=t.length),e||(e=0),0=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),t.length-e>>=0,i=void 0===i?this.length:i>>>0,t||(t=0),"number"==typeof t)for(n=e;n>6|192,63&i|128)}else if(i<65536){if((e-=3)<0)break;n.push(i>>12|224,i>>6&63|128,63&i|128)}else{if(!(i<1114112))throw new Error("Invalid code point");if((e-=4)<0)break;n.push(i>>18|240,i>>12&63|128,i>>6&63|128,63&i|128)}}return n}function D(t){return r.toByteArray(function(t){var e;if((t=(e=t,e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")).replace(I,"")).length<2)return"";for(;t.length%4!=0;)t+="=";return t}(t))}function U(t,e,i,r){for(var s=0;s=e.length||s>=t.length);++s)e[s+i]=t[s];return s}}).call(B,e(7))},function(e,t,i){(function(R){var t=function(){"use strict";function E(t,e){return null!=e&&t instanceof e}var b,S,N;try{b=Map}catch(t){b=function(){}}try{S=Set}catch(t){S=function(){}}try{N=Promise}catch(t){N=function(){}}function P(t,f,e,d,y){"object"==typeof f&&(e=f.depth,d=f.prototype,y=f.includeNonEnumerable,f=f.circular);var v=[],g=[],m=void 0!==R;return void 0===f&&(f=!0),void 0===e&&(e=1/0),function s(t,n){if(null===t)return null;if(0===n)return t;var o,e;if("object"!=typeof t)return t;if(E(t,b))o=new b;else if(E(t,S))o=new S;else if(E(t,N))o=new N(function(e,i){t.then(function(t){e(s(t,n-1))},function(t){i(s(t,n-1))})});else if(P.__isArray(t))o=[];else if(P.__isRegExp(t))o=new RegExp(t.source,x(t)),t.lastIndex&&(o.lastIndex=t.lastIndex);else if(P.__isDate(t))o=new Date(t.getTime());else{if(m&&R.isBuffer(t))return o=new R(t.length),t.copy(o),o;E(t,Error)?o=Object.create(t):void 0===d?(e=Object.getPrototypeOf(t),o=Object.create(e)):(o=Object.create(d),e=d)}if(f){var i=v.indexOf(t);if(-1!=i)return g[i];v.push(t),g.push(o)}for(var r in E(t,b)&&t.forEach(function(t,e){var i=s(e,n-1),r=s(t,n-1);o.set(i,r)}),E(t,S)&&t.forEach(function(t){var e=s(t,n-1);o.add(e)}),t){var a;e&&(a=Object.getOwnPropertyDescriptor(e,r)),Object.keys(t).indexOf(r)<0&&a&&null==a.set||(o[r]=s(t[r],n-1))}if(Object.getOwnPropertySymbols){var h=Object.getOwnPropertySymbols(t);for(r=0;r>1,c=-7,u=i?s-1:0,l=i?-1:1,f=t[e+u];for(u+=l,n=f&(1<<-c)-1,f>>=-c,c+=a;0>=-c,c+=r;0>1,l=23===s?Math.pow(2,-24)-Math.pow(2,-77):0,f=r?0:n-1,d=r?1:-1,y=e<0||0===e&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(a=isNaN(e)?1:0,o=c):(o=Math.floor(Math.log(e)/Math.LN2),e*(h=Math.pow(2,-o))<1&&(o--,h*=2),2<=(e+=1<=o+u?l/h:l*Math.pow(2,1-u))*h&&(o++,h/=2),c<=o+u?(a=0,o=c):1<=o+u?(a=(e*h-1)*Math.pow(2,s),o+=u):(a=e*Math.pow(2,u-1)*Math.pow(2,s),o=0));8<=s;t[i+f]=255&a,f+=d,a/=256,s-=8);for(o=o< + +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Kevin Decker nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/README.md b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/README.md new file mode 100644 index 000000000..ec2ba840c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/README.md @@ -0,0 +1,211 @@ +# jsdiff + +[![Build Status](https://secure.travis-ci.org/kpdecker/jsdiff.svg)](http://travis-ci.org/kpdecker/jsdiff) +[![Sauce Test Status](https://saucelabs.com/buildstatus/jsdiff)](https://saucelabs.com/u/jsdiff) + +A javascript text differencing implementation. + +Based on the algorithm proposed in +["An O(ND) Difference Algorithm and its Variations" (Myers, 1986)](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927). + +## Installation +```bash +npm install diff --save +``` + +## API + +* `Diff.diffChars(oldStr, newStr[, options])` - diffs two blocks of text, comparing character by character. + + Returns a list of change objects (See below). + + Options + * `ignoreCase`: `true` to ignore casing difference. Defaults to `false`. + +* `Diff.diffWords(oldStr, newStr[, options])` - diffs two blocks of text, comparing word by word, ignoring whitespace. + + Returns a list of change objects (See below). + + Options + * `ignoreCase`: Same as in `diffChars`. + +* `Diff.diffWordsWithSpace(oldStr, newStr[, options])` - diffs two blocks of text, comparing word by word, treating whitespace as significant. + + Returns a list of change objects (See below). + +* `Diff.diffLines(oldStr, newStr[, options])` - diffs two blocks of text, comparing line by line. + + Options + * `ignoreWhitespace`: `true` to ignore leading and trailing whitespace. This is the same as `diffTrimmedLines` + * `newlineIsToken`: `true` to treat newline characters as separate tokens. This allows for changes to the newline structure to occur independently of the line content and to be treated as such. In general this is the more human friendly form of `diffLines` and `diffLines` is better suited for patches and other computer friendly output. + + Returns a list of change objects (See below). + +* `Diff.diffTrimmedLines(oldStr, newStr[, options])` - diffs two blocks of text, comparing line by line, ignoring leading and trailing whitespace. + + Returns a list of change objects (See below). + +* `Diff.diffSentences(oldStr, newStr[, options])` - diffs two blocks of text, comparing sentence by sentence. + + Returns a list of change objects (See below). + +* `Diff.diffCss(oldStr, newStr[, options])` - diffs two blocks of text, comparing CSS tokens. + + Returns a list of change objects (See below). + +* `Diff.diffJson(oldObj, newObj[, options])` - diffs two JSON objects, comparing the fields defined on each. The order of fields, etc does not matter in this comparison. + + Returns a list of change objects (See below). + +* `Diff.diffArrays(oldArr, newArr[, options])` - diffs two arrays, comparing each item for strict equality (===). + + Options + * `comparator`: `function(left, right)` for custom equality checks + + Returns a list of change objects (See below). + +* `Diff.createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader)` - creates a unified diff patch. + + Parameters: + * `oldFileName` : String to be output in the filename section of the patch for the removals + * `newFileName` : String to be output in the filename section of the patch for the additions + * `oldStr` : Original string value + * `newStr` : New string value + * `oldHeader` : Additional information to include in the old file header + * `newHeader` : Additional information to include in the new file header + * `options` : An object with options. + - `context` describes how many lines of context should be included. + - `ignoreWhitespace`: `true` to ignore leading and trailing whitespace. + - `newlineIsToken`: `true` to treat newline characters as separate tokens. This allows for changes to the newline structure to occur independently of the line content and to be treated as such. In general this is the more human friendly form of `diffLines` and `diffLines` is better suited for patches and other computer friendly output. + +* `Diff.createPatch(fileName, oldStr, newStr, oldHeader, newHeader)` - creates a unified diff patch. + + Just like Diff.createTwoFilesPatch, but with oldFileName being equal to newFileName. + + +* `Diff.structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options)` - returns an object with an array of hunk objects. + + This method is similar to createTwoFilesPatch, but returns a data structure + suitable for further processing. Parameters are the same as createTwoFilesPatch. The data structure returned may look like this: + + ```js + { + oldFileName: 'oldfile', newFileName: 'newfile', + oldHeader: 'header1', newHeader: 'header2', + hunks: [{ + oldStart: 1, oldLines: 3, newStart: 1, newLines: 3, + lines: [' line2', ' line3', '-line4', '+line5', '\\ No newline at end of file'], + }] + } + ``` + +* `Diff.applyPatch(source, patch[, options])` - applies a unified diff patch. + + Return a string containing new version of provided data. `patch` may be a string diff or the output from the `parsePatch` or `structuredPatch` methods. + + The optional `options` object may have the following keys: + + - `fuzzFactor`: Number of lines that are allowed to differ before rejecting a patch. Defaults to 0. + - `compareLine(lineNumber, line, operation, patchContent)`: Callback used to compare to given lines to determine if they should be considered equal when patching. Defaults to strict equality but may be overridden to provide fuzzier comparison. Should return false if the lines should be rejected. + +* `Diff.applyPatches(patch, options)` - applies one or more patches. + + This method will iterate over the contents of the patch and apply to data provided through callbacks. The general flow for each patch index is: + + - `options.loadFile(index, callback)` is called. The caller should then load the contents of the file and then pass that to the `callback(err, data)` callback. Passing an `err` will terminate further patch execution. + - `options.patched(index, content, callback)` is called once the patch has been applied. `content` will be the return value from `applyPatch`. When it's ready, the caller should call `callback(err)` callback. Passing an `err` will terminate further patch execution. + + Once all patches have been applied or an error occurs, the `options.complete(err)` callback is made. + +* `Diff.parsePatch(diffStr)` - Parses a patch into structured data + + Return a JSON object representation of the a patch, suitable for use with the `applyPatch` method. This parses to the same structure returned by `Diff.structuredPatch`. + +* `convertChangesToXML(changes)` - converts a list of changes to a serialized XML format + + +All methods above which accept the optional `callback` method will run in sync mode when that parameter is omitted and in async mode when supplied. This allows for larger diffs without blocking the event loop. This may be passed either directly as the final parameter or as the `callback` field in the `options` object. + +### Change Objects +Many of the methods above return change objects. These objects consist of the following fields: + +* `value`: Text content +* `added`: True if the value was inserted into the new string +* `removed`: True if the value was removed from the old string + +Note that some cases may omit a particular flag field. Comparison on the flag fields should always be done in a truthy or falsy manner. + +## Examples + +Basic example in Node + +```js +require('colors'); +const Diff = require('diff'); + +const one = 'beep boop'; +const other = 'beep boob blah'; + +const diff = Diff.diffChars(one, other); + +diff.forEach((part) => { + // green for additions, red for deletions + // grey for common parts + const color = part.added ? 'green' : + part.removed ? 'red' : 'grey'; + process.stderr.write(part.value[color]); +}); + +console.log(); +``` +Running the above program should yield + +Node Example + +Basic example in a web page + +```html +

+
+
+```
+
+Open the above .html file in a browser and you should see
+
+Node Example
+
+**[Full online demo](https://kpdecker.github.io/jsdiff)**
+
+## Compatibility
+
+[![Sauce Test Status](https://saucelabs.com/browser-matrix/jsdiff.svg)](https://saucelabs.com/u/jsdiff)
+
+jsdiff supports all ES3 environments with some known issues on IE8 and below. Under these browsers some diff algorithms such as word diff and others may fail due to lack of support for capturing groups in the `split` operation.
+
+## License
+
+See [LICENSE](https://github.com/kpdecker/jsdiff/blob/master/LICENSE).
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/convert/dmp.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/convert/dmp.js
new file mode 100644
index 000000000..b411dc2de
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/convert/dmp.js
@@ -0,0 +1,19 @@
+// See: http://code.google.com/p/google-diff-match-patch/wiki/API
+export function convertChangesToDMP(changes) {
+  let ret = [],
+      change,
+      operation;
+  for (let i = 0; i < changes.length; i++) {
+    change = changes[i];
+    if (change.added) {
+      operation = 1;
+    } else if (change.removed) {
+      operation = -1;
+    } else {
+      operation = 0;
+    }
+
+    ret.push([operation, change.value]);
+  }
+  return ret;
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/convert/xml.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/convert/xml.js
new file mode 100644
index 000000000..34aa46366
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/convert/xml.js
@@ -0,0 +1,30 @@
+export function convertChangesToXML(changes) {
+  let ret = [];
+  for (let i = 0; i < changes.length; i++) {
+    let change = changes[i];
+    if (change.added) {
+      ret.push('');
+    } else if (change.removed) {
+      ret.push('');
+    }
+
+    ret.push(escapeHTML(change.value));
+
+    if (change.added) {
+      ret.push('');
+    } else if (change.removed) {
+      ret.push('');
+    }
+  }
+  return ret.join('');
+}
+
+function escapeHTML(s) {
+  let n = s;
+  n = n.replace(/&/g, '&');
+  n = n.replace(//g, '>');
+  n = n.replace(/"/g, '"');
+
+  return n;
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/array.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/array.js
new file mode 100644
index 000000000..99de08fcb
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/array.js
@@ -0,0 +1,11 @@
+import Diff from './base';
+
+export const arrayDiff = new Diff();
+arrayDiff.tokenize = function(value) {
+  return value.slice();
+};
+arrayDiff.join = arrayDiff.removeEmpty = function(value) {
+  return value;
+};
+
+export function diffArrays(oldArr, newArr, callback) { return arrayDiff.diff(oldArr, newArr, callback); }
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/base.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/base.js
new file mode 100644
index 000000000..b11e7e523
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/base.js
@@ -0,0 +1,235 @@
+export default function Diff() {}
+
+Diff.prototype = {
+  diff(oldString, newString, options = {}) {
+    let callback = options.callback;
+    if (typeof options === 'function') {
+      callback = options;
+      options = {};
+    }
+    this.options = options;
+
+    let self = this;
+
+    function done(value) {
+      if (callback) {
+        setTimeout(function() { callback(undefined, value); }, 0);
+        return true;
+      } else {
+        return value;
+      }
+    }
+
+    // Allow subclasses to massage the input prior to running
+    oldString = this.castInput(oldString);
+    newString = this.castInput(newString);
+
+    oldString = this.removeEmpty(this.tokenize(oldString));
+    newString = this.removeEmpty(this.tokenize(newString));
+
+    let newLen = newString.length, oldLen = oldString.length;
+    let editLength = 1;
+    let maxEditLength = newLen + oldLen;
+    if(options.maxEditLength) {
+      maxEditLength = Math.min(maxEditLength, options.maxEditLength);
+    }
+
+    let bestPath = [{ newPos: -1, components: [] }];
+
+    // Seed editLength = 0, i.e. the content starts with the same values
+    let oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
+    if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) {
+      // Identity per the equality and tokenizer
+      return done([{value: this.join(newString), count: newString.length}]);
+    }
+
+    // Main worker method. checks all permutations of a given edit length for acceptance.
+    function execEditLength() {
+      for (let diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) {
+        let basePath;
+        let addPath = bestPath[diagonalPath - 1],
+            removePath = bestPath[diagonalPath + 1],
+            oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
+        if (addPath) {
+          // No one else is going to attempt to use this value, clear it
+          bestPath[diagonalPath - 1] = undefined;
+        }
+
+        let canAdd = addPath && addPath.newPos + 1 < newLen,
+            canRemove = removePath && 0 <= oldPos && oldPos < oldLen;
+        if (!canAdd && !canRemove) {
+          // If this path is a terminal then prune
+          bestPath[diagonalPath] = undefined;
+          continue;
+        }
+
+        // Select the diagonal that we want to branch from. We select the prior
+        // path whose position in the new string is the farthest from the origin
+        // and does not pass the bounds of the diff graph
+        if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) {
+          basePath = clonePath(removePath);
+          self.pushComponent(basePath.components, undefined, true);
+        } else {
+          basePath = addPath; // No need to clone, we've pulled it from the list
+          basePath.newPos++;
+          self.pushComponent(basePath.components, true, undefined);
+        }
+
+        oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath);
+
+        // If we have hit the end of both strings, then we are done
+        if (basePath.newPos + 1 >= newLen && oldPos + 1 >= oldLen) {
+          return done(buildValues(self, basePath.components, newString, oldString, self.useLongestToken));
+        } else {
+          // Otherwise track this path as a potential candidate and continue.
+          bestPath[diagonalPath] = basePath;
+        }
+      }
+
+      editLength++;
+    }
+
+    // Performs the length of edit iteration. Is a bit fugly as this has to support the
+    // sync and async mode which is never fun. Loops over execEditLength until a value
+    // is produced, or until the edit length exceeds options.maxEditLength (if given),
+    // in which case it will return undefined.
+    if (callback) {
+      (function exec() {
+        setTimeout(function() {
+          if (editLength > maxEditLength) {
+            return callback();
+          }
+
+          if (!execEditLength()) {
+            exec();
+          }
+        }, 0);
+      }());
+    } else {
+      while (editLength <= maxEditLength) {
+        let ret = execEditLength();
+        if (ret) {
+          return ret;
+        }
+      }
+    }
+  },
+
+  pushComponent(components, added, removed) {
+    let last = components[components.length - 1];
+    if (last && last.added === added && last.removed === removed) {
+      // We need to clone here as the component clone operation is just
+      // as shallow array clone
+      components[components.length - 1] = {count: last.count + 1, added: added, removed: removed };
+    } else {
+      components.push({count: 1, added: added, removed: removed });
+    }
+  },
+  extractCommon(basePath, newString, oldString, diagonalPath) {
+    let newLen = newString.length,
+        oldLen = oldString.length,
+        newPos = basePath.newPos,
+        oldPos = newPos - diagonalPath,
+
+        commonCount = 0;
+    while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) {
+      newPos++;
+      oldPos++;
+      commonCount++;
+    }
+
+    if (commonCount) {
+      basePath.components.push({count: commonCount});
+    }
+
+    basePath.newPos = newPos;
+    return oldPos;
+  },
+
+  equals(left, right) {
+    if (this.options.comparator) {
+      return this.options.comparator(left, right);
+    } else {
+      return left === right
+        || (this.options.ignoreCase && left.toLowerCase() === right.toLowerCase());
+    }
+  },
+  removeEmpty(array) {
+    let ret = [];
+    for (let i = 0; i < array.length; i++) {
+      if (array[i]) {
+        ret.push(array[i]);
+      }
+    }
+    return ret;
+  },
+  castInput(value) {
+    return value;
+  },
+  tokenize(value) {
+    return value.split('');
+  },
+  join(chars) {
+    return chars.join('');
+  }
+};
+
+function buildValues(diff, components, newString, oldString, useLongestToken) {
+  let componentPos = 0,
+      componentLen = components.length,
+      newPos = 0,
+      oldPos = 0;
+
+  for (; componentPos < componentLen; componentPos++) {
+    let component = components[componentPos];
+    if (!component.removed) {
+      if (!component.added && useLongestToken) {
+        let value = newString.slice(newPos, newPos + component.count);
+        value = value.map(function(value, i) {
+          let oldValue = oldString[oldPos + i];
+          return oldValue.length > value.length ? oldValue : value;
+        });
+
+        component.value = diff.join(value);
+      } else {
+        component.value = diff.join(newString.slice(newPos, newPos + component.count));
+      }
+      newPos += component.count;
+
+      // Common case
+      if (!component.added) {
+        oldPos += component.count;
+      }
+    } else {
+      component.value = diff.join(oldString.slice(oldPos, oldPos + component.count));
+      oldPos += component.count;
+
+      // Reverse add and remove so removes are output first to match common convention
+      // The diffing algorithm is tied to add then remove output and this is the simplest
+      // route to get the desired output with minimal overhead.
+      if (componentPos && components[componentPos - 1].added) {
+        let tmp = components[componentPos - 1];
+        components[componentPos - 1] = components[componentPos];
+        components[componentPos] = tmp;
+      }
+    }
+  }
+
+  // Special case handle for when one terminal is ignored (i.e. whitespace).
+  // For this case we merge the terminal into the prior string and drop the change.
+  // This is only available for string mode.
+  let lastComponent = components[componentLen - 1];
+  if (componentLen > 1
+      && typeof lastComponent.value === 'string'
+      && (lastComponent.added || lastComponent.removed)
+      && diff.equals('', lastComponent.value)) {
+    components[componentLen - 2].value += lastComponent.value;
+    components.pop();
+  }
+
+  return components;
+}
+
+function clonePath(path) {
+  return { newPos: path.newPos, components: path.components.slice(0) };
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/character.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/character.js
new file mode 100644
index 000000000..e9f17b111
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/character.js
@@ -0,0 +1,4 @@
+import Diff from './base';
+
+export const characterDiff = new Diff();
+export function diffChars(oldStr, newStr, options) { return characterDiff.diff(oldStr, newStr, options); }
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/css.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/css.js
new file mode 100644
index 000000000..e2e445a60
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/css.js
@@ -0,0 +1,8 @@
+import Diff from './base';
+
+export const cssDiff = new Diff();
+cssDiff.tokenize = function(value) {
+  return value.split(/([{}:;,]|\s+)/);
+};
+
+export function diffCss(oldStr, newStr, callback) { return cssDiff.diff(oldStr, newStr, callback); }
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/json.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/json.js
new file mode 100644
index 000000000..6c06c9ab6
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/json.js
@@ -0,0 +1,83 @@
+import Diff from './base';
+import {lineDiff} from './line';
+
+const objectPrototypeToString = Object.prototype.toString;
+
+
+export const jsonDiff = new Diff();
+// Discriminate between two lines of pretty-printed, serialized JSON where one of them has a
+// dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output:
+jsonDiff.useLongestToken = true;
+
+jsonDiff.tokenize = lineDiff.tokenize;
+jsonDiff.castInput = function(value) {
+  const {undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v} = this.options;
+
+  return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, '  ');
+};
+jsonDiff.equals = function(left, right) {
+  return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'));
+};
+
+export function diffJson(oldObj, newObj, options) { return jsonDiff.diff(oldObj, newObj, options); }
+
+// This function handles the presence of circular references by bailing out when encountering an
+// object that is already on the "stack" of items being processed. Accepts an optional replacer
+export function canonicalize(obj, stack, replacementStack, replacer, key) {
+  stack = stack || [];
+  replacementStack = replacementStack || [];
+
+  if (replacer) {
+    obj = replacer(key, obj);
+  }
+
+  let i;
+
+  for (i = 0; i < stack.length; i += 1) {
+    if (stack[i] === obj) {
+      return replacementStack[i];
+    }
+  }
+
+  let canonicalizedObj;
+
+  if ('[object Array]' === objectPrototypeToString.call(obj)) {
+    stack.push(obj);
+    canonicalizedObj = new Array(obj.length);
+    replacementStack.push(canonicalizedObj);
+    for (i = 0; i < obj.length; i += 1) {
+      canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key);
+    }
+    stack.pop();
+    replacementStack.pop();
+    return canonicalizedObj;
+  }
+
+  if (obj && obj.toJSON) {
+    obj = obj.toJSON();
+  }
+
+  if (typeof obj === 'object' && obj !== null) {
+    stack.push(obj);
+    canonicalizedObj = {};
+    replacementStack.push(canonicalizedObj);
+    let sortedKeys = [],
+        key;
+    for (key in obj) {
+      /* istanbul ignore else */
+      if (obj.hasOwnProperty(key)) {
+        sortedKeys.push(key);
+      }
+    }
+    sortedKeys.sort();
+    for (i = 0; i < sortedKeys.length; i += 1) {
+      key = sortedKeys[i];
+      canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key);
+    }
+    stack.pop();
+    replacementStack.pop();
+  } else {
+    canonicalizedObj = obj;
+  }
+  return canonicalizedObj;
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/line.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/line.js
new file mode 100644
index 000000000..f9b4f4993
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/line.js
@@ -0,0 +1,35 @@
+import Diff from './base';
+import {generateOptions} from '../util/params';
+
+export const lineDiff = new Diff();
+lineDiff.tokenize = function(value) {
+  let retLines = [],
+      linesAndNewlines = value.split(/(\n|\r\n)/);
+
+  // Ignore the final empty token that occurs if the string ends with a new line
+  if (!linesAndNewlines[linesAndNewlines.length - 1]) {
+    linesAndNewlines.pop();
+  }
+
+  // Merge the content and line separators into single tokens
+  for (let i = 0; i < linesAndNewlines.length; i++) {
+    let line = linesAndNewlines[i];
+
+    if (i % 2 && !this.options.newlineIsToken) {
+      retLines[retLines.length - 1] += line;
+    } else {
+      if (this.options.ignoreWhitespace) {
+        line = line.trim();
+      }
+      retLines.push(line);
+    }
+  }
+
+  return retLines;
+};
+
+export function diffLines(oldStr, newStr, callback) { return lineDiff.diff(oldStr, newStr, callback); }
+export function diffTrimmedLines(oldStr, newStr, callback) {
+  let options = generateOptions(callback, {ignoreWhitespace: true});
+  return lineDiff.diff(oldStr, newStr, options);
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/sentence.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/sentence.js
new file mode 100644
index 000000000..d855efd3e
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/sentence.js
@@ -0,0 +1,9 @@
+import Diff from './base';
+
+
+export const sentenceDiff = new Diff();
+sentenceDiff.tokenize = function(value) {
+  return value.split(/(\S.+?[.!?])(?=\s+|$)/);
+};
+
+export function diffSentences(oldStr, newStr, callback) { return sentenceDiff.diff(oldStr, newStr, callback); }
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/word.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/word.js
new file mode 100644
index 000000000..6d8741de7
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/word.js
@@ -0,0 +1,60 @@
+import Diff from './base';
+import {generateOptions} from '../util/params';
+
+// Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode
+//
+// Ranges and exceptions:
+// Latin-1 Supplement, 0080–00FF
+//  - U+00D7  × Multiplication sign
+//  - U+00F7  ÷ Division sign
+// Latin Extended-A, 0100–017F
+// Latin Extended-B, 0180–024F
+// IPA Extensions, 0250–02AF
+// Spacing Modifier Letters, 02B0–02FF
+//  - U+02C7  ˇ ˇ  Caron
+//  - U+02D8  ˘ ˘  Breve
+//  - U+02D9  ˙ ˙  Dot Above
+//  - U+02DA  ˚ ˚  Ring Above
+//  - U+02DB  ˛ ˛  Ogonek
+//  - U+02DC  ˜ ˜  Small Tilde
+//  - U+02DD  ˝ ˝  Double Acute Accent
+// Latin Extended Additional, 1E00–1EFF
+const extendedWordChars = /^[a-zA-Z\u{C0}-\u{FF}\u{D8}-\u{F6}\u{F8}-\u{2C6}\u{2C8}-\u{2D7}\u{2DE}-\u{2FF}\u{1E00}-\u{1EFF}]+$/u;
+
+const reWhitespace = /\S/;
+
+export const wordDiff = new Diff();
+wordDiff.equals = function(left, right) {
+  if (this.options.ignoreCase) {
+    left = left.toLowerCase();
+    right = right.toLowerCase();
+  }
+  return left === right || (this.options.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right));
+};
+wordDiff.tokenize = function(value) {
+  // All whitespace symbols except newline group into one token, each newline - in separate token
+  let tokens = value.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/);
+
+  // Join the boundary splits that we do not consider to be boundaries. This is primarily the extended Latin character set.
+  for (let i = 0; i < tokens.length - 1; i++) {
+    // If we have an empty string in the next field and we have only word chars before and after, merge
+    if (!tokens[i + 1] && tokens[i + 2]
+          && extendedWordChars.test(tokens[i])
+          && extendedWordChars.test(tokens[i + 2])) {
+      tokens[i] += tokens[i + 2];
+      tokens.splice(i + 1, 2);
+      i--;
+    }
+  }
+
+  return tokens;
+};
+
+export function diffWords(oldStr, newStr, options) {
+  options = generateOptions(options, {ignoreWhitespace: true});
+  return wordDiff.diff(oldStr, newStr, options);
+}
+
+export function diffWordsWithSpace(oldStr, newStr, options) {
+  return wordDiff.diff(oldStr, newStr, options);
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/index.js
new file mode 100644
index 000000000..a15358242
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/index.js
@@ -0,0 +1,61 @@
+/* See LICENSE file for terms of use */
+
+/*
+ * Text diff implementation.
+ *
+ * This library supports the following APIS:
+ * JsDiff.diffChars: Character by character diff
+ * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace
+ * JsDiff.diffLines: Line based diff
+ *
+ * JsDiff.diffCss: Diff targeted at CSS content
+ *
+ * These methods are based on the implementation proposed in
+ * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
+ * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927
+ */
+import Diff from './diff/base';
+import {diffChars} from './diff/character';
+import {diffWords, diffWordsWithSpace} from './diff/word';
+import {diffLines, diffTrimmedLines} from './diff/line';
+import {diffSentences} from './diff/sentence';
+
+import {diffCss} from './diff/css';
+import {diffJson, canonicalize} from './diff/json';
+
+import {diffArrays} from './diff/array';
+
+import {applyPatch, applyPatches} from './patch/apply';
+import {parsePatch} from './patch/parse';
+import {merge} from './patch/merge';
+import {structuredPatch, createTwoFilesPatch, createPatch} from './patch/create';
+
+import {convertChangesToDMP} from './convert/dmp';
+import {convertChangesToXML} from './convert/xml';
+
+export {
+  Diff,
+
+  diffChars,
+  diffWords,
+  diffWordsWithSpace,
+  diffLines,
+  diffTrimmedLines,
+  diffSentences,
+
+  diffCss,
+  diffJson,
+
+  diffArrays,
+
+  structuredPatch,
+  createTwoFilesPatch,
+  createPatch,
+  applyPatch,
+  applyPatches,
+  parsePatch,
+  merge,
+  convertChangesToDMP,
+  convertChangesToXML,
+  canonicalize
+};
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/apply.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/apply.js
new file mode 100644
index 000000000..4cf0ddf05
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/apply.js
@@ -0,0 +1,160 @@
+import {parsePatch} from './parse';
+import distanceIterator from '../util/distance-iterator';
+
+export function applyPatch(source, uniDiff, options = {}) {
+  if (typeof uniDiff === 'string') {
+    uniDiff = parsePatch(uniDiff);
+  }
+
+  if (Array.isArray(uniDiff)) {
+    if (uniDiff.length > 1) {
+      throw new Error('applyPatch only works with a single input.');
+    }
+
+    uniDiff = uniDiff[0];
+  }
+
+  // Apply the diff to the input
+  let lines = source.split(/\r\n|[\n\v\f\r\x85]/),
+      delimiters = source.match(/\r\n|[\n\v\f\r\x85]/g) || [],
+      hunks = uniDiff.hunks,
+
+      compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent),
+      errorCount = 0,
+      fuzzFactor = options.fuzzFactor || 0,
+      minLine = 0,
+      offset = 0,
+
+      removeEOFNL,
+      addEOFNL;
+
+  /**
+   * Checks if the hunk exactly fits on the provided location
+   */
+  function hunkFits(hunk, toPos) {
+    for (let j = 0; j < hunk.lines.length; j++) {
+      let line = hunk.lines[j],
+          operation = (line.length > 0 ? line[0] : ' '),
+          content = (line.length > 0 ? line.substr(1) : line);
+
+      if (operation === ' ' || operation === '-') {
+        // Context sanity check
+        if (!compareLine(toPos + 1, lines[toPos], operation, content)) {
+          errorCount++;
+
+          if (errorCount > fuzzFactor) {
+            return false;
+          }
+        }
+        toPos++;
+      }
+    }
+
+    return true;
+  }
+
+  // Search best fit offsets for each hunk based on the previous ones
+  for (let i = 0; i < hunks.length; i++) {
+    let hunk = hunks[i],
+        maxLine = lines.length - hunk.oldLines,
+        localOffset = 0,
+        toPos = offset + hunk.oldStart - 1;
+
+    let iterator = distanceIterator(toPos, minLine, maxLine);
+
+    for (; localOffset !== undefined; localOffset = iterator()) {
+      if (hunkFits(hunk, toPos + localOffset)) {
+        hunk.offset = offset += localOffset;
+        break;
+      }
+    }
+
+    if (localOffset === undefined) {
+      return false;
+    }
+
+    // Set lower text limit to end of the current hunk, so next ones don't try
+    // to fit over already patched text
+    minLine = hunk.offset + hunk.oldStart + hunk.oldLines;
+  }
+
+  // Apply patch hunks
+  let diffOffset = 0;
+  for (let i = 0; i < hunks.length; i++) {
+    let hunk = hunks[i],
+        toPos = hunk.oldStart + hunk.offset + diffOffset - 1;
+    diffOffset += hunk.newLines - hunk.oldLines;
+
+    for (let j = 0; j < hunk.lines.length; j++) {
+      let line = hunk.lines[j],
+          operation = (line.length > 0 ? line[0] : ' '),
+          content = (line.length > 0 ? line.substr(1) : line),
+          delimiter = hunk.linedelimiters[j];
+
+      if (operation === ' ') {
+        toPos++;
+      } else if (operation === '-') {
+        lines.splice(toPos, 1);
+        delimiters.splice(toPos, 1);
+      /* istanbul ignore else */
+      } else if (operation === '+') {
+        lines.splice(toPos, 0, content);
+        delimiters.splice(toPos, 0, delimiter);
+        toPos++;
+      } else if (operation === '\\') {
+        let previousOperation = hunk.lines[j - 1] ? hunk.lines[j - 1][0] : null;
+        if (previousOperation === '+') {
+          removeEOFNL = true;
+        } else if (previousOperation === '-') {
+          addEOFNL = true;
+        }
+      }
+    }
+  }
+
+  // Handle EOFNL insertion/removal
+  if (removeEOFNL) {
+    while (!lines[lines.length - 1]) {
+      lines.pop();
+      delimiters.pop();
+    }
+  } else if (addEOFNL) {
+    lines.push('');
+    delimiters.push('\n');
+  }
+  for (let _k = 0; _k < lines.length - 1; _k++) {
+    lines[_k] = lines[_k] + delimiters[_k];
+  }
+  return lines.join('');
+}
+
+// Wrapper that supports multiple file patches via callbacks.
+export function applyPatches(uniDiff, options) {
+  if (typeof uniDiff === 'string') {
+    uniDiff = parsePatch(uniDiff);
+  }
+
+  let currentIndex = 0;
+  function processIndex() {
+    let index = uniDiff[currentIndex++];
+    if (!index) {
+      return options.complete();
+    }
+
+    options.loadFile(index, function(err, data) {
+      if (err) {
+        return options.complete(err);
+      }
+
+      let updatedContent = applyPatch(data, index, options);
+      options.patched(index, updatedContent, function(err) {
+        if (err) {
+          return options.complete(err);
+        }
+
+        processIndex();
+      });
+    });
+  }
+  processIndex();
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/create.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/create.js
new file mode 100644
index 000000000..384865457
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/create.js
@@ -0,0 +1,144 @@
+import {diffLines} from '../diff/line';
+
+export function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
+  if (!options) {
+    options = {};
+  }
+  if (typeof options.context === 'undefined') {
+    options.context = 4;
+  }
+
+  const diff = diffLines(oldStr, newStr, options);
+  if(!diff) {
+    return;
+  }
+
+  diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier
+
+  function contextLines(lines) {
+    return lines.map(function(entry) { return ' ' + entry; });
+  }
+
+  let hunks = [];
+  let oldRangeStart = 0, newRangeStart = 0, curRange = [],
+      oldLine = 1, newLine = 1;
+  for (let i = 0; i < diff.length; i++) {
+    const current = diff[i],
+          lines = current.lines || current.value.replace(/\n$/, '').split('\n');
+    current.lines = lines;
+
+    if (current.added || current.removed) {
+      // If we have previous context, start with that
+      if (!oldRangeStart) {
+        const prev = diff[i - 1];
+        oldRangeStart = oldLine;
+        newRangeStart = newLine;
+
+        if (prev) {
+          curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : [];
+          oldRangeStart -= curRange.length;
+          newRangeStart -= curRange.length;
+        }
+      }
+
+      // Output our changes
+      curRange.push(... lines.map(function(entry) {
+        return (current.added ? '+' : '-') + entry;
+      }));
+
+      // Track the updated file position
+      if (current.added) {
+        newLine += lines.length;
+      } else {
+        oldLine += lines.length;
+      }
+    } else {
+      // Identical context lines. Track line changes
+      if (oldRangeStart) {
+        // Close out any changes that have been output (or join overlapping)
+        if (lines.length <= options.context * 2 && i < diff.length - 2) {
+          // Overlapping
+          curRange.push(... contextLines(lines));
+        } else {
+          // end the range and output
+          let contextSize = Math.min(lines.length, options.context);
+          curRange.push(... contextLines(lines.slice(0, contextSize)));
+
+          let hunk = {
+            oldStart: oldRangeStart,
+            oldLines: (oldLine - oldRangeStart + contextSize),
+            newStart: newRangeStart,
+            newLines: (newLine - newRangeStart + contextSize),
+            lines: curRange
+          };
+          if (i >= diff.length - 2 && lines.length <= options.context) {
+            // EOF is inside this hunk
+            let oldEOFNewline = ((/\n$/).test(oldStr));
+            let newEOFNewline = ((/\n$/).test(newStr));
+            let noNlBeforeAdds = lines.length == 0 && curRange.length > hunk.oldLines;
+            if (!oldEOFNewline && noNlBeforeAdds && oldStr.length > 0) {
+              // special case: old has no eol and no trailing context; no-nl can end up before adds
+              // however, if the old file is empty, do not output the no-nl line
+              curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file');
+            }
+            if ((!oldEOFNewline && !noNlBeforeAdds) || !newEOFNewline) {
+              curRange.push('\\ No newline at end of file');
+            }
+          }
+          hunks.push(hunk);
+
+          oldRangeStart = 0;
+          newRangeStart = 0;
+          curRange = [];
+        }
+      }
+      oldLine += lines.length;
+      newLine += lines.length;
+    }
+  }
+
+  return {
+    oldFileName: oldFileName, newFileName: newFileName,
+    oldHeader: oldHeader, newHeader: newHeader,
+    hunks: hunks
+  };
+}
+
+export function formatPatch(diff) {
+  const ret = [];
+  if (diff.oldFileName == diff.newFileName) {
+    ret.push('Index: ' + diff.oldFileName);
+  }
+  ret.push('===================================================================');
+  ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader));
+  ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader));
+
+  for (let i = 0; i < diff.hunks.length; i++) {
+    const hunk = diff.hunks[i];
+    // Unified Diff Format quirk: If the chunk size is 0,
+    // the first number is one lower than one would expect.
+    // https://www.artima.com/weblogs/viewpost.jsp?thread=164293
+    if (hunk.oldLines === 0) {
+      hunk.oldStart -= 1;
+    }
+    if (hunk.newLines === 0) {
+      hunk.newStart -= 1;
+    }
+    ret.push(
+      '@@ -' + hunk.oldStart + ',' + hunk.oldLines
+      + ' +' + hunk.newStart + ',' + hunk.newLines
+      + ' @@'
+    );
+    ret.push.apply(ret, hunk.lines);
+  }
+
+  return ret.join('\n') + '\n';
+}
+
+export function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
+  return formatPatch(structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options));
+}
+
+export function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) {
+  return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options);
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/merge.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/merge.js
new file mode 100644
index 000000000..4509fb387
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/merge.js
@@ -0,0 +1,376 @@
+import {structuredPatch} from './create';
+import {parsePatch} from './parse';
+
+import {arrayEqual, arrayStartsWith} from '../util/array';
+
+export function calcLineCount(hunk) {
+  const {oldLines, newLines} = calcOldNewLineCount(hunk.lines);
+
+  if (oldLines !== undefined) {
+    hunk.oldLines = oldLines;
+  } else {
+    delete hunk.oldLines;
+  }
+
+  if (newLines !== undefined) {
+    hunk.newLines = newLines;
+  } else {
+    delete hunk.newLines;
+  }
+}
+
+export function merge(mine, theirs, base) {
+  mine = loadPatch(mine, base);
+  theirs = loadPatch(theirs, base);
+
+  let ret = {};
+
+  // For index we just let it pass through as it doesn't have any necessary meaning.
+  // Leaving sanity checks on this to the API consumer that may know more about the
+  // meaning in their own context.
+  if (mine.index || theirs.index) {
+    ret.index = mine.index || theirs.index;
+  }
+
+  if (mine.newFileName || theirs.newFileName) {
+    if (!fileNameChanged(mine)) {
+      // No header or no change in ours, use theirs (and ours if theirs does not exist)
+      ret.oldFileName = theirs.oldFileName || mine.oldFileName;
+      ret.newFileName = theirs.newFileName || mine.newFileName;
+      ret.oldHeader = theirs.oldHeader || mine.oldHeader;
+      ret.newHeader = theirs.newHeader || mine.newHeader;
+    } else if (!fileNameChanged(theirs)) {
+      // No header or no change in theirs, use ours
+      ret.oldFileName = mine.oldFileName;
+      ret.newFileName = mine.newFileName;
+      ret.oldHeader = mine.oldHeader;
+      ret.newHeader = mine.newHeader;
+    } else {
+      // Both changed... figure it out
+      ret.oldFileName = selectField(ret, mine.oldFileName, theirs.oldFileName);
+      ret.newFileName = selectField(ret, mine.newFileName, theirs.newFileName);
+      ret.oldHeader = selectField(ret, mine.oldHeader, theirs.oldHeader);
+      ret.newHeader = selectField(ret, mine.newHeader, theirs.newHeader);
+    }
+  }
+
+  ret.hunks = [];
+
+  let mineIndex = 0,
+      theirsIndex = 0,
+      mineOffset = 0,
+      theirsOffset = 0;
+
+  while (mineIndex < mine.hunks.length || theirsIndex < theirs.hunks.length) {
+    let mineCurrent = mine.hunks[mineIndex] || {oldStart: Infinity},
+        theirsCurrent = theirs.hunks[theirsIndex] || {oldStart: Infinity};
+
+    if (hunkBefore(mineCurrent, theirsCurrent)) {
+      // This patch does not overlap with any of the others, yay.
+      ret.hunks.push(cloneHunk(mineCurrent, mineOffset));
+      mineIndex++;
+      theirsOffset += mineCurrent.newLines - mineCurrent.oldLines;
+    } else if (hunkBefore(theirsCurrent, mineCurrent)) {
+      // This patch does not overlap with any of the others, yay.
+      ret.hunks.push(cloneHunk(theirsCurrent, theirsOffset));
+      theirsIndex++;
+      mineOffset += theirsCurrent.newLines - theirsCurrent.oldLines;
+    } else {
+      // Overlap, merge as best we can
+      let mergedHunk = {
+        oldStart: Math.min(mineCurrent.oldStart, theirsCurrent.oldStart),
+        oldLines: 0,
+        newStart: Math.min(mineCurrent.newStart + mineOffset, theirsCurrent.oldStart + theirsOffset),
+        newLines: 0,
+        lines: []
+      };
+      mergeLines(mergedHunk, mineCurrent.oldStart, mineCurrent.lines, theirsCurrent.oldStart, theirsCurrent.lines);
+      theirsIndex++;
+      mineIndex++;
+
+      ret.hunks.push(mergedHunk);
+    }
+  }
+
+  return ret;
+}
+
+function loadPatch(param, base) {
+  if (typeof param === 'string') {
+    if ((/^@@/m).test(param) || ((/^Index:/m).test(param))) {
+      return parsePatch(param)[0];
+    }
+
+    if (!base) {
+      throw new Error('Must provide a base reference or pass in a patch');
+    }
+    return structuredPatch(undefined, undefined, base, param);
+  }
+
+  return param;
+}
+
+function fileNameChanged(patch) {
+  return patch.newFileName && patch.newFileName !== patch.oldFileName;
+}
+
+function selectField(index, mine, theirs) {
+  if (mine === theirs) {
+    return mine;
+  } else {
+    index.conflict = true;
+    return {mine, theirs};
+  }
+}
+
+function hunkBefore(test, check) {
+  return test.oldStart < check.oldStart
+    && (test.oldStart + test.oldLines) < check.oldStart;
+}
+
+function cloneHunk(hunk, offset) {
+  return {
+    oldStart: hunk.oldStart, oldLines: hunk.oldLines,
+    newStart: hunk.newStart + offset, newLines: hunk.newLines,
+    lines: hunk.lines
+  };
+}
+
+function mergeLines(hunk, mineOffset, mineLines, theirOffset, theirLines) {
+  // This will generally result in a conflicted hunk, but there are cases where the context
+  // is the only overlap where we can successfully merge the content here.
+  let mine = {offset: mineOffset, lines: mineLines, index: 0},
+      their = {offset: theirOffset, lines: theirLines, index: 0};
+
+  // Handle any leading content
+  insertLeading(hunk, mine, their);
+  insertLeading(hunk, their, mine);
+
+  // Now in the overlap content. Scan through and select the best changes from each.
+  while (mine.index < mine.lines.length && their.index < their.lines.length) {
+    let mineCurrent = mine.lines[mine.index],
+        theirCurrent = their.lines[their.index];
+
+    if ((mineCurrent[0] === '-' || mineCurrent[0] === '+')
+        && (theirCurrent[0] === '-' || theirCurrent[0] === '+')) {
+      // Both modified ...
+      mutualChange(hunk, mine, their);
+    } else if (mineCurrent[0] === '+' && theirCurrent[0] === ' ') {
+      // Mine inserted
+      hunk.lines.push(... collectChange(mine));
+    } else if (theirCurrent[0] === '+' && mineCurrent[0] === ' ') {
+      // Theirs inserted
+      hunk.lines.push(... collectChange(their));
+    } else if (mineCurrent[0] === '-' && theirCurrent[0] === ' ') {
+      // Mine removed or edited
+      removal(hunk, mine, their);
+    } else if (theirCurrent[0] === '-' && mineCurrent[0] === ' ') {
+      // Their removed or edited
+      removal(hunk, their, mine, true);
+    } else if (mineCurrent === theirCurrent) {
+      // Context identity
+      hunk.lines.push(mineCurrent);
+      mine.index++;
+      their.index++;
+    } else {
+      // Context mismatch
+      conflict(hunk, collectChange(mine), collectChange(their));
+    }
+  }
+
+  // Now push anything that may be remaining
+  insertTrailing(hunk, mine);
+  insertTrailing(hunk, their);
+
+  calcLineCount(hunk);
+}
+
+function mutualChange(hunk, mine, their) {
+  let myChanges = collectChange(mine),
+      theirChanges = collectChange(their);
+
+  if (allRemoves(myChanges) && allRemoves(theirChanges)) {
+    // Special case for remove changes that are supersets of one another
+    if (arrayStartsWith(myChanges, theirChanges)
+        && skipRemoveSuperset(their, myChanges, myChanges.length - theirChanges.length)) {
+      hunk.lines.push(... myChanges);
+      return;
+    } else if (arrayStartsWith(theirChanges, myChanges)
+        && skipRemoveSuperset(mine, theirChanges, theirChanges.length - myChanges.length)) {
+      hunk.lines.push(... theirChanges);
+      return;
+    }
+  } else if (arrayEqual(myChanges, theirChanges)) {
+    hunk.lines.push(... myChanges);
+    return;
+  }
+
+  conflict(hunk, myChanges, theirChanges);
+}
+
+function removal(hunk, mine, their, swap) {
+  let myChanges = collectChange(mine),
+      theirChanges = collectContext(their, myChanges);
+  if (theirChanges.merged) {
+    hunk.lines.push(... theirChanges.merged);
+  } else {
+    conflict(hunk, swap ? theirChanges : myChanges, swap ? myChanges : theirChanges);
+  }
+}
+
+function conflict(hunk, mine, their) {
+  hunk.conflict = true;
+  hunk.lines.push({
+    conflict: true,
+    mine: mine,
+    theirs: their
+  });
+}
+
+function insertLeading(hunk, insert, their) {
+  while (insert.offset < their.offset && insert.index < insert.lines.length) {
+    let line = insert.lines[insert.index++];
+    hunk.lines.push(line);
+    insert.offset++;
+  }
+}
+function insertTrailing(hunk, insert) {
+  while (insert.index < insert.lines.length) {
+    let line = insert.lines[insert.index++];
+    hunk.lines.push(line);
+  }
+}
+
+function collectChange(state) {
+  let ret = [],
+      operation = state.lines[state.index][0];
+  while (state.index < state.lines.length) {
+    let line = state.lines[state.index];
+
+    // Group additions that are immediately after subtractions and treat them as one "atomic" modify change.
+    if (operation === '-' && line[0] === '+') {
+      operation = '+';
+    }
+
+    if (operation === line[0]) {
+      ret.push(line);
+      state.index++;
+    } else {
+      break;
+    }
+  }
+
+  return ret;
+}
+function collectContext(state, matchChanges) {
+  let changes = [],
+      merged = [],
+      matchIndex = 0,
+      contextChanges = false,
+      conflicted = false;
+  while (matchIndex < matchChanges.length
+        && state.index < state.lines.length) {
+    let change = state.lines[state.index],
+        match = matchChanges[matchIndex];
+
+    // Once we've hit our add, then we are done
+    if (match[0] === '+') {
+      break;
+    }
+
+    contextChanges = contextChanges || change[0] !== ' ';
+
+    merged.push(match);
+    matchIndex++;
+
+    // Consume any additions in the other block as a conflict to attempt
+    // to pull in the remaining context after this
+    if (change[0] === '+') {
+      conflicted = true;
+
+      while (change[0] === '+') {
+        changes.push(change);
+        change = state.lines[++state.index];
+      }
+    }
+
+    if (match.substr(1) === change.substr(1)) {
+      changes.push(change);
+      state.index++;
+    } else {
+      conflicted = true;
+    }
+  }
+
+  if ((matchChanges[matchIndex] || '')[0] === '+'
+      && contextChanges) {
+    conflicted = true;
+  }
+
+  if (conflicted) {
+    return changes;
+  }
+
+  while (matchIndex < matchChanges.length) {
+    merged.push(matchChanges[matchIndex++]);
+  }
+
+  return {
+    merged,
+    changes
+  };
+}
+
+function allRemoves(changes) {
+  return changes.reduce(function(prev, change) {
+    return prev && change[0] === '-';
+  }, true);
+}
+function skipRemoveSuperset(state, removeChanges, delta) {
+  for (let i = 0; i < delta; i++) {
+    let changeContent = removeChanges[removeChanges.length - delta + i].substr(1);
+    if (state.lines[state.index + i] !== ' ' + changeContent) {
+      return false;
+    }
+  }
+
+  state.index += delta;
+  return true;
+}
+
+function calcOldNewLineCount(lines) {
+  let oldLines = 0;
+  let newLines = 0;
+
+  lines.forEach(function(line) {
+    if (typeof line !== 'string') {
+      let myCount = calcOldNewLineCount(line.mine);
+      let theirCount = calcOldNewLineCount(line.theirs);
+
+      if (oldLines !== undefined) {
+        if (myCount.oldLines === theirCount.oldLines) {
+          oldLines += myCount.oldLines;
+        } else {
+          oldLines = undefined;
+        }
+      }
+
+      if (newLines !== undefined) {
+        if (myCount.newLines === theirCount.newLines) {
+          newLines += myCount.newLines;
+        } else {
+          newLines = undefined;
+        }
+      }
+    } else {
+      if (newLines !== undefined && (line[0] === '+' || line[0] === ' ')) {
+        newLines++;
+      }
+      if (oldLines !== undefined && (line[0] === '-' || line[0] === ' ')) {
+        oldLines++;
+      }
+    }
+  });
+
+  return {oldLines, newLines};
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/parse.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/parse.js
new file mode 100644
index 000000000..0a8807bb4
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/parse.js
@@ -0,0 +1,153 @@
+export function parsePatch(uniDiff, options = {}) {
+  let diffstr = uniDiff.split(/\r\n|[\n\v\f\r\x85]/),
+      delimiters = uniDiff.match(/\r\n|[\n\v\f\r\x85]/g) || [],
+      list = [],
+      i = 0;
+
+  function parseIndex() {
+    let index = {};
+    list.push(index);
+
+    // Parse diff metadata
+    while (i < diffstr.length) {
+      let line = diffstr[i];
+
+      // File header found, end parsing diff metadata
+      if ((/^(\-\-\-|\+\+\+|@@)\s/).test(line)) {
+        break;
+      }
+
+      // Diff index
+      let header = (/^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/).exec(line);
+      if (header) {
+        index.index = header[1];
+      }
+
+      i++;
+    }
+
+    // Parse file headers if they are defined. Unified diff requires them, but
+    // there's no technical issues to have an isolated hunk without file header
+    parseFileHeader(index);
+    parseFileHeader(index);
+
+    // Parse hunks
+    index.hunks = [];
+
+    while (i < diffstr.length) {
+      let line = diffstr[i];
+
+      if ((/^(Index:|diff|\-\-\-|\+\+\+)\s/).test(line)) {
+        break;
+      } else if ((/^@@/).test(line)) {
+        index.hunks.push(parseHunk());
+      } else if (line && options.strict) {
+        // Ignore unexpected content unless in strict mode
+        throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(line));
+      } else {
+        i++;
+      }
+    }
+  }
+
+  // Parses the --- and +++ headers, if none are found, no lines
+  // are consumed.
+  function parseFileHeader(index) {
+    const fileHeader = (/^(---|\+\+\+)\s+(.*)$/).exec(diffstr[i]);
+    if (fileHeader) {
+      let keyPrefix = fileHeader[1] === '---' ? 'old' : 'new';
+      const data = fileHeader[2].split('\t', 2);
+      let fileName = data[0].replace(/\\\\/g, '\\');
+      if ((/^".*"$/).test(fileName)) {
+        fileName = fileName.substr(1, fileName.length - 2);
+      }
+      index[keyPrefix + 'FileName'] = fileName;
+      index[keyPrefix + 'Header'] = (data[1] || '').trim();
+
+      i++;
+    }
+  }
+
+  // Parses a hunk
+  // This assumes that we are at the start of a hunk.
+  function parseHunk() {
+    let chunkHeaderIndex = i,
+        chunkHeaderLine = diffstr[i++],
+        chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
+
+    let hunk = {
+      oldStart: +chunkHeader[1],
+      oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2],
+      newStart: +chunkHeader[3],
+      newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4],
+      lines: [],
+      linedelimiters: []
+    };
+
+    // Unified Diff Format quirk: If the chunk size is 0,
+    // the first number is one lower than one would expect.
+    // https://www.artima.com/weblogs/viewpost.jsp?thread=164293
+    if (hunk.oldLines === 0) {
+      hunk.oldStart += 1;
+    }
+    if (hunk.newLines === 0) {
+      hunk.newStart += 1;
+    }
+
+    let addCount = 0,
+        removeCount = 0;
+    for (; i < diffstr.length; i++) {
+      // Lines starting with '---' could be mistaken for the "remove line" operation
+      // But they could be the header for the next file. Therefore prune such cases out.
+      if (diffstr[i].indexOf('--- ') === 0
+            && (i + 2 < diffstr.length)
+            && diffstr[i + 1].indexOf('+++ ') === 0
+            && diffstr[i + 2].indexOf('@@') === 0) {
+          break;
+      }
+      let operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0];
+
+      if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') {
+        hunk.lines.push(diffstr[i]);
+        hunk.linedelimiters.push(delimiters[i] || '\n');
+
+        if (operation === '+') {
+          addCount++;
+        } else if (operation === '-') {
+          removeCount++;
+        } else if (operation === ' ') {
+          addCount++;
+          removeCount++;
+        }
+      } else {
+        break;
+      }
+    }
+
+    // Handle the empty block count case
+    if (!addCount && hunk.newLines === 1) {
+      hunk.newLines = 0;
+    }
+    if (!removeCount && hunk.oldLines === 1) {
+      hunk.oldLines = 0;
+    }
+
+    // Perform optional sanity checking
+    if (options.strict) {
+      if (addCount !== hunk.newLines) {
+        throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
+      }
+      if (removeCount !== hunk.oldLines) {
+        throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
+      }
+    }
+
+    return hunk;
+  }
+
+  while (i < diffstr.length) {
+    parseIndex();
+  }
+
+  return list;
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/release-notes.md b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/release-notes.md
new file mode 100644
index 000000000..acc75aa83
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/release-notes.md
@@ -0,0 +1,303 @@
+# Release Notes
+
+## Development
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v5.0.0...master)
+
+## v5.0.0
+
+- Breaking: UMD export renamed from `JsDiff` to `Diff`.
+- Breaking: Newlines separated into separate tokens for word diff.
+- Breaking: Unified diffs now match ["quirks"](https://www.artima.com/weblogs/viewpost.jsp?thread=164293)
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v4.0.1...v5.0.0)
+
+## v4.0.1 - January 6th, 2019
+
+- Fix main reference path - b826104
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v4.0.0...v4.0.1)
+
+## v4.0.0 - January 5th, 2019
+
+- [#94](https://github.com/kpdecker/jsdiff/issues/94) - Missing "No newline at end of file" when comparing two texts that do not end in newlines ([@federicotdn](https://api.github.com/users/federicotdn))
+- [#227](https://github.com/kpdecker/jsdiff/issues/227) - Licence
+- [#199](https://github.com/kpdecker/jsdiff/issues/199) - Import statement for jsdiff
+- [#159](https://github.com/kpdecker/jsdiff/issues/159) - applyPatch affecting wrong line number with with new lines
+- [#8](https://github.com/kpdecker/jsdiff/issues/8) - A new state "replace"
+- Drop ie9 from karma targets - 79c31bd
+- Upgrade deps. Convert from webpack to rollup - 2c1a29c
+- Make ()[]"' as word boundaries between each other - f27b899
+- jsdiff: Replaced phantomJS by chrome - ec3114e
+- Add yarn.lock to .npmignore - 29466d8
+
+Compatibility notes:
+
+- Bower and Component packages no longer supported
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v3.5.0...v4.0.0)
+
+## v3.5.0 - March 4th, 2018
+
+- Omit redundant slice in join method of diffArrays - 1023590
+- Support patches with empty lines - fb0f208
+- Accept a custom JSON replacer function for JSON diffing - 69c7f0a
+- Optimize parch header parser - 2aec429
+- Fix typos - e89c832
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v3.4.0...v3.5.0)
+
+## v3.4.0 - October 7th, 2017
+
+- [#183](https://github.com/kpdecker/jsdiff/issues/183) - Feature request: ability to specify a custom equality checker for `diffArrays`
+- [#173](https://github.com/kpdecker/jsdiff/issues/173) - Bug: diffArrays gives wrong result on array of booleans
+- [#158](https://github.com/kpdecker/jsdiff/issues/158) - diffArrays will not compare the empty string in array?
+- comparator for custom equality checks - 30e141e
+- count oldLines and newLines when there are conflicts - 53bf384
+- Fix: diffArrays can compare falsey items - 9e24284
+- Docs: Replace grunt with npm test - 00e2f94
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v3.3.1...v3.4.0)
+
+## v3.3.1 - September 3rd, 2017
+
+- [#141](https://github.com/kpdecker/jsdiff/issues/141) - Cannot apply patch because my file delimiter is "/r/n" instead of "/n"
+- [#192](https://github.com/kpdecker/jsdiff/pull/192) - Fix: Bad merge when adding new files (#189)
+- correct spelling mistake - 21fa478
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v3.3.0...v3.3.1)
+
+## v3.3.0 - July 5th, 2017
+
+- [#114](https://github.com/kpdecker/jsdiff/issues/114) - /patch/merge not exported
+- Gracefully accept invalid newStart in hunks, same as patch(1) does. - d8a3635
+- Use regex rather than starts/ends with for parsePatch - 6cab62c
+- Add browser flag - e64f674
+- refactor: simplified code a bit more - 8f8e0f2
+- refactor: simplified code a bit - b094a6f
+- fix: some corrections re ignoreCase option - 3c78fd0
+- ignoreCase option - 3cbfbb5
+- Sanitize filename while parsing patches - 2fe8129
+- Added better installation methods - aced50b
+- Simple export of functionality - 8690f31
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v3.2.0...v3.3.0)
+
+## v3.2.0 - December 26th, 2016
+
+- [#156](https://github.com/kpdecker/jsdiff/pull/156) - Add `undefinedReplacement` option to `diffJson` ([@ewnd9](https://api.github.com/users/ewnd9))
+- [#154](https://github.com/kpdecker/jsdiff/pull/154) - Add `examples` and `images` to `.npmignore`. ([@wtgtybhertgeghgtwtg](https://api.github.com/users/wtgtybhertgeghgtwtg))
+- [#153](https://github.com/kpdecker/jsdiff/pull/153) - feat(structuredPatch): Pass options to diffLines ([@Kiougar](https://api.github.com/users/Kiougar))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v3.1.0...v3.2.0)
+
+## v3.1.0 - November 27th, 2016
+
+- [#146](https://github.com/kpdecker/jsdiff/pull/146) - JsDiff.diffArrays to compare arrays ([@wvanderdeijl](https://api.github.com/users/wvanderdeijl))
+- [#144](https://github.com/kpdecker/jsdiff/pull/144) - Split file using all possible line delimiter instead of hard-coded "/n" and join lines back using the original delimiters ([@soulbeing](https://api.github.com/users/soulbeing))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v3.0.1...v3.1.0)
+
+## v3.0.1 - October 9th, 2016
+
+- [#139](https://github.com/kpdecker/jsdiff/pull/139) - Make README.md look nicer in npmjs.com ([@takenspc](https://api.github.com/users/takenspc))
+- [#135](https://github.com/kpdecker/jsdiff/issues/135) - parsePatch combines patches from multiple files into a single IUniDiff when there is no "Index" line ([@ramya-rao-a](https://api.github.com/users/ramya-rao-a))
+- [#124](https://github.com/kpdecker/jsdiff/issues/124) - IE7/IE8 failure since 2.0.0 ([@boneskull](https://api.github.com/users/boneskull))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v3.0.0...v3.0.1)
+
+## v3.0.0 - August 23rd, 2016
+
+- [#130](https://github.com/kpdecker/jsdiff/pull/130) - Add callback argument to applyPatches `patched` option ([@piranna](https://api.github.com/users/piranna))
+- [#120](https://github.com/kpdecker/jsdiff/pull/120) - Correctly handle file names containing spaces ([@adius](https://api.github.com/users/adius))
+- [#119](https://github.com/kpdecker/jsdiff/pull/119) - Do single reflow ([@wifiextender](https://api.github.com/users/wifiextender))
+- [#117](https://github.com/kpdecker/jsdiff/pull/117) - Make more usable with long strings. ([@abnbgist](https://api.github.com/users/abnbgist))
+
+Compatibility notes:
+
+- applyPatches patch callback now is async and requires the callback be called to continue operation
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.3...v3.0.0)
+
+## v2.2.3 - May 31st, 2016
+
+- [#118](https://github.com/kpdecker/jsdiff/pull/118) - Add a fix for applying 0-length destination patches ([@chaaz](https://api.github.com/users/chaaz))
+- [#115](https://github.com/kpdecker/jsdiff/pull/115) - Fixed grammar in README ([@krizalys](https://api.github.com/users/krizalys))
+- [#113](https://github.com/kpdecker/jsdiff/pull/113) - fix typo ([@vmazare](https://api.github.com/users/vmazare))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.2...v2.2.3)
+
+## v2.2.2 - March 13th, 2016
+
+- [#102](https://github.com/kpdecker/jsdiff/issues/102) - diffJson with dates, returns empty curly braces ([@dr-dimitru](https://api.github.com/users/dr-dimitru))
+- [#97](https://github.com/kpdecker/jsdiff/issues/97) - Whitespaces & diffWords ([@faiwer](https://api.github.com/users/faiwer))
+- [#92](https://github.com/kpdecker/jsdiff/pull/92) - Fixes typo in the readme ([@bg451](https://api.github.com/users/bg451))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.1...v2.2.2)
+
+## v2.2.1 - November 12th, 2015
+
+- [#89](https://github.com/kpdecker/jsdiff/pull/89) - add in display selector to readme ([@FranDias](https://api.github.com/users/FranDias))
+- [#88](https://github.com/kpdecker/jsdiff/pull/88) - Split diffs based on file headers instead of 'Index:' metadata ([@piranna](https://api.github.com/users/piranna))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.0...v2.2.1)
+
+## v2.2.0 - October 29th, 2015
+
+- [#80](https://github.com/kpdecker/jsdiff/pull/80) - Fix a typo: applyPath -> applyPatch ([@fluxxu](https://api.github.com/users/fluxxu))
+- [#83](https://github.com/kpdecker/jsdiff/pull/83) - Add basic fuzzy matching to applyPatch ([@piranna](https://github.com/piranna))
+  [Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.0...v2.2.0)
+
+## v2.2.0 - October 29th, 2015
+
+- [#80](https://github.com/kpdecker/jsdiff/pull/80) - Fix a typo: applyPath -> applyPatch ([@fluxxu](https://api.github.com/users/fluxxu))
+- [#83](https://github.com/kpdecker/jsdiff/pull/83) - Add basic fuzzy matching to applyPatch ([@piranna](https://github.com/piranna))
+  [Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.3...v2.2.0)
+
+## v2.1.3 - September 30th, 2015
+
+- [#78](https://github.com/kpdecker/jsdiff/pull/78) - fix: error throwing when apply patch to empty string ([@21paradox](https://api.github.com/users/21paradox))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.2...v2.1.3)
+
+## v2.1.2 - September 23rd, 2015
+
+- [#76](https://github.com/kpdecker/jsdiff/issues/76) - diff headers give error ([@piranna](https://api.github.com/users/piranna))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.1...v2.1.2)
+
+## v2.1.1 - September 9th, 2015
+
+- [#73](https://github.com/kpdecker/jsdiff/issues/73) - Is applyPatches() exposed in the API? ([@davidparsson](https://api.github.com/users/davidparsson))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v2.1.0...v2.1.1)
+
+## v2.1.0 - August 27th, 2015
+
+- [#72](https://github.com/kpdecker/jsdiff/issues/72) - Consider using options object API for flag permutations ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#70](https://github.com/kpdecker/jsdiff/issues/70) - diffWords treats \n at the end as significant whitespace ([@nesQuick](https://api.github.com/users/nesQuick))
+- [#69](https://github.com/kpdecker/jsdiff/issues/69) - Missing count ([@wfalkwallace](https://api.github.com/users/wfalkwallace))
+- [#68](https://github.com/kpdecker/jsdiff/issues/68) - diffLines seems broken ([@wfalkwallace](https://api.github.com/users/wfalkwallace))
+- [#60](https://github.com/kpdecker/jsdiff/issues/60) - Support multiple diff hunks ([@piranna](https://api.github.com/users/piranna))
+- [#54](https://github.com/kpdecker/jsdiff/issues/54) - Feature Request: 3-way merge ([@mog422](https://api.github.com/users/mog422))
+- [#42](https://github.com/kpdecker/jsdiff/issues/42) - Fuzz factor for applyPatch ([@stuartpb](https://api.github.com/users/stuartpb))
+- Move whitespace ignore out of equals method - 542063c
+- Include source maps in babel output - 7f7ab21
+- Merge diff/line and diff/patch implementations - 1597705
+- Drop map utility method - 1ddc939
+- Documentation for parsePatch and applyPatches - 27c4b77
+
+Compatibility notes:
+
+- The undocumented ignoreWhitespace flag has been removed from the Diff equality check directly. This implementation may be copied to diff utilities if dependencies existed on this functionality.
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v2.0.2...v2.1.0)
+
+## v2.0.2 - August 8th, 2015
+
+- [#67](https://github.com/kpdecker/jsdiff/issues/67) - cannot require from npm module in node ([@commenthol](https://api.github.com/users/commenthol))
+- Convert to chai since we don’t support IE8 - a96bbad
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v2.0.1...v2.0.2)
+
+## v2.0.1 - August 7th, 2015
+
+- Add release build at proper step - 57542fd
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v2.0.0...v2.0.1)
+
+## v2.0.0 - August 7th, 2015
+
+- [#66](https://github.com/kpdecker/jsdiff/issues/66) - Add karma and sauce tests ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#65](https://github.com/kpdecker/jsdiff/issues/65) - Create component repository for bower ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#64](https://github.com/kpdecker/jsdiff/issues/64) - Automatically call removeEmpty for all tokenizer calls ([@kpdecker](https://api.github.com/users/kpdecker))
+- [#62](https://github.com/kpdecker/jsdiff/pull/62) - Allow access to structured object representation of patch data ([@bittrance](https://api.github.com/users/bittrance))
+- [#61](https://github.com/kpdecker/jsdiff/pull/61) - Use svg instead of png to get better image quality ([@PeterDaveHello](https://api.github.com/users/PeterDaveHello))
+- [#29](https://github.com/kpdecker/jsdiff/issues/29) - word tokenizer works only for 7 bit ascii ([@plasmagunman](https://api.github.com/users/plasmagunman))
+
+Compatibility notes:
+
+- `this.removeEmpty` is now called automatically for all instances. If this is not desired, this may be overridden on a per instance basis.
+- The library has been refactored to use some ES6 features. The external APIs should remain the same, but bower projects that directly referenced the repository will now have to point to the [components/jsdiff](https://github.com/components/jsdiff) repository.
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v1.4.0...v2.0.0)
+
+## v1.4.0 - May 6th, 2015
+
+- [#57](https://github.com/kpdecker/jsdiff/issues/57) - createPatch -> applyPatch failed. ([@mog422](https://api.github.com/users/mog422))
+- [#56](https://github.com/kpdecker/jsdiff/pull/56) - Two files patch ([@rgeissert](https://api.github.com/users/rgeissert))
+- [#14](https://github.com/kpdecker/jsdiff/issues/14) - Flip added and removed order? ([@jakesandlund](https://api.github.com/users/jakesandlund))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v1.3.2...v1.4.0)
+
+## v1.3.2 - March 30th, 2015
+
+- [#53](https://github.com/kpdecker/jsdiff/pull/53) - Updated README.MD with Bower installation instructions ([@ofbriggs](https://api.github.com/users/ofbriggs))
+- [#49](https://github.com/kpdecker/jsdiff/issues/49) - Cannot read property 'oldlines' of undefined ([@nwtn](https://api.github.com/users/nwtn))
+- [#44](https://github.com/kpdecker/jsdiff/issues/44) - invalid-meta jsdiff is missing "main" entry in bower.json
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v1.3.1...v1.3.2)
+
+## v1.3.1 - March 13th, 2015
+
+- [#52](https://github.com/kpdecker/jsdiff/pull/52) - Fix for #51 Wrong result of JsDiff.diffLines ([@felicienfrancois](https://api.github.com/users/felicienfrancois))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v1.3.0...v1.3.1)
+
+## v1.3.0 - March 2nd, 2015
+
+- [#47](https://github.com/kpdecker/jsdiff/pull/47) - Adding Diff Trimmed Lines ([@JamesGould123](https://api.github.com/users/JamesGould123))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v1.2.2...v1.3.0)
+
+## v1.2.2 - January 26th, 2015
+
+- [#45](https://github.com/kpdecker/jsdiff/pull/45) - Fix AMD module loading ([@pedrocarrico](https://api.github.com/users/pedrocarrico))
+- [#43](https://github.com/kpdecker/jsdiff/pull/43) - added a bower file ([@nbrustein](https://api.github.com/users/nbrustein))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v1.2.1...v1.2.2)
+
+## v1.2.1 - December 26th, 2014
+
+- [#41](https://github.com/kpdecker/jsdiff/pull/41) - change condition of using node export system. ([@ironhee](https://api.github.com/users/ironhee))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v1.2.0...v1.2.1)
+
+## v1.2.0 - November 29th, 2014
+
+- [#37](https://github.com/kpdecker/jsdiff/pull/37) - Add support for sentences. ([@vmariano](https://api.github.com/users/vmariano))
+- [#28](https://github.com/kpdecker/jsdiff/pull/28) - Implemented diffJson ([@papandreou](https://api.github.com/users/papandreou))
+- [#27](https://github.com/kpdecker/jsdiff/issues/27) - Slow to execute over diffs with a large number of changes ([@termi](https://api.github.com/users/termi))
+- Allow for optional async diffing - 19385b9
+- Fix diffChars implementation - eaa44ed
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v1.1.0...v1.2.0)
+
+## v1.1.0 - November 25th, 2014
+
+- [#33](https://github.com/kpdecker/jsdiff/pull/33) - AMD and global exports ([@ovcharik](https://api.github.com/users/ovcharik))
+- [#32](https://github.com/kpdecker/jsdiff/pull/32) - Add support for component ([@vmariano](https://api.github.com/users/vmariano))
+- [#31](https://github.com/kpdecker/jsdiff/pull/31) - Don't rely on Array.prototype.map ([@papandreou](https://api.github.com/users/papandreou))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v1.0.8...v1.1.0)
+
+## v1.0.8 - December 22nd, 2013
+
+- [#24](https://github.com/kpdecker/jsdiff/pull/24) - Handle windows newlines on non windows machines. ([@benogle](https://api.github.com/users/benogle))
+- [#23](https://github.com/kpdecker/jsdiff/pull/23) - Prettied up the API formatting a little, and added basic node and web examples ([@airportyh](https://api.github.com/users/airportyh))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v1.0.7...v1.0.8)
+
+## v1.0.7 - September 11th, 2013
+
+- [#22](https://github.com/kpdecker/jsdiff/pull/22) - Added variant of WordDiff that doesn't ignore whitespace differences ([@papandreou](https://api.github.com/users/papandreou)
+
+- Add 0.10 to travis tests - 243a526
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v1.0.6...v1.0.7)
+
+## v1.0.6 - August 30th, 2013
+
+- [#19](https://github.com/kpdecker/jsdiff/pull/19) - Explicitly define contents of npm package ([@sindresorhus](https://api.github.com/users/sindresorhus)
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v1.0.5...v1.0.6)
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/util/array.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/util/array.js
new file mode 100644
index 000000000..5de3cab9d
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/util/array.js
@@ -0,0 +1,21 @@
+export function arrayEqual(a, b) {
+  if (a.length !== b.length) {
+    return false;
+  }
+
+  return arrayStartsWith(a, b);
+}
+
+export function arrayStartsWith(array, start) {
+  if (start.length > array.length) {
+    return false;
+  }
+
+  for (let i = 0; i < start.length; i++) {
+    if (start[i] !== array[i]) {
+      return false;
+    }
+  }
+
+  return true;
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/util/distance-iterator.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/util/distance-iterator.js
new file mode 100644
index 000000000..73be1f80c
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/util/distance-iterator.js
@@ -0,0 +1,45 @@
+// Iterator that traverses in the range of [min, max], stepping
+// by distance from a given start position. I.e. for [0, 4], with
+// start of 2, this will iterate 2, 3, 1, 4, 0.
+export default function(start, minLine, maxLine) {
+  let wantForward = true,
+      backwardExhausted = false,
+      forwardExhausted = false,
+      localOffset = 1;
+
+  return function iterator() {
+    if (wantForward && !forwardExhausted) {
+      if (backwardExhausted) {
+        localOffset++;
+      } else {
+        wantForward = false;
+      }
+
+      // Check if trying to fit beyond text length, and if not, check it fits
+      // after offset location (or desired location on first iteration)
+      if (start + localOffset <= maxLine) {
+        return localOffset;
+      }
+
+      forwardExhausted = true;
+    }
+
+    if (!backwardExhausted) {
+      if (!forwardExhausted) {
+        wantForward = true;
+      }
+
+      // Check if trying to fit before text beginning, and if not, check it fits
+      // before offset location
+      if (minLine <= start - localOffset) {
+        return -localOffset++;
+      }
+
+      backwardExhausted = true;
+      return iterator();
+    }
+
+    // We tried to fit hunk before text beginning and beyond text length, then
+    // hunk can't fit on the text. Return undefined
+  };
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/util/params.js b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/util/params.js
new file mode 100644
index 000000000..07e03e59e
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/util/params.js
@@ -0,0 +1,13 @@
+export function generateOptions(options, defaults) {
+  if (typeof options === 'function') {
+    defaults.callback = options;
+  } else if (options) {
+    for (let name in options) {
+      /* istanbul ignore else */
+      if (options.hasOwnProperty(name)) {
+        defaults[name] = options[name];
+      }
+    }
+  }
+  return defaults;
+}
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/loader/BinaryToTextureCache.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/loader/BinaryToTextureCache.d.ts
new file mode 100644
index 000000000..c2298b7e5
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/loader/BinaryToTextureCache.d.ts
@@ -0,0 +1,8 @@
+export default BinaryToTextureCache;
+
+declare function BinaryToTextureCache(
+    scene: Phaser.Scene,
+    buffer: Uint8Array | string,
+    textureKey: string,
+    imageType?: string
+): void;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/loader/BinaryToTextureCache.js b/ui/src/phaser3-rex-plugins/plugins/utils/loader/BinaryToTextureCache.js
new file mode 100644
index 000000000..8f2f99d41
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/loader/BinaryToTextureCache.js
@@ -0,0 +1,22 @@
+var BinaryToTextureCache = function (scene, buffer, textureKey, imageType) {
+    if (typeof (buffer) === 'string') {
+        buffer = scene.cache.binary.get(buffer);
+    }
+
+    if (!buffer) {
+        return;
+    }
+
+    // png, jpeg, webp
+    if (imageType === undefined) {
+        imageType = 'png';
+    }
+
+    var blob = new Blob([buffer], { type: `image/${imageType}` });
+    var url = window.URL.createObjectURL(blob);
+    scene.load.image(textureKey, url);
+
+    // url Will be removed in loader
+}
+
+export default BinaryToTextureCache;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/loader/FileObjectToCache.js b/ui/src/phaser3-rex-plugins/plugins/utils/loader/FileObjectToCache.js
new file mode 100644
index 000000000..70bd1f141
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/loader/FileObjectToCache.js
@@ -0,0 +1,36 @@
+import GetCache from '../system/GetCache.js';
+import IsFunction from '../object/IsFunction.js';
+
+var FileObjectToCache = function (scene, file, loaderType, key, cacheType, onComplete) {
+    // Remove data from cache
+    if ((cacheType === null) || (cacheType === false)) {
+
+    } else if (IsFunction(cacheType)) {
+        cacheType();
+    } else {
+        var cache = GetCache(scene, loaderType, cacheType);
+        if (cache.exists(key)) {
+            cache.remove(key);
+        }
+    }
+
+    // Add filecomplete event
+    var loader = scene.load;
+    if (onComplete) {
+        loader.once(`filecomplete-${loaderType}-${key}`, function (key, type, data) {
+            onComplete(data);
+        })
+    }
+
+    // Load file from url
+    if (IsFunction(file)) {
+        file();
+    } else {
+        var url = window.URL.createObjectURL(file);
+        loader[loaderType](key, url);
+    }
+
+    loader.start();
+}
+
+export default FileObjectToCache;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadBinaryAndImage.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadBinaryAndImage.d.ts
new file mode 100644
index 000000000..d5eaf16b0
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadBinaryAndImage.d.ts
@@ -0,0 +1,20 @@
+export default LoadBinaryAndImage;
+
+declare namespace LoadBinaryAndImage {
+    type OnLoadBinaryCallbackType = (buffer: Uint8Array) => Uint8Array
+}
+
+declare function LoadBinaryAndImage(
+    scene: Phaser.Scene,
+    key: string,
+    url: string,
+    imageType?: string,
+    onLoadBinary?: LoadBinaryAndImage.OnLoadBinaryCallbackType
+): void;
+
+declare function LoadBinaryAndImage(
+    scene: Phaser.Scene,
+    key: string,
+    url: string,
+    onLoadBinary?: LoadBinaryAndImage.OnLoadBinaryCallbackType
+): void;
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadBinaryAndImage.js b/ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadBinaryAndImage.js
new file mode 100644
index 000000000..56464d78d
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadBinaryAndImage.js
@@ -0,0 +1,26 @@
+var LoadBinaryAndImage = function (scene, key, url, imageType, onLoadBinary) {
+    if (typeof (imageType) === 'function') {
+        onLoadBinary = imageType;
+        imageType = undefined;
+    }
+
+    // png, jpeg, webp
+    if (imageType === undefined) {
+        var slices = url.split('.')
+        imageType = slices[slices.length - 1];
+    }
+
+    scene.load.binary(key, url, Uint8Array);
+    scene.load.once(`filecomplete-binary-${key}`, function () {
+        var buffer = scene.cache.binary.get(key);
+        if (onLoadBinary) {
+            buffer = onLoadBinary(buffer);
+        }
+
+        var blob = new Blob([buffer], { type: `image/${imageType}` });
+        url = window.URL.createObjectURL(blob);
+        scene.load.image(key, url);
+    }, this);
+}
+
+export default LoadBinaryAndImage;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadScript.js b/ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadScript.js
new file mode 100644
index 000000000..f4fe03e1e
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadScript.js
@@ -0,0 +1,21 @@
+var LoadScript = function (url, onload) {
+    var scripts = document.getElementsByTagName('script');
+    for (var i = 0, cnt = scripts.length; i < cnt; i++) {
+        if (scripts[i].src.indexOf(url) != -1) {
+            if (onload) {
+                onload();
+            }
+            return;
+        }
+    }
+
+    var newScriptTag = document.createElement('script');
+    newScriptTag.setAttribute('src', url);
+
+    if (onload) {
+        newScriptTag.onload = onload;
+    }
+
+    document.head.appendChild(newScriptTag);
+};
+export default LoadScript;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadScriptPromise.js b/ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadScriptPromise.js
new file mode 100644
index 000000000..bc6b86208
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadScriptPromise.js
@@ -0,0 +1,9 @@
+import LoadScript from './LoadScript.js';
+
+var LoadScriptPromise = function (url) {
+    return new Promise(function (resolve, reject) {
+        LoadScript(url, resolve);
+    });
+};
+
+export default LoadScriptPromise;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/lokijs/SerializeMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/lokijs/SerializeMethods.js
new file mode 100644
index 000000000..3b0389040
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/lokijs/SerializeMethods.js
@@ -0,0 +1,27 @@
+import LZString from '../lzstring/lz-string.min.js';
+
+// Serialize then compress
+var Serialize = function (db, compress) {
+    if ((compress === undefined) || (compress === true)) {
+        compress = 'compress';
+    }
+    var s = db.serialize();
+    if (compress) {
+        s = LZString[compress](s);
+    }
+    return s;
+}
+
+// Decompress then deserialize, load into db
+var Deserialize = function (db, s, decompress) {
+    if ((decompress === undefined) || (decompress === true)) {
+        decompress = 'decompress';
+    }
+    if (decompress) {
+        s = LZString[decompress](s);
+    }
+    db.loadJSON(s);
+    return db;
+}
+
+export { Serialize, Deserialize };
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/lokijs/loki-indexed-adapter.js b/ui/src/phaser3-rex-plugins/plugins/utils/lokijs/loki-indexed-adapter.js
new file mode 100644
index 000000000..b0c94eb1e
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/lokijs/loki-indexed-adapter.js
@@ -0,0 +1,617 @@
+/*
+  Loki IndexedDb Adapter (need to include this script to use it)
+
+  Console Usage can be used for management/diagnostic, here are a few examples :
+  adapter.getDatabaseList(); // with no callback passed, this method will log results to console
+  adapter.saveDatabase('UserDatabase', JSON.stringify(myDb));
+  adapter.loadDatabase('UserDatabase'); // will log the serialized db to console
+  adapter.deleteDatabase('UserDatabase');
+*/
+
+(function (root, factory) {
+    if (typeof define === 'function' && define.amd) {
+        // AMD
+        define([], factory);
+    } else if (typeof exports === 'object') {
+        // Node, CommonJS-like
+        module.exports = factory();
+    } else {
+        // Browser globals (root is window)
+        root.LokiIndexedAdapter = factory();
+    }
+}(this, function () {
+  return (function() {
+
+    /**
+     * Loki persistence adapter class for indexedDb.
+     *     This class fulfills abstract adapter interface which can be applied to other storage methods. 
+     *     Utilizes the included LokiCatalog app/key/value database for actual database persistence.
+     *     Indexeddb is highly async, but this adapter has been made 'console-friendly' as well.
+     *     Anywhere a callback is omitted, it should return results (if applicable) to console.
+     *     IndexedDb storage is provided per-domain, so we implement app/key/value database to 
+     *     allow separate contexts for separate apps within a domain.
+     *
+     * @example
+     * var idbAdapter = new LokiIndexedAdapter('finance');
+     *
+     * @constructor LokiIndexedAdapter
+     *
+     * @param {string} appname - (Optional) Application name context can be used to distinguish subdomains, 'loki' by default
+     */
+    function LokiIndexedAdapter(appname)
+    {
+      this.app = 'loki';
+
+      if (typeof (appname) !== 'undefined')
+      {
+        this.app = appname;
+      }
+
+      // keep reference to catalog class for base AKV operations
+      this.catalog = null;
+
+      if (!this.checkAvailability()) {
+        throw new Error('indexedDB does not seem to be supported for your environment');
+      }
+    }
+
+    /**
+     * Used to check if adapter is available
+     *
+     * @returns {boolean} true if indexeddb is available, false if not.
+     * @memberof LokiIndexedAdapter
+     */
+    LokiIndexedAdapter.prototype.checkAvailability = function()
+    {
+      if (typeof indexedDB !== 'undefined' && indexedDB) return true;
+
+      return false;
+    };
+
+    /**
+     * Retrieves a serialized db string from the catalog.
+     *
+     * @example
+     * // LOAD
+     * var idbAdapter = new LokiIndexedAdapter('finance');
+     * var db = new loki('test', { adapter: idbAdapter });
+     *   db.loadDatabase(function(result) {
+     *   console.log('done');
+     * });
+     *
+     * @param {string} dbname - the name of the database to retrieve.
+     * @param {function} callback - callback should accept string param containing serialized db string.
+     * @memberof LokiIndexedAdapter
+     */
+    LokiIndexedAdapter.prototype.loadDatabase = function(dbname, callback)
+    {
+      var appName = this.app;
+      var adapter = this;
+
+      // lazy open/create db reference so dont -need- callback in constructor
+      if (this.catalog === null || this.catalog.db === null) {
+        this.catalog = new LokiCatalog(function(cat) {
+          adapter.catalog = cat;
+
+          adapter.loadDatabase(dbname, callback);
+        });
+
+        return;
+      }
+
+      // lookup up db string in AKV db
+      this.catalog.getAppKey(appName, dbname, function(result) {
+        if (typeof (callback) === 'function') {
+          if (result.id === 0) {
+            callback(null);
+            return;
+          }
+          callback(result.val);
+        }
+        else {
+          // support console use of api
+          console.log(result.val);
+        }
+      });
+    };
+
+    // alias
+    LokiIndexedAdapter.prototype.loadKey = LokiIndexedAdapter.prototype.loadDatabase;
+
+    /**
+     * Saves a serialized db to the catalog.
+     *
+     * @example
+     * // SAVE : will save App/Key/Val as 'finance'/'test'/{serializedDb}
+     * var idbAdapter = new LokiIndexedAdapter('finance');
+     * var db = new loki('test', { adapter: idbAdapter });
+     * var coll = db.addCollection('testColl');
+     * coll.insert({test: 'val'});
+     * db.saveDatabase();  // could pass callback if needed for async complete
+     *
+     * @param {string} dbname - the name to give the serialized database within the catalog.
+     * @param {string} dbstring - the serialized db string to save.
+     * @param {function} callback - (Optional) callback passed obj.success with true or false
+     * @memberof LokiIndexedAdapter
+     */
+    LokiIndexedAdapter.prototype.saveDatabase = function(dbname, dbstring, callback)
+    {
+      var appName = this.app;
+      var adapter = this;
+
+      function saveCallback(result) {
+        if (result && result.success === true) {
+          callback(null);
+        }
+        else {
+          callback(new Error("Error saving database"));
+        }
+      }
+
+      // lazy open/create db reference so dont -need- callback in constructor
+      if (this.catalog === null || this.catalog.db === null) {
+        this.catalog = new LokiCatalog(function(cat) {
+          adapter.catalog = cat;
+
+          // now that catalog has been initialized, set (add/update) the AKV entry
+          cat.setAppKey(appName, dbname, dbstring, saveCallback);
+        });
+
+        return;
+      }
+
+      // set (add/update) entry to AKV database
+      this.catalog.setAppKey(appName, dbname, dbstring, saveCallback);
+    };
+
+    // alias
+    LokiIndexedAdapter.prototype.saveKey = LokiIndexedAdapter.prototype.saveDatabase;
+
+    /**
+     * Deletes a serialized db from the catalog.
+     *
+     * @example
+     * // DELETE DATABASE
+     * // delete 'finance'/'test' value from catalog
+     * idbAdapter.deleteDatabase('test', function {
+     *   // database deleted
+     * });
+     *
+     * @param {string} dbname - the name of the database to delete from the catalog.
+     * @param {function=} callback - (Optional) executed on database delete
+     * @memberof LokiIndexedAdapter
+     */
+    LokiIndexedAdapter.prototype.deleteDatabase = function(dbname, callback)
+    {
+      var appName = this.app;
+      var adapter = this;
+
+      // lazy open/create db reference and pass callback ahead
+      if (this.catalog === null || this.catalog.db === null) {
+        this.catalog = new LokiCatalog(function(cat) {
+          adapter.catalog = cat;
+
+          adapter.deleteDatabase(dbname, callback);
+        });
+
+        return;
+      }
+
+      // catalog was already initialized, so just lookup object and delete by id
+      this.catalog.getAppKey(appName, dbname, function(result) {
+        var id = result.id;
+
+        if (id !== 0) {
+          adapter.catalog.deleteAppKey(id, callback);
+        } else if (typeof (callback) === 'function') {
+          callback({ success: true });
+        }
+      });
+    };
+
+    // alias
+    LokiIndexedAdapter.prototype.deleteKey = LokiIndexedAdapter.prototype.deleteDatabase;
+
+    /**
+     * Removes all database partitions and pages with the base filename passed in.
+     * This utility method does not (yet) guarantee async deletions will be completed before returning
+     *
+     * @param {string} dbname - the base filename which container, partitions, or pages are derived
+     * @memberof LokiIndexedAdapter
+     */
+    LokiIndexedAdapter.prototype.deleteDatabasePartitions = function(dbname) {
+      var self=this;
+      this.getDatabaseList(function(result) {
+        result.forEach(function(str) {
+          if (str.startsWith(dbname)) {
+            self.deleteDatabase(str);
+          }
+        });
+      });
+    };
+
+    /**
+     * Retrieves object array of catalog entries for current app.
+     *
+     * @example
+     * idbAdapter.getDatabaseList(function(result) {
+     *   // result is array of string names for that appcontext ('finance')
+     *   result.forEach(function(str) {
+     *     console.log(str);
+     *   });
+     * });
+     *
+     * @param {function} callback - should accept array of database names in the catalog for current app.
+     * @memberof LokiIndexedAdapter
+     */
+    LokiIndexedAdapter.prototype.getDatabaseList = function(callback)
+    {
+      var appName = this.app;
+      var adapter = this;
+
+      // lazy open/create db reference so dont -need- callback in constructor
+      if (this.catalog === null || this.catalog.db === null) {
+        this.catalog = new LokiCatalog(function(cat) {
+          adapter.catalog = cat;
+
+          adapter.getDatabaseList(callback);
+        });
+
+        return;
+      }
+
+      // catalog already initialized
+      // get all keys for current appName, and transpose results so just string array
+      this.catalog.getAppKeys(appName, function(results) {
+        var names = [];
+
+        for(var idx = 0; idx < results.length; idx++) {
+          names.push(results[idx].key);
+        }
+
+        if (typeof (callback) === 'function') {
+          callback(names);
+        }
+        else {
+          names.forEach(function(obj) {
+            console.log(obj);
+          });
+        }
+      });
+    };
+
+    // alias
+    LokiIndexedAdapter.prototype.getKeyList = LokiIndexedAdapter.prototype.getDatabaseList;
+
+    /**
+     * Allows retrieval of list of all keys in catalog along with size
+     *
+     * @param {function} callback - (Optional) callback to accept result array.
+     * @memberof LokiIndexedAdapter
+     */
+    LokiIndexedAdapter.prototype.getCatalogSummary = function(callback)
+    {
+      var appName = this.app;
+      var adapter = this;
+
+      // lazy open/create db reference
+      if (this.catalog === null || this.catalog.db === null) {
+        this.catalog = new LokiCatalog(function(cat) {
+          adapter.catalog = cat;
+
+          adapter.getCatalogSummary(callback);
+        });
+
+        return;
+      }
+
+      // catalog already initialized
+      // get all keys for current appName, and transpose results so just string array
+      this.catalog.getAllKeys(function(results) {
+        var entries = [];
+        var obj,
+          size,
+          oapp,
+          okey,
+          oval;
+
+        for(var idx = 0; idx < results.length; idx++) {
+          obj = results[idx];
+          oapp = obj.app || '';
+          okey = obj.key || '';
+          oval = obj.val || '';
+
+          // app and key are composited into an appkey column so we will mult by 2
+          size = oapp.length * 2 + okey.length * 2 + oval.length + 1;
+
+          entries.push({ "app": obj.app, "key": obj.key, "size": size });
+        }
+
+        if (typeof (callback) === 'function') {
+          callback(entries);
+        }
+        else {
+          entries.forEach(function(obj) {
+            console.log(obj);
+          });
+        }
+      });
+    };
+
+    /**
+     * LokiCatalog - underlying App/Key/Value catalog persistence
+     *    This non-interface class implements the actual persistence.
+     *    Used by the IndexedAdapter class.
+     */
+    function LokiCatalog(callback)
+    {
+      this.db = null;
+      this.initializeLokiCatalog(callback);
+    }
+
+    LokiCatalog.prototype.initializeLokiCatalog = function(callback) {
+      var openRequest = indexedDB.open('LokiCatalog', 1);
+      var cat = this;
+
+      // If database doesn't exist yet or its version is lower than our version specified above (2nd param in line above)
+      openRequest.onupgradeneeded = function(e) {
+        var thisDB = e.target.result;
+        if (thisDB.objectStoreNames.contains('LokiAKV')) {
+          thisDB.deleteObjectStore('LokiAKV');
+        }
+
+        if(!thisDB.objectStoreNames.contains('LokiAKV')) {
+          var objectStore = thisDB.createObjectStore('LokiAKV', { keyPath: 'id', autoIncrement:true });
+          objectStore.createIndex('app', 'app', {unique:false});
+          objectStore.createIndex('key', 'key', {unique:false});
+          // hack to simulate composite key since overhead is low (main size should be in val field)
+          // user (me) required to duplicate the app and key into comma delimited appkey field off object
+          // This will allow retrieving single record with that composite key as well as
+          // still supporting opening cursors on app or key alone
+          objectStore.createIndex('appkey', 'appkey', {unique:true});
+        }
+      };
+
+      openRequest.onsuccess = function(e) {
+        cat.db = e.target.result;
+
+        if (typeof (callback) === 'function') callback(cat);
+      };
+
+      openRequest.onerror = function(e) {
+        throw e;
+      };
+    };
+
+    LokiCatalog.prototype.getAppKey = function(app, key, callback) {
+      var transaction = this.db.transaction(['LokiAKV'], 'readonly');
+      var store = transaction.objectStore('LokiAKV');
+      var index = store.index('appkey');
+      var appkey = app + "," + key;
+      var request = index.get(appkey);
+
+      request.onsuccess = (function(usercallback) {
+        return function(e) {
+          var lres = e.target.result;
+
+          if (lres === null || typeof(lres) === 'undefined') {
+            lres = {
+              id: 0,
+              success: false
+            };
+          }
+
+          if (typeof(usercallback) === 'function') {
+            usercallback(lres);
+          }
+          else {
+            console.log(lres);
+          }
+        };
+      })(callback);
+
+      request.onerror = (function(usercallback) {
+        return function(e) {
+          if (typeof(usercallback) === 'function') {
+            usercallback({ id: 0, success: false });
+          }
+          else {
+            throw e;
+          }
+        };
+      })(callback);
+    };
+
+    LokiCatalog.prototype.getAppKeyById = function (id, callback, data) {
+      var transaction = this.db.transaction(['LokiAKV'], 'readonly');
+      var store = transaction.objectStore('LokiAKV');
+      var request = store.get(id);
+
+      request.onsuccess = (function(data, usercallback){
+        return function(e) {
+          if (typeof(usercallback) === 'function') {
+            usercallback(e.target.result, data);
+          }
+          else {
+            console.log(e.target.result);
+          }
+        };
+      })(data, callback);
+    };
+
+    LokiCatalog.prototype.setAppKey = function (app, key, val, callback) {
+      var transaction = this.db.transaction(['LokiAKV'], 'readwrite');
+      var store = transaction.objectStore('LokiAKV');
+      var index = store.index('appkey');
+      var appkey = app + "," + key;
+      var request = index.get(appkey);
+
+      // first try to retrieve an existing object by that key
+      // need to do this because to update an object you need to have id in object, otherwise it will append id with new autocounter and clash the unique index appkey
+      request.onsuccess = function(e) {
+        var res = e.target.result;
+
+        if (res === null || res === undefined) {
+          res = {
+            app:app,
+            key:key,
+            appkey: app + ',' + key,
+            val:val
+          };
+        }
+        else {
+          res.val = val;
+        }
+
+        var requestPut = store.put(res);
+
+        requestPut.onerror = (function(usercallback) {
+          return function(e) {
+            if (typeof(usercallback) === 'function') {
+              usercallback({ success: false });
+            }
+            else {
+              console.error('LokiCatalog.setAppKey (set) onerror');
+              console.error(request.error);
+            }
+          };
+
+        })(callback);
+
+        requestPut.onsuccess = (function(usercallback) {
+          return function(e) {
+            if (typeof(usercallback) === 'function') {
+              usercallback({ success: true });
+            }
+          };
+        })(callback);
+      };
+
+      request.onerror = (function(usercallback) {
+        return function(e) {
+          if (typeof(usercallback) === 'function') {
+            usercallback({ success: false });
+          }
+          else {
+            console.error('LokiCatalog.setAppKey (get) onerror');
+            console.error(request.error);
+          }
+        };
+      })(callback);
+    };
+
+    LokiCatalog.prototype.deleteAppKey = function (id, callback) {
+      var transaction = this.db.transaction(['LokiAKV'], 'readwrite');
+      var store = transaction.objectStore('LokiAKV');
+      var request = store.delete(id);
+
+      request.onsuccess = (function(usercallback) {
+        return function(evt) {
+          if (typeof(usercallback) === 'function') usercallback({ success: true });
+        };
+      })(callback);
+
+      request.onerror = (function(usercallback) {
+        return function(evt) {
+          if (typeof(usercallback) === 'function') {
+            usercallback({ success: false });
+          }
+          else {
+            console.error('LokiCatalog.deleteAppKey raised onerror');
+            console.error(request.error);
+          }
+        };
+      })(callback);
+    };
+
+    LokiCatalog.prototype.getAppKeys = function(app, callback) {
+      var transaction = this.db.transaction(['LokiAKV'], 'readonly');
+      var store = transaction.objectStore('LokiAKV');
+      var index = store.index('app');
+
+      // We want cursor to all values matching our (single) app param
+      var singleKeyRange = IDBKeyRange.only(app);
+
+      // To use one of the key ranges, pass it in as the first argument of openCursor()/openKeyCursor()
+      var cursor = index.openCursor(singleKeyRange);
+
+      // cursor internally, pushing results into this.data[] and return
+      // this.data[] when done (similar to service)
+      var localdata = [];
+
+      cursor.onsuccess = (function(data, callback) {
+        return function(e) {
+          var cursor = e.target.result;
+          if (cursor) {
+            var currObject = cursor.value;
+
+            data.push(currObject);
+
+            cursor.continue();
+          }
+          else {
+            if (typeof(callback) === 'function') {
+              callback(data);
+            }
+            else {
+              console.log(data);
+            }
+          }
+        };
+      })(localdata, callback);
+
+      cursor.onerror = (function(usercallback) {
+        return function(e) {
+          if (typeof(usercallback) === 'function') {
+            usercallback(null);
+          }
+          else {
+            console.error('LokiCatalog.getAppKeys raised onerror');
+            console.error(e);
+          }
+        };
+      })(callback);
+
+    };
+
+    // Hide 'cursoring' and return array of { id: id, key: key }
+    LokiCatalog.prototype.getAllKeys = function (callback) {
+      var transaction = this.db.transaction(['LokiAKV'], 'readonly');
+      var store = transaction.objectStore('LokiAKV');
+      var cursor = store.openCursor();
+
+      var localdata = [];
+
+      cursor.onsuccess = (function(data, callback) {
+        return function(e) {
+          var cursor = e.target.result;
+          if (cursor) {
+            var currObject = cursor.value;
+
+            data.push(currObject);
+
+            cursor.continue();
+          }
+          else {
+            if (typeof(callback) === 'function') {
+              callback(data);
+            }
+            else {
+              console.log(data);
+            }
+          }
+        };
+      })(localdata, callback);
+
+      cursor.onerror = (function(usercallback) {
+        return function(e) {
+          if (typeof(usercallback) === 'function') usercallback(null);
+        };
+      })(callback);
+
+    };
+
+    return LokiIndexedAdapter;
+
+  }());
+}));
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/lokijs/lokijs.min.js b/ui/src/phaser3-rex-plugins/plugins/utils/lokijs/lokijs.min.js
new file mode 100644
index 000000000..3fbed3d6c
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/lokijs/lokijs.min.js
@@ -0,0 +1,3 @@
+(function(root,factory){if(typeof define==="function"&&define.amd){define([],factory)}else if(typeof exports==="object"){module.exports=factory()}else{root.loki=factory()}})(this,function(){return function(){"use strict";var hasOwnProperty=Object.prototype.hasOwnProperty;var Utils={copyProperties:function(src,dest){var prop;for(prop in src){dest[prop]=src[prop]}},resolveTransformObject:function(subObj,params,depth){var prop,pname;if(typeof depth!=="number"){depth=0}if(++depth>=10)return subObj;for(prop in subObj){if(typeof subObj[prop]==="string"&&subObj[prop].indexOf("[%lktxp]")===0){pname=subObj[prop].substring(8);if(params.hasOwnProperty(pname)){subObj[prop]=params[pname]}}else if(typeof subObj[prop]==="object"){subObj[prop]=Utils.resolveTransformObject(subObj[prop],params,depth)}}return subObj},resolveTransformParams:function(transform,params){var idx,clonedStep,resolvedTransform=[];if(typeof params==="undefined")return transform;for(idx=0;idxcv2)return false;return equal}if(cv1===cv1&&cv2!==cv2){return true}if(cv2===cv2&&cv1!==cv1){return false}if(prop1prop2)return false;if(prop1==prop2)return equal;cv1=prop1.toString();cv2=prop2.toString();if(cv1t2}}cv1=Number(prop1);cv2=Number(prop2);if(cv1===cv1&&cv2===cv2){if(cv1>cv2)return true;if(cv1prop2)return true;if(prop1cv2){return true}if(cv1==cv2){return equal}return false}function sortHelper(prop1,prop2,desc){if(aeqHelper(prop1,prop2))return 0;if(ltHelper(prop1,prop2,false)){return desc?1:-1}if(gtHelper(prop1,prop2,false)){return desc?-1:1}return 0}function compoundeval(properties,obj1,obj2){var res=0;var prop,field,val1,val2,arr;for(var i=0,len=properties.length;i=paths.length){valueFound=fun(element,value)}else if(Array.isArray(element)){for(var index=0,len=element.length;indexb},$jgte:function(a,b){return a>=b},$jlt:function(a,b){return a=vals[0]&&a<=vals[1]},$in:function(a,b){return b.indexOf(a)!==-1},$nin:function(a,b){return b.indexOf(a)===-1},$keyin:function(a,b){return a in b},$nkeyin:function(a,b){return!(a in b)},$definedin:function(a,b){return b[a]!==undefined},$undefinedin:function(a,b){return b[a]===undefined},$regex:function(a,b){return b.test(a)},$containsString:function(a,b){return typeof a==="string"&&a.indexOf(b)!==-1},$containsNone:function(a,b){return!LokiOps.$containsAny(a,b)},$containsAny:function(a,b){var checkFn=containsCheckFn(a);if(checkFn!==null){return Array.isArray(b)?b.some(checkFn):checkFn(b)}return false},$contains:function(a,b){var checkFn=containsCheckFn(a);if(checkFn!==null){return Array.isArray(b)?b.every(checkFn):checkFn(b)}return false},$type:function(a,b){var type=typeof a;if(type==="object"){if(Array.isArray(a)){type="array"}else if(a instanceof Date){type="date"}}return typeof b!=="object"?type===b:doQueryOp(type,b)},$finite:function(a,b){return b===isFinite(a)},$size:function(a,b){if(Array.isArray(a)){return typeof b!=="object"?a.length===b:doQueryOp(a.length,b)}return false},$len:function(a,b){if(typeof a==="string"){return typeof b!=="object"?a.length===b:doQueryOp(a.length,b)}return false},$where:function(a,b){return b(a)===true},$not:function(a,b){return!doQueryOp(a,b)},$and:function(a,b){for(var idx=0,len=b.length;idx0){throw new Error("disableMeta option cannot be passed as true when ttl is enabled")}}for(i=0;i=0){return this.serializeCollection({delimited:options.delimited,delimiter:options.delimiter,collectionIndex:options.partition})}dbcopy=new Loki(this.filename);dbcopy.loadJSONObject(this);for(idx=0;idxcollCount){done=true}}else{currObject=JSON.parse(workarray[lineIndex]);cdb.collections[collIndex].data.push(currObject)}workarray[lineIndex++]=null}return cdb};Loki.prototype.deserializeCollection=function(destructuredSource,options){var workarray=[];var idx,len;options=options||{};if(!options.hasOwnProperty("partitioned")){options.partitioned=false}if(!options.hasOwnProperty("delimited")){options.delimited=true}if(!options.hasOwnProperty("delimiter")){options.delimiter=this.options.destructureDelimiter}if(options.delimited){workarray=destructuredSource.split(options.delimiter);workarray.pop()}else{workarray=destructuredSource}len=workarray.length;for(idx=0;idx=cdlen)doneWithPartition=true}if(pageLen>=this.options.pageSize)doneWithPage=true;if(!doneWithPage||doneWithPartition){pageBuilder+=this.options.delimiter;pageLen+=delimlen}if(doneWithPartition||doneWithPage){this.adapter.saveDatabase(keyname,pageBuilder,pageSaveCallback);return}}};function LokiFsAdapter(){this.fs=require("fs")}LokiFsAdapter.prototype.loadDatabase=function loadDatabase(dbname,callback){var self=this;this.fs.stat(dbname,function(err,stats){if(!err&&stats.isFile()){self.fs.readFile(dbname,{encoding:"utf8"},function readFileCallback(err,data){if(err){callback(new Error(err))}else{callback(data)}})}else{callback(null)}})};LokiFsAdapter.prototype.saveDatabase=function saveDatabase(dbname,dbstring,callback){var self=this;var tmpdbname=dbname+"~";this.fs.writeFile(tmpdbname,dbstring,function writeFileCallback(err){if(err){callback(new Error(err))}else{self.fs.rename(tmpdbname,dbname,callback)}})};LokiFsAdapter.prototype.deleteDatabase=function deleteDatabase(dbname,callback){this.fs.unlink(dbname,function deleteDatabaseCallback(err){if(err){callback(new Error(err))}else{callback()}})};function LokiLocalStorageAdapter(){}LokiLocalStorageAdapter.prototype.loadDatabase=function loadDatabase(dbname,callback){if(localStorageAvailable()){callback(localStorage.getItem(dbname))}else{callback(new Error("localStorage is not available"))}};LokiLocalStorageAdapter.prototype.saveDatabase=function saveDatabase(dbname,dbstring,callback){if(localStorageAvailable()){localStorage.setItem(dbname,dbstring);callback(null)}else{callback(new Error("localStorage is not available"))}};LokiLocalStorageAdapter.prototype.deleteDatabase=function deleteDatabase(dbname,callback){if(localStorageAvailable()){localStorage.removeItem(dbname);callback(null)}else{callback(new Error("localStorage is not available"))}}
+;Loki.prototype.throttledSaveDrain=function(callback,options){var self=this;var now=(new Date).getTime();if(!this.throttledSaves){callback(true)}options=options||{};if(!options.hasOwnProperty("recursiveWait")){options.recursiveWait=true}if(!options.hasOwnProperty("recursiveWaitLimit")){options.recursiveWaitLimit=false}if(!options.hasOwnProperty("recursiveWaitLimitDuration")){options.recursiveWaitLimitDuration=2e3}if(!options.hasOwnProperty("started")){options.started=(new Date).getTime()}if(this.throttledSaves&&this.throttledSavePending){if(options.recursiveWait){this.throttledCallbacks.push(function(){if(self.throttledSavePending){if(options.recursiveWaitLimit&&now-options.started>options.recursiveWaitLimitDuration){callback(false);return}self.throttledSaveDrain(callback,options);return}else{callback(true);return}})}else{this.throttledCallbacks.push(callback);return}}else{callback(true)}};Loki.prototype.loadDatabaseInternal=function(options,callback){var cFun=callback||function(err,data){if(err){throw err}},self=this;if(this.persistenceAdapter!==null){this.persistenceAdapter.loadDatabase(this.filename,function loadDatabaseCallback(dbString){if(typeof dbString==="string"){var parseSuccess=false;try{self.loadJSON(dbString,options||{});parseSuccess=true}catch(err){cFun(err)}if(parseSuccess){cFun(null);self.emit("loaded","database "+self.filename+" loaded")}}else{if(!dbString){cFun(null);self.emit("loaded","empty database "+self.filename+" loaded");return}if(dbString instanceof Error){cFun(dbString);return}if(typeof dbString==="object"){self.loadJSONObject(dbString,options||{});cFun(null);self.emit("loaded","database "+self.filename+" loaded");return}cFun("unexpected adapter response : "+dbString)}})}else{cFun(new Error("persistenceAdapter not configured"))}};Loki.prototype.loadDatabase=function(options,callback){var self=this;if(!this.throttledSaves){this.loadDatabaseInternal(options,callback);return}this.throttledSaveDrain(function(success){if(success){self.throttledSavePending=true;self.loadDatabaseInternal(options,function(err){if(self.throttledCallbacks.length===0){self.throttledSavePending=false}else{self.saveDatabase()}if(typeof callback==="function"){callback(err)}});return}else{if(typeof callback==="function"){callback(new Error("Unable to pause save throttling long enough to read database"))}}},options)};Loki.prototype.saveDatabaseInternal=function(callback){var cFun=callback||function(err){if(err){throw err}return},self=this;if(this.persistenceAdapter!==null){if(this.persistenceAdapter.mode==="reference"&&typeof this.persistenceAdapter.exportDatabase==="function"){this.persistenceAdapter.exportDatabase(this.filename,this.copy({removeNonSerializable:true}),function exportDatabaseCallback(err){self.autosaveClearFlags();cFun(err)})}else{self.autosaveClearFlags();this.persistenceAdapter.saveDatabase(this.filename,self.serialize(),function saveDatabasecallback(err){cFun(err)})}}else{cFun(new Error("persistenceAdapter not configured"))}};Loki.prototype.saveDatabase=function(callback){if(!this.throttledSaves){this.saveDatabaseInternal(callback);return}if(this.throttledSavePending){this.throttledCallbacks.push(callback);return}var localCallbacks=this.throttledCallbacks;this.throttledCallbacks=[];localCallbacks.unshift(callback);this.throttledSavePending=true;var self=this;this.saveDatabaseInternal(function(err){self.throttledSavePending=false;localCallbacks.forEach(function(pcb){if(typeof pcb==="function"){setTimeout(function(){pcb(err)},1)}});if(self.throttledCallbacks.length>0){self.saveDatabase()}})};Loki.prototype.save=Loki.prototype.saveDatabase;Loki.prototype.deleteDatabase=function(options,callback){var cFun=callback||function(err,data){if(err){throw err}};if(typeof options==="function"&&!callback){cFun=options}if(this.persistenceAdapter!==null){this.persistenceAdapter.deleteDatabase(this.filename,function deleteDatabaseCallback(err){cFun(err)})}else{cFun(new Error("persistenceAdapter not configured"))}};Loki.prototype.autosaveDirty=function(){for(var idx=0;idx0){this.filteredrows=[]}this.filterInitialized=false;return this};Resultset.prototype.toJSON=function(){var copy=this.copy();copy.collection=null;return copy};Resultset.prototype.limit=function(qty){if(!this.filterInitialized&&this.filteredrows.length===0){this.filteredrows=this.collection.prepareFullDocIndex()}var rscopy=new Resultset(this.collection);rscopy.filteredrows=this.filteredrows.slice(0,qty);rscopy.filterInitialized=true;return rscopy};Resultset.prototype.offset=function(pos){if(!this.filterInitialized&&this.filteredrows.length===0){this.filteredrows=this.collection.prepareFullDocIndex()}var rscopy=new Resultset(this.collection);rscopy.filteredrows=this.filteredrows.slice(pos);rscopy.filterInitialized=true;return rscopy};Resultset.prototype.copy=function(){var result=new Resultset(this.collection);if(this.filteredrows.length>0){result.filteredrows=this.filteredrows.slice()}result.filterInitialized=this.filterInitialized;return result};Resultset.prototype.branch=Resultset.prototype.copy;Resultset.prototype.transform=function(transform,parameters){var idx,step,rs=this;if(typeof transform==="string"){if(this.collection.transforms.hasOwnProperty(transform)){transform=this.collection.transforms[transform]}}if(typeof transform!=="object"||!Array.isArray(transform)){throw new Error("Invalid transform")}if(typeof parameters!=="undefined"){transform=Utils.resolveTransformParams(transform,parameters)}for(idx=0;idxobj2[propname])return 1;if(obj1[propname]1){return this.find({$and:filters},firstOnly)}}if(!property||queryObject==="getAll"){if(firstOnly){this.filteredrows=this.collection.data.length>0?[0]:[];this.filterInitialized=true}return this}if(property==="$and"||property==="$or"){this[property](queryObjectOp);if(firstOnly&&this.filteredrows.length>1){this.filteredrows=this.filteredrows.slice(0,1)}return this}if(queryObjectOp===null||(typeof queryObjectOp!=="object"||queryObjectOp instanceof Date)){operator="$eq";value=queryObjectOp}else if(typeof queryObjectOp==="object"){for(key in queryObjectOp){if(hasOwnProperty.call(queryObjectOp,key)){operator=key;value=queryObjectOp[key];break}}}else{throw new Error("Do not know what you want to do.")}if(operator==="$regex"){if(Array.isArray(value)){value=new RegExp(value[0],value[1])}else if(!(value instanceof RegExp)){value=new RegExp(value)}}var usingDotNotation=property.indexOf(".")!==-1;var doIndexCheck=!usingDotNotation&&!this.filterInitialized;if(doIndexCheck&&this.collection.binaryIndices[property]&&indexedOps[operator]){if(this.collection.adaptiveBinaryIndices!==true){this.collection.ensureIndex(property)}searchByIndex=true;index=this.collection.binaryIndices[property]}var fun=LokiOps[operator];var t=this.collection.data;var i=0,len=0;var filter,rowIdx=0;if(this.filterInitialized){filter=this.filteredrows;len=filter.length;if(usingDotNotation){property=property.split(".");for(i=0;i=0){this.filterPipeline[idx]=filter;return this.reapplyFilters()}this.cachedresultset=null;if(this.options.persistent){this.resultdata=[];this.resultsdirty=true}this._addFilter(filter);if(this.sortFunction||this.sortCriteria){this.queueSortPhase()}else{this.queueRebuildEvent()}return this};DynamicView.prototype.applyFind=function(query,uid){this.applyFilter({type:"find",val:query,uid:uid});return this};DynamicView.prototype.applyWhere=function(fun,uid){this.applyFilter({type:"where",val:fun,uid:uid});return this};DynamicView.prototype.removeFilter=function(uid){var idx=this._indexOfFilterWithId(uid);if(idx<0){throw new Error("Dynamic view does not contain a filter with ID: "+uid)}this.filterPipeline.splice(idx,1);this.reapplyFilters();return this};DynamicView.prototype.count=function(){if(this.resultsdirty){this.resultdata=this.resultset.data()}return this.resultset.count()};DynamicView.prototype.data=function(options){if(this.sortDirty||this.resultsdirty){this.performSortPhase({suppressRebuildEvent:true})}return this.options.persistent?this.resultdata:this.resultset.data(options)};DynamicView.prototype.queueRebuildEvent=function(){if(this.rebuildPending){return}this.rebuildPending=true;var self=this;setTimeout(function(){if(self.rebuildPending){self.rebuildPending=false;self.emit("rebuild",self)}},this.options.minRebuildInterval)};DynamicView.prototype.queueSortPhase=function(){if(this.sortDirty){return}this.sortDirty=true;var self=this;if(this.options.sortPriority==="active"){setTimeout(function(){self.performSortPhase()},this.options.minRebuildInterval)}else{this.queueRebuildEvent()}};DynamicView.prototype.performSortPhase=function(options){if(!this.sortDirty&&!this.resultsdirty){return}options=options||{};if(this.sortDirty){if(this.sortFunction){this.resultset.sort(this.sortFunction)}else if(this.sortCriteria){this.resultset.compoundsort(this.sortCriteria)}this.sortDirty=false}if(this.options.persistent){this.resultdata=this.resultset.data();this.resultsdirty=false}if(!options.suppressRebuildEvent){this.emit("rebuild",this)}};DynamicView.prototype.evaluateDocument=function(objIndex,isNew){if(!this.resultset.filterInitialized){if(this.options.persistent){this.resultdata=this.resultset.data()}if(this.sortFunction||this.sortCriteria){this.queueSortPhase()}else{this.queueRebuildEvent()}return}var ofr=this.resultset.filteredrows;var oldPos=isNew?-1:ofr.indexOf(+objIndex);var oldlen=ofr.length;var evalResultset=new Resultset(this.collection);evalResultset.filteredrows=[objIndex];evalResultset.filterInitialized=true;var filter;for(var idx=0,len=this.filterPipeline.length;idxobjIndex){ofr[idx]--}}};DynamicView.prototype.mapReduce=function(mapFunction,reduceFunction){try{return reduceFunction(this.data().map(mapFunction))}catch(err){throw err}};function Collection(name,options){this.name=name;this.data=[];this.idIndex=[];this.binaryIndices={};this.constraints={unique:{},exact:{}};this.uniqueNames=[];this.transforms={};this.objType=name;this.dirty=true;this.cachedIndex=null;this.cachedBinaryIndex=null;this.cachedData=null;var self=this;options=options||{};if(options.hasOwnProperty("unique")){if(!Array.isArray(options.unique)){options.unique=[options.unique]}options.unique.forEach(function(prop){self.uniqueNames.push(prop);self.constraints.unique[prop]=new UniqueIndex(prop)})}if(options.hasOwnProperty("exact")){options.exact.forEach(function(prop){self.constraints.exact[prop]=new ExactIndex(prop)})}this.adaptiveBinaryIndices=options.hasOwnProperty("adaptiveBinaryIndices")?options.adaptiveBinaryIndices:true;this.transactional=options.hasOwnProperty("transactional")?options.transactional:false;this.cloneObjects=options.hasOwnProperty("clone")?options.clone:false;this.cloneMethod=options.hasOwnProperty("cloneMethod")?options.cloneMethod:"parse-stringify";this.asyncListeners=options.hasOwnProperty("asyncListeners")?options.asyncListeners:false;this.disableMeta=options.hasOwnProperty("disableMeta")?options.disableMeta:false;this.disableChangesApi=options.hasOwnProperty("disableChangesApi")?options.disableChangesApi:true;this.disableDeltaChangesApi=options.hasOwnProperty("disableDeltaChangesApi")?options.disableDeltaChangesApi:true;if(this.disableChangesApi){this.disableDeltaChangesApi=true}this.autoupdate=options.hasOwnProperty("autoupdate")?options.autoupdate:false;this.serializableIndices=options.hasOwnProperty("serializableIndices")?options.serializableIndices:true;this.ttl={age:null,ttlInterval:null,daemon:null};this.setTTL(options.ttl||-1,options.ttlInterval);this.maxId=0;this.DynamicViews=[];this.events={insert:[],update:[],"pre-insert":[],"pre-update":[],close:[],flushbuffer:[],error:[],delete:[],warning:[]};this.changes=[];this.ensureId();var indices=[];if(options&&options.indices){if(Object.prototype.toString.call(options.indices)==="[object Array]"){indices=options.indices}else if(typeof options.indices==="string"){indices=[options.indices]}else{throw new TypeError("Indices needs to be a string or an array of strings")}}for(var idx=0;idx=0||propertyName=="$loki"||propertyName=="meta"){delta[propertyName]=newObject[propertyName]}else{var propertyDelta=getObjectDelta(oldObject[propertyName],newObject[propertyName]);if(typeof propertyDelta!=="undefined"&&propertyDelta!={}){delta[propertyName]=propertyDelta}}}}return Object.keys(delta).length===0?undefined:delta}else{return oldObject===newObject?undefined:newObject}}this.getObjectDelta=getObjectDelta;function flushChanges(){self.changes=[]}this.getChanges=function(){return self.changes};this.flushChanges=flushChanges;function insertMeta(obj){var len,idx;if(self.disableMeta||!obj){return}if(Array.isArray(obj)){len=obj.length;for(idx=0;idx1){options.randomSamplingFactor=.1}var valid=true,idx,iter,pos,len,biv;if(!this.binaryIndices.hasOwnProperty(property)){throw new Error("called checkIndex on property without an index: "+property)}if(!this.adaptiveBinaryIndices){this.ensureIndex(property)}biv=this.binaryIndices[property].values;len=biv.length;if(len!==this.data.length){if(options.repair){this.ensureIndex(property,true)}return false}if(len===0){return true}if(len===1){valid=biv[0]===0}if(options.randomSampling){if(!LokiOps.$lte(this.data[biv[0]][property],this.data[biv[1]][property])){valid=false}if(!LokiOps.$lte(this.data[biv[len-2]][property],this.data[biv[len-1]][property])){valid=false}if(valid){iter=Math.floor((len-1)*options.randomSamplingFactor);for(idx=0;idx0;if(adaptiveBatchOverride){this.adaptiveBinaryIndices=false}for(k;k>1;id=typeof id==="number"?id:parseInt(id,10);if(isNaN(id)){throw new TypeError("Passed id is not an integer")}while(data[min]>1;if(data[mid]dataPosition){index[idx]--}}};Collection.prototype.calculateRangeStart=function(prop,val,adaptive){var rcd=this.data;var index=this.binaryIndices[prop].values;var min=0;var max=index.length-1;var mid=0;if(index.length===0){return-1}var minVal=rcd[index[min]][prop];var maxVal=rcd[index[max]][prop];while(min>1;if(ltHelper(rcd[index[mid]][prop],val,false)){min=mid+1}else{max=mid}}var lbound=min;if(aeqHelper(val,rcd[index[lbound]][prop])){return lbound}if(ltHelper(val,rcd[index[lbound]][prop],false)){return adaptive?lbound:lbound-1}return adaptive?lbound+1:lbound};Collection.prototype.calculateRangeEnd=function(prop,val){var rcd=this.data;var index=this.binaryIndices[prop].values;var min=0;var max=index.length-1;var mid=0;if(index.length===0){return-1}var minVal=rcd[index[min]][prop];var maxVal=rcd[index[max]][prop];while(min>1;if(ltHelper(val,rcd[index[mid]][prop],false)){max=mid}else{min=mid+1}}var ubound=max;if(aeqHelper(val,rcd[index[ubound]][prop])){return ubound}if(gtHelper(val,rcd[index[ubound]][prop],false)){return ubound+1}if(aeqHelper(val,rcd[index[ubound-1]][prop])){return ubound-1}return ubound};Collection.prototype.calculateRange=function(op,prop,val){var rcd=this.data;var index=this.binaryIndices[prop].values;var min=0;var max=index.length-1;var mid=0;var lbound,lval;var ubound,uval;if(rcd.length===0){return[0,-1]}var minVal=rcd[index[min]][prop];var maxVal=rcd[index[max]][prop];switch(op){case"$eq":case"$aeq":if(ltHelper(val,minVal,false)||gtHelper(val,maxVal,false)){return[0,-1]}break;case"$dteq":if(ltHelper(val,minVal,false)||gtHelper(val,maxVal,false)){return[0,-1]}break;case"$gt":if(gtHelper(val,maxVal,true)){return[0,-1]}if(gtHelper(minVal,val,false)){return[min,max]}break;case"$gte":if(gtHelper(val,maxVal,false)){return[0,-1]}if(gtHelper(minVal,val,true)){return[min,max]}break;case"$lt":if(ltHelper(val,minVal,true)){return[0,-1]}if(ltHelper(maxVal,val,false)){return[min,max]}break;case"$lte":if(ltHelper(val,minVal,false)){return[0,-1]}if(ltHelper(maxVal,val,true)){return[min,max]}break;case"$between":if(gtHelper(val[0],maxVal,false)){return[0,-1]}if(ltHelper(val[1],minVal,false)){return[0,-1]}lbound=this.calculateRangeStart(prop,val[0]);ubound=this.calculateRangeEnd(prop,val[1]);if(lbound<0)lbound++;if(ubound>max)ubound--;if(!gtHelper(rcd[index[lbound]][prop],val[0],true))lbound++;if(!ltHelper(rcd[index[ubound]][prop],val[1],true))ubound--;if(ubounddeepProperty(this.data[i],field,deep)){min=deepProperty(this.data[i],field,deep);result.index=this.data[i].$loki}}else{min=deepProperty(this.data[i],field,deep);result.index=this.data[i].$loki}}result.value=min;return result};Collection.prototype.extractNumerical=function(field){return this.extract(field).map(parseBase10).filter(Number).filter(function(n){return!isNaN(n)})};Collection.prototype.avg=function(field){return average(this.extractNumerical(field))};Collection.prototype.stdDev=function(field){return standardDeviation(this.extractNumerical(field))};Collection.prototype.mode=function(field){var dict={},data=this.extract(field);data.forEach(function(obj){if(dict[obj]){dict[obj]+=1}else{dict[obj]=1}});var max,prop,mode;for(prop in dict){if(max){if(max0){root=root[pieces.shift()]}return root}function binarySearch(array,item,fun){var lo=0,hi=array.length,compared,mid;while(lo>1;compared=fun.apply(null,[item,array[mid]]);if(compared===0){return{found:true,index:mid}}else if(compared<0){hi=mid}else{lo=mid+1}}return{found:false,index:hi}}function BSonSort(fun){return function(array,item){return binarySearch(array,item,fun)}}function KeyValueStore(){}KeyValueStore.prototype={keys:[],values:[],sort:function(a,b){return ab?1:0},setSort:function(fun){this.bs=new BSonSort(fun)},bs:function(){return new BSonSort(this.sort)},set:function(key,value){var pos=this.bs(this.keys,key);if(pos.found){this.values[pos.index]=value}else{this.keys.splice(pos.index,0,key);this.values.splice(pos.index,0,value)}},get:function(key){return this.values[binarySearch(this.keys,key,this.sort).index]}};function UniqueIndex(uniqueField){this.field=uniqueField;this.keyMap={};this.lokiMap={}}UniqueIndex.prototype.keyMap={};UniqueIndex.prototype.lokiMap={};UniqueIndex.prototype.set=function(obj){var fieldValue=obj[this.field];if(fieldValue!==null&&typeof fieldValue!=="undefined"){if(this.keyMap[fieldValue]){throw new Error("Duplicate key for property "+this.field+": "+fieldValue)}else{this.keyMap[fieldValue]=obj;this.lokiMap[obj.$loki]=fieldValue}}};UniqueIndex.prototype.get=function(key){return this.keyMap[key]};UniqueIndex.prototype.byId=function(id){return this.keyMap[this.lokiMap[id]]};UniqueIndex.prototype.update=function(obj,doc){if(this.lokiMap[obj.$loki]!==doc[this.field]){var old=this.lokiMap[obj.$loki];this.set(doc);this.keyMap[old]=undefined}else{this.keyMap[obj[this.field]]=doc}};UniqueIndex.prototype.remove=function(key){var obj=this.keyMap[key];if(obj!==null&&typeof obj!=="undefined"){this.keyMap[key]=undefined;this.lokiMap[obj.$loki]=undefined}else{throw new Error("Key is not in unique index: "+this.field)}};UniqueIndex.prototype.clear=function(){this.keyMap={};this.lokiMap={}};function ExactIndex(exactField){this.index={};this.field=exactField}ExactIndex.prototype={set:function add(key,val){if(this.index[key]){this.index[key].push(val)}else{this.index[key]=[val]}},remove:function remove(key,val){var idxSet=this.index[key];for(var i in idxSet){if(idxSet[i]==val){idxSet.splice(i,1)}}if(idxSet.length<1){this.index[key]=undefined}},get:function get(key){return this.index[key]},clear:function clear(key){this.index={}}};function SortedIndex(sortedField){this.field=sortedField}SortedIndex.prototype={keys:[],values:[],sort:function(a,b){return ab?1:0},bs:function(){return new BSonSort(this.sort)},setSort:function(fun){this.bs=new BSonSort(fun)},set:function(key,value){var pos=binarySearch(this.keys,key,this.sort);if(pos.found){this.values[pos.index].push(value)}else{this.keys.splice(pos.index,0,key);this.values.splice(pos.index,0,[value])}},get:function(key){var bsr=binarySearch(this.keys,key,this.sort);if(bsr.found){return this.values[bsr.index]}else{return[]}},getLt:function(key){var bsr=binarySearch(this.keys,key,this.sort);var pos=bsr.index;if(bsr.found)pos--;return this.getAll(key,0,pos)},getGt:function(key){var bsr=binarySearch(this.keys,key,this.sort);var pos=bsr.index;if(bsr.found)pos++;return this.getAll(key,pos,this.keys.length)},getAll:function(key,start,end){var results=[];for(var i=start;ie;e++){var s=r.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null===o||void 0===o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;t>e;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;ie;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module&&(module.exports=LZString);
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/DestroyManagers.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/DestroyManagers.js
new file mode 100644
index 000000000..2548e7cb6
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/DestroyManagers.js
@@ -0,0 +1,24 @@
+var DestroyManagers = function (fromScene) {
+    this.waitEventManager.destroy();
+    this.waitEventManager = undefined;
+
+    if (this.soundManager) {
+        this.soundManager.destroy();
+    }
+    this.soundManager = undefined;
+
+    for (var name in this.gameObjectManagers) {
+        this.gameObjectManagers[name].destroy(fromScene);
+        delete this.gameObjectManagers[name];
+    }
+
+    if (this.timeline) {
+        this.timeline.destroy();
+    }
+
+    this.timeline = undefined;
+
+    this.managersScene = undefined;
+}
+
+export default DestroyManagers;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/Extend.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/Extend.js
new file mode 100644
index 000000000..332ca3c39
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/Extend.js
@@ -0,0 +1,28 @@
+import InitManagers from './InitManagers.js';
+import SetTimeScale from './SetTimeScale.js';
+import GetTimeScale from './GetTimeScale.js';
+import DestroyManagers from './DestroyManagers.js';
+import GameObjectManagerMethods from './GameObjectManagerMethods.js';
+import GameObjectMethods from './GameObjectMethods.js';
+
+var Extend = function (BaseClass) {
+    class Managers extends BaseClass { }
+
+    var Methods = {
+        initManagers: InitManagers,
+        setTimeScale: SetTimeScale,
+        getTimeScale: GetTimeScale,
+        destroyManagers: DestroyManagers,
+    }
+
+    Object.assign(
+        Managers.prototype,
+        Methods,
+        GameObjectManagerMethods,
+        GameObjectMethods,
+    )
+
+    return Managers;
+}
+
+export default Extend;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/GameObjectManagerMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/GameObjectManagerMethods.js
new file mode 100644
index 000000000..ff7ef018f
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/GameObjectManagerMethods.js
@@ -0,0 +1,50 @@
+import GameObjectManagerBase from '../gameobject/gomanager/GOManager.js';
+
+export default {
+    addGameObjectManager(config, GameObjectManagerClass) {
+        if (config === undefined) {
+            config = {};
+        }
+        if (GameObjectManagerClass === undefined) {
+            GameObjectManagerClass = GameObjectManagerBase;
+        }
+
+        if (!config.createGameObjectScope) {
+            config.createGameObjectScope = this;
+        }
+        var gameobjectManager = new GameObjectManagerClass(this.managersScene, config);
+        this.gameObjectManagers[config.name] = gameobjectManager;
+
+        return this;
+    },
+
+    getGameObjectManager(managerName, gameObjectName) {
+        if (managerName) {
+            var manager = this.gameObjectManagers[managerName]
+            return manager;
+        } else {
+            for (var managerName in this.gameObjectManagers) {
+                var manager = this.gameObjectManagers[managerName]
+                if (manager.has(gameObjectName)) {
+                    return manager;
+                }
+            }
+        }
+    },
+
+    getGameObjectManagerNames() {
+        var names = [];
+        for (var name in this.gameObjectManagers) {
+            names.push(name);
+        }
+        return names;
+    },
+
+    getGameObjectManagerName(gameObjectName) {
+        for (var managerName in this.gameObjectManagers) {
+            if (this.gameObjectManagers[managerName].has(gameObjectName)) {
+                return managerName;
+            }
+        }
+    },
+}
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/GameObjectMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/GameObjectMethods.js
new file mode 100644
index 000000000..3c899bee5
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/GameObjectMethods.js
@@ -0,0 +1,92 @@
+export default {
+    createGameObject(goType, name, ...params) {
+        this.getGameObjectManager(goType, name).add(name, ...params);
+        return this;
+    },
+
+    destroyGameObject(goType, name) {
+        var gameObjectManager = this.getGameObjectManager(goType, name);
+        if (name === undefined) {
+            gameObjectManager.removeAll();
+        } else {
+            gameObjectManager.remove(name);
+        }
+        return this;
+    },
+
+    callGameObjectMethod(goType, name, methodName, ...params) {
+        this.getGameObjectManager(goType, name).call(name, methodName, ...params);
+        return this;
+    },
+
+    setGameObjectProperty(goType, name, prop, value) {
+        this.getGameObjectManager(goType, name).setProperty(name, prop, value);
+        return this;
+    },
+
+    easeGameObjectProperty(goType, name, prop, value, duration, ease, repeat, isYoyo) {
+        this.getGameObjectManager(goType, name).easeProperty(
+            name, prop, value,
+            duration, ease, repeat, isYoyo
+        );
+        return this;
+    },
+
+    getGameObjectTweenTask(goType, name, property) {
+        return this.getGameObjectManager(goType, name).getTweenTask(name, property);
+    },
+
+    getGameObject(goType, name, out) {
+        var gameobjectManager = this.getGameObjectManager(goType, name);
+        if (typeof (name) === 'string') {
+            return gameobjectManager.getGO(name);
+        } else {
+            var names = name;
+            if (names === undefined) {
+                names = gameobjectManager.bobs;
+            }
+            if (out === undefined) {
+                out = {};
+            }
+            for (name in names) {
+                out[name] = gameobjectManager.getGO(name);
+            }
+            return out;
+        }
+    },
+
+    addGameObject(goType, name, gameObject) {
+        var gameobjectManager = this.getGameObjectManager(goType, name);
+        if (typeof (name) === 'string') {
+            gameobjectManager.addGO(name, gameObject);
+        } else {
+            var names = name;
+            for (name in names) {
+                gameobjectManager.addGO(name, names[name]);
+            }
+        }
+        return this;
+    },
+
+    drawGameObjectsBounds(goTypes, graphics, config) {
+        if (goTypes instanceof Phaser.GameObjects.Graphics) {
+            config = graphics;
+            graphics = goTypes;
+            goTypes = undefined;
+        }
+
+        if (goTypes === undefined) {
+            goTypes = this.getGameObjectManagerNames();
+        }
+
+        if (!Array.isArray(goTypes)) {
+            goTypes = [goTypes];
+        }
+        for (var i = 0, cnt = goTypes.length; i < cnt; i++) {
+            this.getGameObjectManager(goTypes[i]).drawGameObjectsBounds(graphics, config)
+        }
+
+        return this;
+    }
+
+}
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/GetTimeScale.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/GetTimeScale.js
new file mode 100644
index 000000000..8c235ba53
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/GetTimeScale.js
@@ -0,0 +1,5 @@
+var GetTimeScale = function () {
+    return this.timeline.timeScale;
+}
+
+export default GetTimeScale;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/InitManagers.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/InitManagers.js
new file mode 100644
index 000000000..9c6fd58d5
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/InitManagers.js
@@ -0,0 +1,24 @@
+import SoundManager from '../audio/soundmanager/SoundManager.js';
+import Timeline from '../../time/progresses/Timeline.js';
+import WaitEventManager from './waiteventmanager/WaitEventManager.js';
+
+const GetValue = Phaser.Utils.Objects.GetValue;
+
+var InitManagers = function (scene, config) {
+    this.managersScene = scene;
+
+    var soundManagerConfig = GetValue(config, 'sounds');
+    if (soundManagerConfig !== false) {
+        this.soundManager = new SoundManager(scene, soundManagerConfig);
+    }
+
+    this.gameObjectManagers = {};
+
+    this.timeline = new Timeline(this);
+
+    this.waitEventManager = new WaitEventManager(this, config);
+
+    return this;
+}
+
+export default InitManagers;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/Managers.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/managers/Managers.d.ts
new file mode 100644
index 000000000..3ea32ba92
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/Managers.d.ts
@@ -0,0 +1,131 @@
+import GOManager from '../gameobject/gomanager/GOManager';
+import SoundManager from '../audio/soundmanager/SoundManager';
+import WaitEventManager from './waiteventmanager/WaitEventManager';
+
+export default Managers;
+
+declare namespace Managers {
+    interface IConfigSounds {
+        bgm?: {
+            initial?: string,
+            loop?: boolean,
+            fade?: number
+        },
+        bgm2?: {
+            initial?: string,
+            loop?: boolean,
+            fade?: number
+        }
+    }
+
+    type CreateGameObjectCallbackType = (
+        scene: Phaser.Scene,
+        ...args: any[]
+    ) => Phaser.GameObjects.GameObject
+
+    interface IGameObjectConfig {
+        name: string,
+
+        createGameObject: CreateGameObjectCallbackType,
+
+        fade?: number | {
+            mode?: 0 | 1 | 'tint' | 'alpha',
+            time?: number
+        },
+
+        viewportCoordinate?: boolean | {
+            enable?: boolean,
+            viewport?: Phaser.Geom.Rectangle
+        }
+    }
+
+    interface IDrawBoundsConfig {
+        color?: number,
+        lineWidth?: number
+    }
+
+    interface IConfig {
+        sounds?: IConfigSounds
+    }
+}
+
+declare class Managers extends Phaser.Events.EventEmitter {
+    constructor(
+        scene: Phaser.Scene,
+        config?: Managers.IConfig,
+    );
+
+    gameObjectManagers: { [name: string]: GOManager };
+
+    soundManager: SoundManager;
+
+    waitEventManager: WaitEventManager;
+
+    destroy(fromScene?: boolean): this;
+
+    addGameObjectManager(config: Managers.IGameObjectConfig): this;
+
+    getGameObjectManager(
+        managerName: string | null | undefined,
+        name?: string
+    ): GOManager;
+
+    getGameObjectManagerNames(): string[];
+
+    getGameObjectManagerName(gameObjectName: string): string;
+
+    createGameObject(
+        goType: string, name: string,
+        ...params: any[]
+    ): this;
+
+    destroyGameObject(goType: string | undefined, name: string): this;
+
+    callGameObjectMethod(
+        goType: string | undefined, name: string,
+        methodName: string, ...params: any[]
+    ): this;
+
+    setGameObjectProperty(
+        goType: string | undefined, name: string,
+        prop: string, value: any,
+    ): this;
+
+    easeGameObjectProperty(
+        goType: string | undefined, name: string,
+        prop: string, value: any,
+        duration?: number, ease?: string, repeat?: number, isYoyo?: boolean
+    ): this;
+
+    getGameObjectTweenTask(
+        goType: string | undefined, name: string,
+        property: string | undefined
+    ): Phaser.Tweens.Tween | null;
+
+    getGameObject(
+        goType: string | undefined, name: string,
+    ): Phaser.GameObjects.GameObject;
+
+    getGameObject(
+        goType: string, name: { [name: string]: string }
+    ): { [name: string]: Phaser.GameObjects.GameObject };
+
+    addGameObject(
+        goType: string, name: string,
+        gameObject: Phaser.GameObjects.GameObject
+    ): this;
+
+    drawGameObjectsBounds(
+        graphics: Phaser.GameObjects.Graphics,
+        config?: number | Managers.IDrawBoundsConfig,
+    ): this;
+    drawGameObjectsBounds(
+        goTypes: string[],
+        graphics: Phaser.GameObjects.Graphics,
+        config?: number | Managers.IDrawBoundsConfig,
+    ): this;
+
+    setTimeScale(value: number): this;
+    getTimeScale(value: number): this;
+
+}
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/Managers.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/Managers.js
new file mode 100644
index 000000000..2fb9fa6eb
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/Managers.js
@@ -0,0 +1,33 @@
+import Extend from './Extend';
+
+const EventEmitter = Phaser.Events.EventEmitter;
+
+class Managers extends Extend(EventEmitter) {
+    constructor(scene, config) {
+        if (config === undefined) {
+            config = {};
+        }
+        config.completeEventName = 'complete';
+
+        super();
+
+        this.scene = scene;
+
+        this.initManagers(scene, config);
+    }
+
+    destroy(fromScene) {
+        //  This Game Object has already been destroyed
+        if (!this.scene) {
+            return;
+        }
+
+        this.destroyManagers(fromScene);
+
+        this.scene = undefined;
+
+        super.destroy();
+    }
+}
+
+export default Managers;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/SetTimeScale.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/SetTimeScale.js
new file mode 100644
index 000000000..2fef3b684
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/SetTimeScale.js
@@ -0,0 +1,9 @@
+var SetTimeScale = function (value) {
+    this.timeline.timeScale = value;
+    for (var name in this.gameObjectManagers) {
+        this.gameObjectManagers[name].setTimeScale(value);
+    }
+    return this;
+}
+
+export default SetTimeScale;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitAny.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitAny.js
new file mode 100644
index 000000000..290fca906
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitAny.js
@@ -0,0 +1,90 @@
+var WaitAny = function (config) {
+    if (!config) {
+        return this.waitTime(0);
+    }
+
+    var hasAnyWaitEvent = false;
+    for (var name in config) {
+        switch (name) {
+            case 'time':
+                hasAnyWaitEvent = true;
+                this.waitTime(config.time);
+                break;
+
+            case 'click':
+                hasAnyWaitEvent = true;
+                this.waitClick(config.key);
+                break;
+
+
+            case 'key':
+                hasAnyWaitEvent = true;
+                this.waitKeyDown(config.key);
+                break;
+
+            case 'camera':
+                hasAnyWaitEvent = true;
+                this.waitCameraEffectComplete(config.camera);
+                break;
+
+            case 'bgm':
+                hasAnyWaitEvent = true;
+                this.waitBackgroundMusicComplete();
+                break;
+
+            case 'bgm2':
+                hasAnyWaitEvent = true;
+                this.waitBackgroundMusic2Complete();
+                break;
+
+            case 'se':
+                hasAnyWaitEvent = true;
+                this.waitSoundEffectComplete();
+                break;
+
+            case 'se2':
+                hasAnyWaitEvent = true;
+                this.waitSoundEffect2Complete();
+                break;
+
+            default:
+                var names = name.split('.');
+                if (names.length === 2) {
+                    var gameObjectName = names[0];
+                    var propName = names[1];
+                    var gameObjectManager = this.parent.getGameObjectManager(undefined, gameObjectName);
+                    if (!gameObjectManager) {
+                        continue;
+                    }
+
+                    var value = gameObjectManager.getProperty(gameObjectName, propName);
+                    if (typeof (value) === 'number') {
+                        hasAnyWaitEvent = true;
+                        this.waitGameObjectTweenComplete(undefined, gameObjectName, propName);
+                        continue;
+
+                    }
+
+                    var dataKey = propName;
+                    var matchFalseFlag = dataKey.startsWith('!');
+                    if (matchFalseFlag) {
+                        dataKey = dataKey.substring(1);
+                    }
+                    if (gameObjectManager.hasData(gameObjectName, propName)) {
+                        hasAnyWaitEvent = true;
+                        this.waitGameObjectDataFlag(undefined, gameObjectName, dataKey, !matchFalseFlag);
+                    }
+                }
+                break;
+
+        }
+    }
+
+    if (!hasAnyWaitEvent) {
+        this.waitTime(0);
+    }
+
+    return this.parent;
+}
+
+export default WaitAny;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitCameraMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitCameraMethods.js
new file mode 100644
index 000000000..77aa2158b
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitCameraMethods.js
@@ -0,0 +1,52 @@
+export default {
+    waitCameraEffectComplete(effectName) {
+        var camera = this.targetCamera;
+        if (!camera) {
+            return this.waitTime(0);
+        }
+
+        var effect, completeEventName;
+        switch (effectName) {
+            case 'camera.fadein':
+                effect = camera.fadeEffect;
+                completeEventName = 'camerafadeincomplete';
+                break;
+
+            case 'camera.fadeout':
+                effect = camera.fadeEffect;
+                completeEventName = 'camerafadeoutcomplete';
+                break;
+
+            case 'camera.flash':
+                effect = camera.flashEffect;
+                completeEventName = 'cameraflashcomplete';
+                break;
+
+            case 'camera.shake':
+                effect = camera.shakeEffect;
+                completeEventName = 'camerashakecomplete';
+                break;
+
+            case 'camera.zoom':
+                effect = camera.zoomEffect;
+                completeEventName = 'camerazoomcomplete';
+                break;
+
+            case 'camera.rotate':
+                effect = camera.rotateToEffect;
+                completeEventName = 'camerarotatecomplete';
+                break;
+
+            case 'camera.scroll':
+                effect = camera.panEffect;
+                completeEventName = 'camerapancomplete';
+                break;
+        }
+
+        if (!effect.isRunning) {
+            return this.waitTime(0);
+        }
+
+        return this.waitEvent(camera, completeEventName);
+    },
+}
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitEventManager.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitEventManager.d.ts
new file mode 100644
index 000000000..e7049eb13
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitEventManager.d.ts
@@ -0,0 +1,52 @@
+import Managers from '../Managers';
+
+export default WaitEventManager;
+
+declare namespace WaitEventManager {
+    interface IConfig {
+        completeEventName?: string,
+        clickTarget?: Phaser.Events.EventEmitter,
+        camera?: Phaser.Cameras.Scene2D.Camera
+    }
+
+    interface IWaitAnyConfig {
+        time?: number,
+        click?: boolean,
+        key?: string | boolean,
+        camera?: string,
+        bgm?: boolean,
+        bgm2?: boolean,
+        se?: boolean,
+        se2?: boolean,
+
+        // GameObject tween
+    }
+}
+
+declare class WaitEventManager {
+    constructor(
+        parent: Managers,
+        config?: WaitEventManager.IConfig,
+    );
+
+    waitEvent(
+        eventEmitter: Phaser.Events.EventEmitter,
+        eventName: string,
+        completeNextTick?: boolean
+    ): Managers
+
+
+    waitTime(duration: number): Managers;
+    waitClick(): Managers;
+    waitKeyDown(key?: string): Managers;
+    waitGameObjectTweenComplete(
+        goType: string | undefined, name: string,
+        property: string
+    ): Managers;
+    waitBackgroundMusicComplete(): Managers;
+    waitBackgroundMusic2Complete(): Managers;
+    waitSoundEffectComplete(): Managers;
+    waitSoundEffect2Complete(): Managers;
+    waitCameraEffectComplete(effectName: string): Managers;
+    waitAny(config?: { [name: string]: any }): Managers;
+}
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitEventManager.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitEventManager.js
new file mode 100644
index 000000000..20e76dd44
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitEventManager.js
@@ -0,0 +1,86 @@
+import { WaitCompleteEvent, RemoveWaitEvents } from './const.js';
+import WaitInputMethods from './WaitInputMethods.js';
+import WaitGameObjectMethods from './WaitGameObjectMethods.js';
+import WaitCameraMethods from './WaitCameraMethods.js';
+import WaitMusicMethods from './WaitMusicMethods.js';
+import WaitAny from './WaitAny.js';
+import GetValue from '../../object/GetValue.js';
+import PreUpdateDelayCall from '../../time/PreUpdateDelayCall.js';
+
+class WaitEventManager {
+    constructor(parent, config) {
+        this.parent = parent;
+
+        this.waitCompleteEventName = GetValue(config, 'completeEventName', WaitCompleteEvent);
+        this.clickEE = GetValue(config, 'clickTarget', this.scene.input);
+        this.targetCamera = GetValue(config, 'camera', this.scene.cameras.main);
+    }
+
+    destroy() {
+        this.removeWaitEvents();
+        this.clickEE = undefined;
+        this.targetCamer = undefined;
+    }
+
+    get scene() {
+        return this.parent.managersScene;
+    }
+
+    waitEvent(eventEmitter, eventName, completeNextTick) {
+        if (completeNextTick === undefined) {
+            completeNextTick = true;
+        }
+
+        var callback = (completeNextTick) ? this.completeNextTick : this.complete;
+
+        eventEmitter.once(eventName, callback, this);
+        this.parent.once(RemoveWaitEvents, function () {
+            eventEmitter.off(eventName, callback, this);
+        })
+
+        return this.parent;
+    }
+
+    removeWaitEvents() {
+        this.parent.emit(RemoveWaitEvents);
+        return this;
+    }
+
+    complete() {
+        this.removeWaitEvents();
+        this.parent.emit(this.waitCompleteEventName);
+        return this;
+    }
+
+    completeNextTick() {
+        // Emit complete event at scene's preupdate event of next tick
+        PreUpdateDelayCall(this.parent, 0, this.complete, this);
+        return this;
+    }
+
+    waitTime(duration) {
+        var timeline = this.parent.timeline
+        timeline.delayEvent(duration, 'delay');
+
+        // Clear delay event on timeline manually
+        this.parent.once(RemoveWaitEvents, function () {
+            timeline.removeDelayEvent('delay');
+        });
+        return this.waitEvent(timeline, 'delay');
+    }
+}
+
+var Methods = {
+    waitAny: WaitAny,
+}
+
+Object.assign(
+    WaitEventManager.prototype,
+    WaitInputMethods,
+    WaitGameObjectMethods,
+    WaitCameraMethods,
+    WaitMusicMethods,
+    Methods,
+)
+
+export default WaitEventManager;
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitGameObjectMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitGameObjectMethods.js
new file mode 100644
index 000000000..941e1f84e
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitGameObjectMethods.js
@@ -0,0 +1,67 @@
+import { RemoveWaitEvents } from './const.js';
+
+export default {
+    waitGameObjectTweenComplete(goType, name, property) {
+        var tweenTask = this.parent.getGameObjectTweenTask(goType, name, property);
+        if (tweenTask) {
+            return this.waitEvent(tweenTask, 'complete');
+        }
+        return this.waitTime(0);
+    },
+
+    waitGameObjectDataFlag(goType, name, dataKey, trueFlag) {
+        var gameObject = this.parent.getGameObject(goType, name);
+        if (!gameObject) {
+            return this.waitTime(0);
+        }
+
+        if (gameObject.getData(dataKey) === trueFlag) {
+            return this.waitTime(0);
+        }
+
+        var eventName = `changedata-${dataKey}`;
+        var callback = function (gameObject, value, previousValue) {
+            value = !!value;
+            if (value === trueFlag) {
+                gameObject.emit('_dataFlagMatch');
+            }
+        }
+        gameObject.on(eventName, callback);
+        // Clear changedata event on gameobject manually
+        this.parent.once(RemoveWaitEvents, function () {
+            gameObject.off(eventName, callback);
+        });
+
+        return this.waitEvent(gameObject, '_dataFlagMatch');
+    },
+
+    waitGameObjectDestroy(goType, name) {
+        var gameObject = this.parent.getGameObject(goType, name);
+        if (!gameObject) {
+            return this.waitTime(0);
+        }
+        return this.waitEvent(gameObject, 'destroy');
+    },
+
+    waitGameObjectManagerEmpty(goType) {
+        if (goType) {
+            var gameObjectManager = this.parent.getGameObjectManager(goType);
+            if (!gameObjectManager) {
+                return this.waitTime(0);
+            }
+            return this.waitEvent(gameObjectManager, 'empty');
+
+        } else {
+            var gameObjectManagers = this.parent.gameObjectManagers;
+            var hasAnyWaitEvent = false;
+            for (var name in gameObjectManagers) {
+                hasAnyWaitEvent = true;
+                this.waitEvent(gameObjectManagers[name], 'empty');
+            }
+            if (!hasAnyWaitEvent) {
+                return this.waitTime(0);
+            }
+            return this.parent;
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitInputMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitInputMethods.js
new file mode 100644
index 000000000..d7df822bd
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitInputMethods.js
@@ -0,0 +1,18 @@
+export default {
+    waitClick() {
+        if (!this.clickEE) {
+            return this.waitTime(0);
+        }
+
+        return this.waitEvent(this.clickEE, 'pointerdown');
+    },
+
+    waitKeyDown(key) {
+        var eventEmitter = this.scene.input.keyboard;
+        if (typeof (key) === 'string') {
+            return this.waitEvent(eventEmitter, `keydown-${key.toUpperCase()}`)
+        } else {
+            return this.waitEvent(eventEmitter, 'keydown');
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitMusicMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitMusicMethods.js
new file mode 100644
index 000000000..a96fd5d8b
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitMusicMethods.js
@@ -0,0 +1,45 @@
+export default {
+    waitBackgroundMusicComplete() {
+        if (!this.parent.soundManager) {
+            return this.waitTime(0);
+        }
+        var music = this.parent.soundManager.getBackgroundMusic();
+        if (!music) {
+            return this.waitTime(0);
+        }
+        return this.waitEvent(music, 'complete');
+    },
+
+    waitBackgroundMusic2Complete() {
+        if (!this.parent.soundManager) {
+            return this.waitTime(0);
+        }
+        var music = this.parent.soundManager.getBackgroundMusic2();
+        if (!music) {
+            return this.waitTime(0);
+        }
+        return this.waitEvent(music, 'complete');
+    },
+
+    waitSoundEffectComplete() {
+        if (!this.parent.soundManager) {
+            return this.waitTime(0);
+        }
+        var music = this.parent.soundManager.getLastSoundEffect();
+        if (!music) {
+            return this.waitTime(0);
+        }
+        return this.waitEvent(music, 'complete');
+    },
+
+    waitSoundEffect2Complete() {
+        if (!this.parent.soundManager) {
+            return this.waitTime(0);
+        }
+        var music = this.parent.soundManager.getLastSoundEffect2();
+        if (!music) {
+            return this.waitTime(0);
+        }
+        return this.waitEvent(music, 'complete');
+    }
+}
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/const.js b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/const.js
new file mode 100644
index 000000000..782886b9b
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/const.js
@@ -0,0 +1,2 @@
+export const WaitCompleteEvent = '_wait.complete';
+export const RemoveWaitEvents = '_remove.wait';
\ No newline at end of file
diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/marked/marked.min.js b/ui/src/phaser3-rex-plugins/plugins/utils/marked/marked.min.js
new file mode 100644
index 000000000..1727d095e
--- /dev/null
+++ b/ui/src/phaser3-rex-plugins/plugins/utils/marked/marked.min.js
@@ -0,0 +1,6 @@
+/**
+ * marked v5.0.2 - a markdown parser
+ * Copyright (c) 2011-2023, Christopher Jeffrey. (MIT Licensed)
+ * https://github.com/markedjs/marked
+ */
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).marked={})}(this,function(r){"use strict";function i(e,t){for(var u=0;ue.length)&&(t=e.length);for(var u=0,n=new Array(t);u=e.length?{done:!0}:{done:!1,value:e[u++]}};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function e(){return{async:!1,baseUrl:null,breaks:!1,extensions:null,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,hooks:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:null,sanitize:!1,sanitizer:null,silent:!1,smartypants:!1,tokenizer:null,walkTokens:null,xhtml:!1}}r.defaults=e();function u(e){return t[e]}var n=/[&<>"']/,a=new RegExp(n.source,"g"),o=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,l=new RegExp(o.source,"g"),t={"&":"&","<":"<",">":">",'"':""","'":"'"};function A(e,t){if(t){if(n.test(e))return e.replace(a,u)}else if(o.test(e))return e.replace(l,u);return e}var c=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function m(e){return e.replace(c,function(e,t){return"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}var p=/(^|[^\[])\^/g;function h(u,e){u="string"==typeof u?u:u.source,e=e||"";var n={replace:function(e,t){return t=(t=t.source||t).replace(p,"$1"),u=u.replace(e,t),n},getRegex:function(){return new RegExp(u,e)}};return n}var j=/[^\w:]/g,Z=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;function f(e,t,u){if(e){try{n=decodeURIComponent(m(u)).replace(j,"").toLowerCase()}catch(e){return null}if(0===n.indexOf("javascript:")||0===n.indexOf("vbscript:")||0===n.indexOf("data:"))return null}var n;t&&!Z.test(u)&&(e=u,g[" "+(n=t)]||(O.test(n)?g[" "+n]=n+"/":g[" "+n]=C(n,"/",!0)),t=-1===(n=g[" "+n]).indexOf(":"),u="//"===e.substring(0,2)?t?e:n.replace(q,"$1")+e:"/"===e.charAt(0)?t?e:n.replace(P,"$1")+e:n+e);try{u=encodeURI(u).replace(/%25/g,"%")}catch(e){return null}return u}var g={},O=/^[^:]+:\/*[^/]*$/,q=/^([^:]+:)[\s\S]*$/,P=/^([^:]+:\/*[^/]*)[\s\S]*$/;var F={exec:function(){}};function k(e,t){var u=e.replace(/\|/g,function(e,t,u){for(var n=!1,r=t;0<=--r&&"\\"===u[r];)n=!n;return n?"|":" |"}).split(/ \|/),n=0;if(u[0].trim()||u.shift(),0t)u.splice(t);else for(;u.length>=1,e+=e;return u+e}function x(e,t,u,n){var r=t.href,t=t.title?A(t.title):null,i=e[1].replace(/\\([\[\]])/g,"$1");return"!"!==e[0].charAt(0)?(n.state.inLink=!0,e={type:"link",raw:u,href:r,title:t,text:i,tokens:n.inlineTokens(i)},n.state.inLink=!1,e):{type:"image",raw:u,href:r,title:t,text:A(i)}}var b=function(){function e(e){this.options=e||r.defaults}var t=e.prototype;return t.space=function(e){e=this.rules.block.newline.exec(e);if(e&&0=r.length?e.slice(r.length):e}).join("\n")),{type:"code",raw:t,lang:e[2]&&e[2].trim().replace(this.rules.inline._escapes,"$1"),text:u}},t.heading=function(e){var t,u,e=this.rules.block.heading.exec(e);if(e)return t=e[2].trim(),/#$/.test(t)&&(u=C(t,"#"),!this.options.pedantic&&u&&!/ $/.test(u)||(t=u.trim())),{type:"heading",raw:e[0],depth:e[1].length,text:t,tokens:this.lexer.inline(t)}},t.hr=function(e){e=this.rules.block.hr.exec(e);if(e)return{type:"hr",raw:e[0]}},t.blockquote=function(e){var t,u,n,e=this.rules.block.blockquote.exec(e);if(e)return t=e[0].replace(/^ *>[ \t]?/gm,""),u=this.lexer.state.top,this.lexer.state.top=!0,n=this.lexer.blockTokens(t),this.lexer.state.top=u,{type:"blockquote",raw:e[0],tokens:n,text:t}},t.list=function(e){var t=this.rules.block.list.exec(e);if(t){var u,n,r,i,s,a,o,l,D,c,p,h=1<(g=t[1].trim()).length,f={type:"list",raw:"",ordered:h,start:h?+g.slice(0,-1):"",loose:!1,items:[]},g=h?"\\d{1,9}\\"+g.slice(-1):"\\"+g;this.options.pedantic&&(g=h?g:"[*+-]");for(var F=new RegExp("^( {0,3}"+g+")((?:[\t ][^\\n]*)?(?:\\n|$))");e&&(p=!1,t=F.exec(e))&&!this.rules.block.hr.test(e);){if(u=t[0],e=e.substring(u.length),o=t[2].split("\n",1)[0].replace(/^\t+/,function(e){return" ".repeat(3*e.length)}),l=e.split("\n",1)[0],this.options.pedantic?(i=2,c=o.trimLeft()):(i=t[2].search(/[^ ]/),c=o.slice(i=4=i||!l.trim())c+="\n"+l.slice(i);else{if(s)break;if(4<=o.search(/[^ ]/))break;if(k.test(o))break;if(C.test(o))break;if(A.test(o))break;c+="\n"+l}s||l.trim()||(s=!0),u+=D+"\n",e=e.substring(D.length+1),o=l.slice(i)}f.loose||(a?f.loose=!0:/\n *\n *$/.test(u)&&(a=!0)),this.options.gfm&&(n=/^\[[ xX]\] /.exec(c))&&(r="[ ] "!==n[0],c=c.replace(/^\[[ xX]\] +/,"")),f.items.push({type:"list_item",raw:u,task:!!n,checked:r,loose:!1,text:c}),f.raw+=u}f.items[f.items.length-1].raw=u.trimRight(),f.items[f.items.length-1].text=c.trimRight(),f.raw=f.raw.trimRight();for(var E,m=f.items.length,x=0;x$/,"$1").replace(this.rules.inline._escapes,"$1"):"",n=e[3]&&e[3].substring(1,e[3].length-1).replace(this.rules.inline._escapes,"$1"),{type:"def",tag:t,raw:e[0],href:u,title:n}},t.table=function(e){e=this.rules.block.table.exec(e);if(e){var t={type:"table",header:k(e[1]).map(function(e){return{text:e}}),align:e[2].replace(/^ *|\| *$/g,"").split(/ *\| */),rows:e[3]&&e[3].trim()?e[3].replace(/\n[ \t]*$/,"").split("\n"):[]};if(t.header.length===t.align.length){t.raw=e[0];for(var u,n,r,i=t.align.length,s=0;s/i.test(e[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(e[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(e[0])&&(this.lexer.state.inRawBlock=!1),{type:this.options.sanitize?"text":"html",raw:e[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):A(e[0]):e[0]}},t.link=function(e){e=this.rules.inline.link.exec(e);if(e){var t=e[2].trim();if(!this.options.pedantic&&/^$/.test(t))return;var u=C(t.slice(0,-1),"\\");if((t.length-u.length)%2==0)return}else{u=function(e,t){if(-1!==e.indexOf(t[1]))for(var u=e.length,n=0,r=0;r$/.test(t)?u.slice(1):u.slice(1,-1):u)&&u.replace(this.rules.inline._escapes,"$1"),title:r&&r.replace(this.rules.inline._escapes,"$1")},e[0],this.lexer)}},t.reflink=function(e,t){var u;if(u=(u=this.rules.inline.reflink.exec(e))||this.rules.inline.nolink.exec(e))return(e=t[(e=(u[2]||u[1]).replace(/\s+/g," ")).toLowerCase()])?x(u,e,u[0],this.lexer):{type:"text",raw:t=u[0].charAt(0),text:t}},t.emStrong=function(e,t,u){void 0===u&&(u="");var n=this.rules.inline.emStrong.lDelim.exec(e);if(n&&(!n[3]||!u.match(/(?:[0-9A-Za-z\xAA\xB2\xB3\xB5\xB9\xBA\xBC-\xBE\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088E\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09F9\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BF2\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C66-\u0C6F\u0C78-\u0C7E\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D58-\u0D61\u0D66-\u0D78\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F33\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1369-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2070\u2071\u2074-\u2079\u207F-\u2089\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2150-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2CFD\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3192-\u3195\u31A0-\u31BF\u31F0-\u31FF\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA830-\uA835\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDE80-\uDE9C\uDEA0-\uDED0\uDEE1-\uDEFB\uDF00-\uDF23\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC58-\uDC76\uDC79-\uDC9E\uDCA7-\uDCAF\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDD1B\uDD20-\uDD39\uDD80-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE40-\uDE48\uDE60-\uDE7E\uDE80-\uDE9F\uDEC0-\uDEC7\uDEC9-\uDEE4\uDEEB-\uDEEF\uDF00-\uDF35\uDF40-\uDF55\uDF58-\uDF72\uDF78-\uDF91\uDFA9-\uDFAF]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDD23\uDD30-\uDD39\uDE60-\uDE7E\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF27\uDF30-\uDF45\uDF51-\uDF54\uDF70-\uDF81\uDFB0-\uDFCB\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC52-\uDC6F\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDDE1-\uDDF4\uDE00-\uDE11\uDE13-\uDE2B\uDE3F\uDE40\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF30-\uDF3B\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCF2\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC6C\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF2\uDF02\uDF04-\uDF10\uDF12-\uDF33\uDF50-\uDF59\uDFB0\uDFC0-\uDFD4]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883\uD885-\uD887][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2F\uDC41-\uDC46]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDE70-\uDEBE\uDEC0-\uDEC9\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE96\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD32\uDD50-\uDD52\uDD55\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD834[\uDEC0-\uDED3\uDEE0-\uDEF3\uDF60-\uDF78]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD837[\uDF00-\uDF1E\uDF25-\uDF2A]|\uD838[\uDC30-\uDC6D\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD839[\uDCD0-\uDCEB\uDCF0-\uDCF9\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDCC7-\uDCCF\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83C[\uDD00-\uDD0C]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF39\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A\uDF50-\uDFFF]|\uD888[\uDC00-\uDFAF])/))){var r=n[1]||n[2]||"";if(!r||""===u||this.rules.inline.punctuation.exec(u)){var i=n[0].length-1,s=i,a=0,o="*"===n[0][0]?this.rules.inline.emStrong.rDelimAst:this.rules.inline.emStrong.rDelimUnd;for(o.lastIndex=0,t=t.slice(-1*e.length+i);null!=(n=o.exec(t));){var l,D=n[1]||n[2]||n[3]||n[4]||n[5]||n[6];if(D)if(l=D.length,n[3]||n[4])s+=l;else if((n[5]||n[6])&&i%3&&!((i+l)%3))a+=l;else if(!(0<(s-=l)))return l=Math.min(l,l+s+a),D=e.slice(0,i+n.index+(n[0].length-D.length)+l),Math.min(i,l)%2?(l=D.slice(1,-1),{type:"em",raw:D,text:l,tokens:this.lexer.inlineTokens(l)}):(l=D.slice(2,-2),{type:"strong",raw:D,text:l,tokens:this.lexer.inlineTokens(l)})}}}},t.codespan=function(e){var t,u,n,e=this.rules.inline.code.exec(e);if(e)return n=e[2].replace(/\n/g," "),t=/[^ ]/.test(n),u=/^ /.test(n)&&/ $/.test(n),n=A(n=t&&u?n.substring(1,n.length-1):n,!0),{type:"codespan",raw:e[0],text:n}},t.br=function(e){e=this.rules.inline.br.exec(e);if(e)return{type:"br",raw:e[0]}},t.del=function(e){e=this.rules.inline.del.exec(e);if(e)return{type:"del",raw:e[0],text:e[2],tokens:this.lexer.inlineTokens(e[2])}},t.autolink=function(e,t){var u,e=this.rules.inline.autolink.exec(e);if(e)return t="@"===e[2]?"mailto:"+(u=A(this.options.mangle?t(e[1]):e[1])):u=A(e[1]),{type:"link",raw:e[0],text:u,href:t,tokens:[{type:"text",raw:u,text:u}]}},t.url=function(e,t){var u,n,r,i;if(u=this.rules.inline.url.exec(e)){if("@"===u[2])r="mailto:"+(n=A(this.options.mangle?t(u[0]):u[0]));else{for(;i=u[0],u[0]=this.rules.inline._backpedal.exec(u[0])[0],i!==u[0];);n=A(u[0]),r="www."===u[1]?"http://"+u[0]:u[0]}return{type:"link",raw:u[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}}},t.inlineText=function(e,t){e=this.rules.inline.text.exec(e);if(e)return t=this.lexer.state.inRawBlock?this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):A(e[0]):e[0]:A(this.options.smartypants?t(e[0]):e[0]),{type:"text",raw:e[0],text:t}},e}(),B={newline:/^(?: *(?:\n|$))+/,code:/^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,fences:/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,hr:/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/,html:"^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n *)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$))",def:/^ {0,3}\[(label)\]: *(?:\n *)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/,table:F,lheading:/^((?:.|\n(?!\n))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,_paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,text:/^[^\n]+/,_label:/(?!\s*\])(?:\\.|[^\[\]\\])+/,_title:/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/},w=(B.def=h(B.def).replace("label",B._label).replace("title",B._title).getRegex(),B.bullet=/(?:[*+-]|\d{1,9}[.)])/,B.listItemStart=h(/^( *)(bull) */).replace("bull",B.bullet).getRegex(),B.list=h(B.list).replace(/bull/g,B.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+B.def.source+")").getRegex(),B._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",B._comment=/|$)/,B.html=h(B.html,"i").replace("comment",B._comment).replace("tag",B._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),B.paragraph=h(B._paragraph).replace("hr",B.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",B._tag).getRegex(),B.blockquote=h(B.blockquote).replace("paragraph",B.paragraph).getRegex(),B.normal=d({},B),B.gfm=d({},B.normal,{table:"^ *([^\\n ].*\\|.*)\\n {0,3}(?:\\| *)?(:?-+:? *(?:\\| *:?-+:? *)*)(?:\\| *)?(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"}),B.gfm.table=h(B.gfm.table).replace("hr",B.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",B._tag).getRegex(),B.gfm.paragraph=h(B._paragraph).replace("hr",B.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("table",B.gfm.table).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",B._tag).getRegex(),B.pedantic=d({},B.normal,{html:h("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",B._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:F,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:h(B.normal._paragraph).replace("hr",B.hr).replace("heading"," *#{1,6} *[^\n]").replace("lheading",B.lheading).replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").getRegex()}),{escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:F,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(ref)\]/,nolink:/^!?\[(ref)\](?:\[\])?/,reflinkSearch:"reflink|nolink(?!\\()",emStrong:{lDelim:/^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/,rDelimAst:/^(?:[^_*\\]|\\.)*?\_\_(?:[^_*\\]|\\.)*?\*(?:[^_*\\]|\\.)*?(?=\_\_)|(?:[^*\\]|\\.)+(?=[^*])|[punct_](\*+)(?=[\s]|$)|(?:[^punct*_\s\\]|\\.)(\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|(?:[^punct*_\s\\]|\\.)(\*+)(?=[^punct*_\s])/,rDelimUnd:/^(?:[^_*\\]|\\.)*?\*\*(?:[^_*\\]|\\.)*?\_(?:[^_*\\]|\\.)*?(?=\*\*)|(?:[^_\\]|\\.)+(?=[^_])|[punct*](\_+)(?=[\s]|$)|(?:[^punct*_\s\\]|\\.)(\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/},code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:F,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@\\[\\]`^{|}~",w.punctuation=h(w.punctuation).replace(/punctuation/g,w._punctuation).getRegex(),w.blockSkip=/\[[^\]]*?\]\([^\)]*?\)|`[^`]*?`|<[^>]*?>/g,w.escapedEmSt=/(?:^|[^\\])(?:\\\\)*\\[*_]/g,w._comment=h(B._comment).replace("(?:--\x3e|$)","--\x3e").getRegex(),w.emStrong.lDelim=h(w.emStrong.lDelim).replace(/punct/g,w._punctuation).getRegex(),w.emStrong.rDelimAst=h(w.emStrong.rDelimAst,"g").replace(/punct/g,w._punctuation).getRegex(),w.emStrong.rDelimUnd=h(w.emStrong.rDelimUnd,"g").replace(/punct/g,w._punctuation).getRegex(),w._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,w._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,w._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,w.autolink=h(w.autolink).replace("scheme",w._scheme).replace("email",w._email).getRegex(),w._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,w.tag=h(w.tag).replace("comment",w._comment).replace("attribute",w._attribute).getRegex(),w._label=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,w._href=/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/,w._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,w.link=h(w.link).replace("label",w._label).replace("href",w._href).replace("title",w._title).getRegex(),w.reflink=h(w.reflink).replace("label",w._label).replace("ref",B._label).getRegex(),w.nolink=h(w.nolink).replace("ref",B._label).getRegex(),w.reflinkSearch=h(w.reflinkSearch,"g").replace("reflink",w.reflink).replace("nolink",w.nolink).getRegex(),w.normal=d({},w),w.pedantic=d({},w.normal,{strong:{start:/^__|\*\*/,middle:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,endAst:/\*\*(?!\*)/g,endUnd:/__(?!_)/g},em:{start:/^_|\*/,middle:/^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,endAst:/\*(?!\*)/g,endUnd:/_(?!_)/g},link:h(/^!?\[(label)\]\((.*?)\)/).replace("label",w._label).getRegex(),reflink:h(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",w._label).getRegex()}),w.gfm=d({},w.normal,{escape:h(w.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\'+(u?e:A(e,!0))+"\n":"
"+(u?e:A(e,!0))+"
\n"},t.blockquote=function(e){return"
\n"+e+"
\n"},t.html=function(e,t){return e},t.heading=function(e,t,u,n){return this.options.headerIds?"'+e+"\n":""+e+"\n"},t.hr=function(){return this.options.xhtml?"
\n":"
\n"},t.list=function(e,t,u){var n=t?"ol":"ul";return"<"+n+(t&&1!==u?' start="'+u+'"':"")+">\n"+e+"\n"},t.listitem=function(e){return"
  • "+e+"
  • \n"},t.checkbox=function(e){return" "},t.paragraph=function(e){return"

    "+e+"

    \n"},t.table=function(e,t){return"\n\n"+e+"\n"+(t=t&&""+t+"")+"
    \n"},t.tablerow=function(e){return"\n"+e+"\n"},t.tablecell=function(e,t){var u=t.header?"th":"td";return(t.align?"<"+u+' align="'+t.align+'">':"<"+u+">")+e+"\n"},t.strong=function(e){return""+e+""},t.em=function(e){return""+e+""},t.codespan=function(e){return""+e+""},t.br=function(){return this.options.xhtml?"
    ":"
    "},t.del=function(e){return""+e+""},t.link=function(e,t,u){return null===(e=f(this.options.sanitize,this.options.baseUrl,e))?u:(e='
    "+u+"")},t.image=function(e,t,u){return null===(e=f(this.options.sanitize,this.options.baseUrl,e))?u:(e=''+u+'":">"))},t.text=function(e){return e},e}(),z=function(){function e(){}var t=e.prototype;return t.strong=function(e){return e},t.em=function(e){return e},t.codespan=function(e){return e},t.del=function(e){return e},t.html=function(e){return e},t.text=function(e){return e},t.link=function(e,t,u){return""+u},t.image=function(e,t,u){return""+u},t.br=function(){return""},e}(),$=function(){function e(){this.seen={}}var t=e.prototype;return t.serialize=function(e){return e.toLowerCase().trim().replace(/<[!\/a-z].*?>/gi,"").replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,"").replace(/\s/g,"-")},t.getNextSafeSlug=function(e,t){var u=e,n=0;if(this.seen.hasOwnProperty(u))for(n=this.seen[e];u=e+"-"+ ++n,this.seen.hasOwnProperty(u););return t||(this.seen[e]=n,this.seen[u]=0),u},t.slug=function(e,t){void 0===t&&(t={});e=this.serialize(e);return this.getNextSafeSlug(e,t.dryrun)},e}(),S=function(){function u(e){this.options=e||r.defaults,this.options.renderer=this.options.renderer||new _,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new z,this.slugger=new $}u.parse=function(e,t){return new u(t).parse(e)},u.parseInline=function(e,t){return new u(t).parseInline(e)};var e=u.prototype;return e.parse=function(e,t){void 0===t&&(t=!0);for(var u,n,r,i,s,a,o,l,D,c,p,h,f,g,F,d,A="",k=e.length,C=0;C",i?Promise.resolve(t):s?void s(null,t):t;if(i)return Promise.reject(e);if(!s)throw e;s(e)});if(null==e)return o(new Error("marked(): input parameter is undefined or null"));if("string"!=typeof e)return o(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(e)+", string expected"));if(a=n,(t=u)&&!t.silent&&(a&&console.warn("marked(): callback is deprecated since version 5.0.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/using_pro#async"),(t.sanitize||t.sanitizer)&&console.warn("marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options"),!t.highlight&&"language-"===t.langPrefix||console.warn("marked(): highlight and langPrefix parameters are deprecated since version 5.0.0, should not be used and will be removed in the future. Instead use https://www.npmjs.com/package/marked-highlight."),t.mangle&&console.warn("marked(): mangle parameter is enabled by default, but is deprecated since version 5.0.0, and will be removed in the future. To clear this warning, install https://www.npmjs.com/package/marked-mangle, or disable by setting `{mangle: false}`."),t.baseUrl&&console.warn("marked(): baseUrl parameter is deprecated since version 5.0.0, should not be used and will be removed in the future. Instead use https://www.npmjs.com/package/marked-base-url."),t.smartypants&&console.warn("marked(): smartypants parameter is deprecated since version 5.0.0, should not be used and will be removed in the future. Instead use https://www.npmjs.com/package/marked-smartypants."),t.xhtml&&console.warn("marked(): xhtml parameter is deprecated since version 5.0.0, should not be used and will be removed in the future. Instead use https://www.npmjs.com/package/marked-xhtml."),t.headerIds||t.headerPrefix)&&console.warn("marked(): headerIds and headerPrefix parameters enabled by default, but are deprecated since version 5.0.0, and will be removed in the future. To clear this warning, install https://www.npmjs.com/package/marked-gfm-heading-id, or disable by setting `{headerIds: false}`."),u.hooks&&(u.hooks.options=u),n){var l,D=u.highlight;try{u.hooks&&(e=u.hooks.preprocess(e)),l=g(e,u)}catch(e){return o(e)}var c,p=function(t){var e;if(!t)try{u.walkTokens&&I.walkTokens(l,u.walkTokens),e=F(l,u),u.hooks&&(e=u.hooks.postprocess(e))}catch(e){t=e}return u.highlight=D,t?o(t):n(null,e)};return!D||D.length<3?p():(delete u.highlight,l.length?(c=0,I.walkTokens(l,function(u){"code"===u.type&&(c++,setTimeout(function(){D(u.text,u.lang,function(e,t){if(e)return p(e);null!=t&&t!==u.text&&(u.text=t,u.escaped=!0),0===--c&&p()})},0))}),void(0===c&&p())):p())}if(u.async)return Promise.resolve(u.hooks?u.hooks.preprocess(e):e).then(function(e){return g(e,u)}).then(function(e){return u.walkTokens?Promise.all(I.walkTokens(e,u.walkTokens)).then(function(){return e}):e}).then(function(e){return F(e,u)}).then(function(e){return u.hooks?u.hooks.postprocess(e):e}).catch(o);try{u.hooks&&(e=u.hooks.preprocess(e));var h=g(e,u),f=(u.walkTokens&&I.walkTokens(h,u.walkTokens),F(h,u));return f=u.hooks?u.hooks.postprocess(f):f}catch(e){return o(e)}}}function I(e,t,u){return R(y.lex,S.parse)(e,t,u)}T.passThroughHooks=new Set(["preprocess","postprocess"]),I.options=I.setOptions=function(e){return I.defaults=d({},I.defaults,e),e=I.defaults,r.defaults=e,I},I.getDefaults=e,I.defaults=r.defaults,I.use=function(){for(var D=I.defaults.extensions||{renderers:{},childTokens:{}},e=arguments.length,t=new Array(e),u=0;u + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Factorial from './Factorial.js'; + +/** + * Calculates the Bernstein basis from the three factorial coefficients. + * + * @function Phaser.Math.Bernstein + * @since 3.0.0 + * + * @param {number} n - The first value. + * @param {number} i - The second value. + * + * @return {number} The Bernstein basis of Factorial(n) / Factorial(i) / Factorial(n - i) + */ +var Bernstein = function (n, i) { + return Factorial(n) / Factorial(i) / Factorial(n - i); +}; + +export default Bernstein; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/Between.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/Between.js new file mode 100644 index 000000000..50a326fbe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/Between.js @@ -0,0 +1,23 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Compute a random integer between the `min` and `max` values, inclusive. + * + * @function Phaser.Math.Between + * @since 3.0.0 + * + * @param {integer} min - The minimum value. + * @param {integer} max - The maximum value. + * + * @return {integer} The random integer. + */ +var Between = function (min, max) +{ + return Math.floor(Math.random() * (max - min + 1) + min); +}; + +export default Between; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/ByteArrayToUint32.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/ByteArrayToUint32.js new file mode 100644 index 000000000..3e469fb85 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/ByteArrayToUint32.js @@ -0,0 +1,14 @@ +var ByteArrayToUint32 = function (a, b, c, d, bigEndian) { + if (bigEndian === undefined) { + bigEndian = false; + } + var value; + if (bigEndian) { + value = (a << 24) | (b << 16) | (c << 8) | d; + } else { + value = a | (b << 8) | (c << 16) | (d << 24); + } + + return value; +} +export default ByteArrayToUint32; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/CatmullRom.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/CatmullRom.js new file mode 100644 index 000000000..6d1a2cf2f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/CatmullRom.js @@ -0,0 +1,30 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculates a Catmull-Rom value from the given points, based on an alpha of 0.5. + * + * @function Phaser.Math.CatmullRom + * @since 3.0.0 + * + * @param {number} t - The amount to interpolate by. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @param {number} p3 - The fourth control point. + * + * @return {number} The Catmull-Rom value. + */ +var CatmullRom = function (t, p0, p1, p2, p3) { + var v0 = (p2 - p0) * 0.5; + var v1 = (p3 - p1) * 0.5; + var t2 = t * t; + var t3 = t * t2; + + return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; +}; + +export default CatmullRom; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/Clamp.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/Clamp.js new file mode 100644 index 000000000..70dd77f7f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/Clamp.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Force a value within the boundaries by clamping it to the range `min`, `max`. + * + * @function Phaser.Math.Clamp + * @since 3.0.0 + * + * @param {number} value - The value to be clamped. + * @param {number} min - The minimum bounds. + * @param {number} max - The maximum bounds. + * + * @return {number} The clamped value. + */ +var Clamp = function (value, min, max) +{ + return Math.max(min, Math.min(max, value)); +}; + +export default Clamp; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/DegToRad.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/DegToRad.js new file mode 100644 index 000000000..a7ae08951 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/DegToRad.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +const DEG_TO_RAD = Math.PI / 180; + +/** + * Convert the given angle from degrees, to the equivalent angle in radians. + * + * @function Phaser.Math.DegToRad + * @since 3.0.0 + * + * @param {integer} degrees - The angle (in degrees) to convert to radians. + * + * @return {number} The given angle converted to radians. + */ +var DegToRad = function (degrees) +{ + return degrees * DEG_TO_RAD; +}; + +export default DegToRad; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/Factorial.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/Factorial.js new file mode 100644 index 000000000..38f1b337d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/Factorial.js @@ -0,0 +1,31 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculates the factorial of a given number for integer values greater than 0. + * + * @function Phaser.Math.Factorial + * @since 3.0.0 + * + * @param {number} value - A positive integer to calculate the factorial of. + * + * @return {number} The factorial of the given number. + */ +var Factorial = function (value) { + if (value === 0) { + return 1; + } + + var res = value; + + while (--value) { + res *= value; + } + + return res; +}; + +export default Factorial; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/FromPercent.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/FromPercent.js new file mode 100644 index 000000000..591a11516 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/FromPercent.js @@ -0,0 +1,27 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Clamp from './Clamp.js'; + +/** + * Return a value based on the range between `min` and `max` and the percentage given. + * + * @function Phaser.Math.FromPercent + * @since 3.0.0 + * + * @param {number} percent - A value between 0 and 1 representing the percentage. + * @param {number} min - The minimum value. + * @param {number} [max] - The maximum value. + * + * @return {number} The value that is `percent` percent between `min` and `max`. + */ +var FromPercent = function (percent, min, max) { + percent = Clamp(percent, 0, 1); + + return (max - min) * percent; +}; + +export default FromPercent; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/Linear.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/Linear.js new file mode 100644 index 000000000..7a0046624 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/Linear.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Calculates a linear (interpolation) value over t. + * + * @function Phaser.Math.Linear + * @since 3.0.0 + * + * @param {number} p0 - The first point. + * @param {number} p1 - The second point. + * @param {number} t - The percentage between p0 and p1 to return, represented as a number between 0 and 1. + * + * @return {number} The step t% of the way between p0 and p1. + */ +var Linear = function (p0, p1, t) +{ + return (p1 - p0) * t + p0; +}; + +export default Linear; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/MapRange.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/MapRange.js new file mode 100644 index 000000000..51dbed673 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/MapRange.js @@ -0,0 +1,6 @@ +var MapRange = function (value, start1, end1, start2, end2) { + var p = (value - start1) / (end1 - start1); + return start2 + (p * (end2 - start2)); +} + +export default MapRange; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/RadToDeg.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/RadToDeg.js new file mode 100644 index 000000000..bda3f8f61 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/RadToDeg.js @@ -0,0 +1,24 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +var RAD_TO_DEG = 180 / Math.PI; + +/** + * Convert the given angle in radians, to the equivalent angle in degrees. + * + * @function Phaser.Math.RadToDeg + * @since 3.0.0 + * + * @param {number} radians - The angle in radians to convert ot degrees. + * + * @return {integer} The given angle converted to degrees. + */ +var RadToDeg = function (radians) +{ + return radians * RAD_TO_DEG; +}; + +export default RadToDeg; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/RotateAround.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/RotateAround.js new file mode 100644 index 000000000..e9f4b556a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/RotateAround.js @@ -0,0 +1,37 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Rotate a `point` around `x` and `y` to the given `angle`, at the same distance. + * + * In polar notation, this maps a point from (r, t) to (r, angle), vs. the origin (x, y). + * + * @function Phaser.Math.RotateAround + * @since 3.0.0 + * + * @generic {Phaser.Types.Math.Vector2Like} T - [point,$return] + * + * @param {(Phaser.Geom.Point|object)} point - The point to be rotated. + * @param {number} x - The horizontal coordinate to rotate around. + * @param {number} y - The vertical coordinate to rotate around. + * @param {number} angle - The angle of rotation in radians. + * + * @return {Phaser.Types.Math.Vector2Like} The given point. + */ +var RotateAround = function (point, x, y, angle) { + var c = Math.cos(angle); + var s = Math.sin(angle); + + var tx = point.x - x; + var ty = point.y - y; + + point.x = tx * c - ty * s + x; + point.y = tx * s + ty * c + y; + + return point; +}; + +export default RotateAround; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/RoundUpPowerOf2.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/RoundUpPowerOf2.js new file mode 100644 index 000000000..07cfd9b1f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/RoundUpPowerOf2.js @@ -0,0 +1,12 @@ +var NearestPowerOf2 = function (value) { + value--; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value++; + return value; +} + +export default NearestPowerOf2; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/SmoothStep.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/SmoothStep.js new file mode 100644 index 000000000..cbadd5fb7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/SmoothStep.js @@ -0,0 +1,38 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculate a smooth interpolation percentage of `x` between `min` and `max`. + * + * The function receives the number `x` as an argument and returns 0 if `x` is less than or equal to the left edge, + * 1 if `x` is greater than or equal to the right edge, and smoothly interpolates, using a Hermite polynomial, + * between 0 and 1 otherwise. + * + * @function Phaser.Math.SmoothStep + * @since 3.0.0 + * @see {@link https://en.wikipedia.org/wiki/Smoothstep} + * + * @param {number} x - The input value. + * @param {number} min - The minimum value, also known as the 'left edge', assumed smaller than the 'right edge'. + * @param {number} max - The maximum value, also known as the 'right edge', assumed greater than the 'left edge'. + * + * @return {number} The percentage of interpolation, between 0 and 1. + */ +var SmoothStep = function (x, min, max) { + if (x <= min) { + return 0; + } + + if (x >= max) { + return 1; + } + + x = (x - min) / (max - min); + + return x * x * (3 - 2 * x); +}; + +export default SmoothStep; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/SmootherStep.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/SmootherStep.js new file mode 100644 index 000000000..4ee1a82cc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/SmootherStep.js @@ -0,0 +1,32 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Calculate a smoother interpolation percentage of `x` between `min` and `max`. + * + * The function receives the number `x` as an argument and returns 0 if `x` is less than or equal to the left edge, + * 1 if `x` is greater than or equal to the right edge, and smoothly interpolates, using a Hermite polynomial, + * between 0 and 1 otherwise. + * + * Produces an even smoother interpolation than {@link Phaser.Math.SmoothStep}. + * + * @function Phaser.Math.SmootherStep + * @since 3.0.0 + * @see {@link https://en.wikipedia.org/wiki/Smoothstep#Variations} + * + * @param {number} x - The input value. + * @param {number} min - The minimum value, also known as the 'left edge', assumed smaller than the 'right edge'. + * @param {number} max - The maximum value, also known as the 'right edge', assumed greater than the 'left edge'. + * + * @return {number} The percentage of interpolation, between 0 and 1. + */ +var SmootherStep = function (x, min, max) { + x = Math.max(0, Math.min(1, (x - min) / (max - min))); + + return x * x * x * (x * (x * 6 - 15) + 10); +}; + +export default SmootherStep; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/Sum.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/Sum.js new file mode 100644 index 000000000..e71de0a62 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/Sum.js @@ -0,0 +1,9 @@ +var Sum = function () { + return Array.prototype.reduce.call(arguments, Add, 0); +} + +var Add = function (a, b) { + return a + b; +} + +export default Sum; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/Uint32ToByteArray.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/Uint32ToByteArray.js new file mode 100644 index 000000000..953dd47c9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/Uint32ToByteArray.js @@ -0,0 +1,26 @@ +var Uint32ToByteArray = function (value, bigEndian, output) { + if (bigEndian === undefined) { + bigEndian = false; + } + + if (output === undefined) { + output = []; + } + output.length = 4; + + if (bigEndian) { + output[0] = (value >> 24) & 0xff; + output[1] = (value >> 16) & 0xff; + output[2] = (value >> 8) & 0xff; + output[3] = value & 0xff; + } else { + output[0] = value & 0xff; + output[1] = (value >> 8) & 0xff; + output[2] = (value >> 16) & 0xff; + output[3] = (value >> 24) & 0xff; + } + + return output; +} + +export default Uint32ToByteArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/Vector2.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/Vector2.js new file mode 100644 index 000000000..f851017f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/Vector2.js @@ -0,0 +1,600 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +// Adapted from [gl-matrix](https://github.com/toji/gl-matrix) by toji +// and [vecmath](https://github.com/mattdesl/vecmath) by mattdesl + +import Class from '../object/Class.js'; + +/** + * @classdesc + * A representation of a vector in 2D space. + * + * A two-component vector. + * + * @class Vector2 + * @memberof Phaser.Math + * @constructor + * @since 3.0.0 + * + * @param {number|Phaser.Types.Math.Vector2Like} [x] - The x component, or an object with `x` and `y` properties. + * @param {number} [y] - The y component. + */ +var Vector2 = new Class({ + + initialize: + + function Vector2(x, y) { + /** + * The x component of this Vector. + * + * @name Phaser.Math.Vector2#x + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.x = 0; + + /** + * The y component of this Vector. + * + * @name Phaser.Math.Vector2#y + * @type {number} + * @default 0 + * @since 3.0.0 + */ + this.y = 0; + + if (typeof x === 'object') { + this.x = x.x || 0; + this.y = x.y || 0; + } + else { + if (y === undefined) { y = x; } + + this.x = x || 0; + this.y = y || 0; + } + }, + + /** + * Make a clone of this Vector2. + * + * @method Phaser.Math.Vector2#clone + * @since 3.0.0 + * + * @return {Phaser.Math.Vector2} A clone of this Vector2. + */ + clone: function () { + return new Vector2(this.x, this.y); + }, + + /** + * Copy the components of a given Vector into this Vector. + * + * @method Phaser.Math.Vector2#copy + * @since 3.0.0 + * + * @param {Phaser.Math.Vector2} src - The Vector to copy the components from. + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + copy: function (src) { + this.x = src.x || 0; + this.y = src.y || 0; + + return this; + }, + + /** + * Set the component values of this Vector from a given Vector2Like object. + * + * @method Phaser.Math.Vector2#setFromObject + * @since 3.0.0 + * + * @param {Phaser.Types.Math.Vector2Like} obj - The object containing the component values to set for this Vector. + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + setFromObject: function (obj) { + this.x = obj.x || 0; + this.y = obj.y || 0; + + return this; + }, + + /** + * Set the `x` and `y` components of the this Vector to the given `x` and `y` values. + * + * @method Phaser.Math.Vector2#set + * @since 3.0.0 + * + * @param {number} x - The x value to set for this Vector. + * @param {number} [y=x] - The y value to set for this Vector. + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + set: function (x, y) { + if (y === undefined) { y = x; } + + this.x = x; + this.y = y; + + return this; + }, + + /** + * This method is an alias for `Vector2.set`. + * + * @method Phaser.Math.Vector2#setTo + * @since 3.4.0 + * + * @param {number} x - The x value to set for this Vector. + * @param {number} [y=x] - The y value to set for this Vector. + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + setTo: function (x, y) { + return this.set(x, y); + }, + + /** + * Sets the `x` and `y` values of this object from a given polar coordinate. + * + * @method Phaser.Math.Vector2#setToPolar + * @since 3.0.0 + * + * @param {number} azimuth - The angular coordinate, in radians. + * @param {number} [radius=1] - The radial coordinate (length). + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + setToPolar: function (azimuth, radius) { + if (radius == null) { radius = 1; } + + this.x = Math.cos(azimuth) * radius; + this.y = Math.sin(azimuth) * radius; + + return this; + }, + + /** + * Check whether this Vector is equal to a given Vector. + * + * Performs a strict equality check against each Vector's components. + * + * @method Phaser.Math.Vector2#equals + * @since 3.0.0 + * + * @param {Phaser.Math.Vector2} v - The vector to compare with this Vector. + * + * @return {boolean} Whether the given Vector is equal to this Vector. + */ + equals: function (v) { + return ((this.x === v.x) && (this.y === v.y)); + }, + + /** + * Calculate the angle between this Vector and the positive x-axis, in radians. + * + * @method Phaser.Math.Vector2#angle + * @since 3.0.0 + * + * @return {number} The angle between this Vector, and the positive x-axis, given in radians. + */ + angle: function () { + // computes the angle in radians with respect to the positive x-axis + + var angle = Math.atan2(this.y, this.x); + + if (angle < 0) { + angle += 2 * Math.PI; + } + + return angle; + }, + + /** + * Add a given Vector to this Vector. Addition is component-wise. + * + * @method Phaser.Math.Vector2#add + * @since 3.0.0 + * + * @param {Phaser.Math.Vector2} src - The Vector to add to this Vector. + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + add: function (src) { + this.x += src.x; + this.y += src.y; + + return this; + }, + + /** + * Subtract the given Vector from this Vector. Subtraction is component-wise. + * + * @method Phaser.Math.Vector2#subtract + * @since 3.0.0 + * + * @param {Phaser.Math.Vector2} src - The Vector to subtract from this Vector. + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + subtract: function (src) { + this.x -= src.x; + this.y -= src.y; + + return this; + }, + + /** + * Perform a component-wise multiplication between this Vector and the given Vector. + * + * Multiplies this Vector by the given Vector. + * + * @method Phaser.Math.Vector2#multiply + * @since 3.0.0 + * + * @param {Phaser.Math.Vector2} src - The Vector to multiply this Vector by. + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + multiply: function (src) { + this.x *= src.x; + this.y *= src.y; + + return this; + }, + + /** + * Scale this Vector by the given value. + * + * @method Phaser.Math.Vector2#scale + * @since 3.0.0 + * + * @param {number} value - The value to scale this Vector by. + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + scale: function (value) { + if (isFinite(value)) { + this.x *= value; + this.y *= value; + } + else { + this.x = 0; + this.y = 0; + } + + return this; + }, + + /** + * Perform a component-wise division between this Vector and the given Vector. + * + * Divides this Vector by the given Vector. + * + * @method Phaser.Math.Vector2#divide + * @since 3.0.0 + * + * @param {Phaser.Math.Vector2} src - The Vector to divide this Vector by. + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + divide: function (src) { + this.x /= src.x; + this.y /= src.y; + + return this; + }, + + /** + * Negate the `x` and `y` components of this Vector. + * + * @method Phaser.Math.Vector2#negate + * @since 3.0.0 + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + negate: function () { + this.x = -this.x; + this.y = -this.y; + + return this; + }, + + /** + * Calculate the distance between this Vector and the given Vector. + * + * @method Phaser.Math.Vector2#distance + * @since 3.0.0 + * + * @param {Phaser.Math.Vector2} src - The Vector to calculate the distance to. + * + * @return {number} The distance from this Vector to the given Vector. + */ + distance: function (src) { + var dx = src.x - this.x; + var dy = src.y - this.y; + + return Math.sqrt(dx * dx + dy * dy); + }, + + /** + * Calculate the distance between this Vector and the given Vector, squared. + * + * @method Phaser.Math.Vector2#distanceSq + * @since 3.0.0 + * + * @param {Phaser.Math.Vector2} src - The Vector to calculate the distance to. + * + * @return {number} The distance from this Vector to the given Vector, squared. + */ + distanceSq: function (src) { + var dx = src.x - this.x; + var dy = src.y - this.y; + + return dx * dx + dy * dy; + }, + + /** + * Calculate the length (or magnitude) of this Vector. + * + * @method Phaser.Math.Vector2#length + * @since 3.0.0 + * + * @return {number} The length of this Vector. + */ + length: function () { + var x = this.x; + var y = this.y; + + return Math.sqrt(x * x + y * y); + }, + + /** + * Calculate the length of this Vector squared. + * + * @method Phaser.Math.Vector2#lengthSq + * @since 3.0.0 + * + * @return {number} The length of this Vector, squared. + */ + lengthSq: function () { + var x = this.x; + var y = this.y; + + return x * x + y * y; + }, + + /** + * Normalize this Vector. + * + * Makes the vector a unit length vector (magnitude of 1) in the same direction. + * + * @method Phaser.Math.Vector2#normalize + * @since 3.0.0 + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + normalize: function () { + var x = this.x; + var y = this.y; + var len = x * x + y * y; + + if (len > 0) { + len = 1 / Math.sqrt(len); + + this.x = x * len; + this.y = y * len; + } + + return this; + }, + + /** + * Right-hand normalize (make unit length) this Vector. + * + * @method Phaser.Math.Vector2#normalizeRightHand + * @since 3.0.0 + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + normalizeRightHand: function () { + var x = this.x; + + this.x = this.y * -1; + this.y = x; + + return this; + }, + + /** + * Calculate the dot product of this Vector and the given Vector. + * + * @method Phaser.Math.Vector2#dot + * @since 3.0.0 + * + * @param {Phaser.Math.Vector2} src - The Vector2 to dot product with this Vector2. + * + * @return {number} The dot product of this Vector and the given Vector. + */ + dot: function (src) { + return this.x * src.x + this.y * src.y; + }, + + /** + * Calculate the cross product of this Vector and the given Vector. + * + * @method Phaser.Math.Vector2#cross + * @since 3.0.0 + * + * @param {Phaser.Math.Vector2} src - The Vector2 to cross with this Vector2. + * + * @return {number} The cross product of this Vector and the given Vector. + */ + cross: function (src) { + return this.x * src.y - this.y * src.x; + }, + + /** + * Linearly interpolate between this Vector and the given Vector. + * + * Interpolates this Vector towards the given Vector. + * + * @method Phaser.Math.Vector2#lerp + * @since 3.0.0 + * + * @param {Phaser.Math.Vector2} src - The Vector2 to interpolate towards. + * @param {number} [t=0] - The interpolation percentage, between 0 and 1. + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + lerp: function (src, t) { + if (t === undefined) { t = 0; } + + var ax = this.x; + var ay = this.y; + + this.x = ax + t * (src.x - ax); + this.y = ay + t * (src.y - ay); + + return this; + }, + + /** + * Transform this Vector with the given Matrix. + * + * @method Phaser.Math.Vector2#transformMat3 + * @since 3.0.0 + * + * @param {Phaser.Math.Matrix3} mat - The Matrix3 to transform this Vector2 with. + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + transformMat3: function (mat) { + var x = this.x; + var y = this.y; + var m = mat.val; + + this.x = m[0] * x + m[3] * y + m[6]; + this.y = m[1] * x + m[4] * y + m[7]; + + return this; + }, + + /** + * Transform this Vector with the given Matrix. + * + * @method Phaser.Math.Vector2#transformMat4 + * @since 3.0.0 + * + * @param {Phaser.Math.Matrix4} mat - The Matrix4 to transform this Vector2 with. + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + transformMat4: function (mat) { + var x = this.x; + var y = this.y; + var m = mat.val; + + this.x = m[0] * x + m[4] * y + m[12]; + this.y = m[1] * x + m[5] * y + m[13]; + + return this; + }, + + /** + * Make this Vector the zero vector (0, 0). + * + * @method Phaser.Math.Vector2#reset + * @since 3.0.0 + * + * @return {Phaser.Math.Vector2} This Vector2. + */ + reset: function () { + this.x = 0; + this.y = 0; + + return this; + } + +}); + +/** + * A static zero Vector2 for use by reference. + * + * This constant is meant for comparison operations and should not be modified directly. + * + * @constant + * @name Phaser.Math.Vector2.ZERO + * @type {Phaser.Math.Vector2} + * @since 3.1.0 + */ +Vector2.ZERO = new Vector2(); + +/** + * A static right Vector2 for use by reference. + * + * This constant is meant for comparison operations and should not be modified directly. + * + * @constant + * @name Phaser.Math.Vector2.RIGHT + * @type {Phaser.Math.Vector2} + * @since 3.16.0 + */ +Vector2.RIGHT = new Vector2(1, 0); + +/** + * A static left Vector2 for use by reference. + * + * This constant is meant for comparison operations and should not be modified directly. + * + * @constant + * @name Phaser.Math.Vector2.LEFT + * @type {Phaser.Math.Vector2} + * @since 3.16.0 + */ +Vector2.LEFT = new Vector2(-1, 0); + +/** + * A static up Vector2 for use by reference. + * + * This constant is meant for comparison operations and should not be modified directly. + * + * @constant + * @name Phaser.Math.Vector2.UP + * @type {Phaser.Math.Vector2} + * @since 3.16.0 + */ +Vector2.UP = new Vector2(0, -1); + +/** + * A static down Vector2 for use by reference. + * + * This constant is meant for comparison operations and should not be modified directly. + * + * @constant + * @name Phaser.Math.Vector2.DOWN + * @type {Phaser.Math.Vector2} + * @since 3.16.0 + */ +Vector2.DOWN = new Vector2(0, 1); + +/** + * A static one Vector2 for use by reference. + * + * This constant is meant for comparison operations and should not be modified directly. + * + * @constant + * @name Phaser.Math.Vector2.ONE + * @type {Phaser.Math.Vector2} + * @since 3.16.0 + */ +Vector2.ONE = new Vector2(1, 1); + +export default Vector2; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/Wrap.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/Wrap.js new file mode 100644 index 000000000..4c9a8ba64 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/Wrap.js @@ -0,0 +1,26 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Wrap the given `value` between `min` and `max. + * + * @function Phaser.Math.Wrap + * @since 3.0.0 + * + * @param {number} value - The value to wrap. + * @param {number} min - The minimum value. + * @param {number} max - The maximum value. + * + * @return {number} The wrapped value. + */ +var Wrap = function (value, min, max) +{ + var range = max - min; + + return (min + ((((value - min) % range) + range) % range)); +}; + +export default Wrap; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/Yoyo.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/Yoyo.js new file mode 100644 index 000000000..81118d893 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/Yoyo.js @@ -0,0 +1,14 @@ +var Yoyo = function (t, threshold) { + if (threshold === undefined) { + threshold = 0.5; + } + if (t <= threshold) { + t = t / threshold; + } else { + t = 1 - ((t - threshold) / (1 - threshold)); + } + + return t; +} + +export default Yoyo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/Between.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/Between.js new file mode 100644 index 000000000..81f5f3b4d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/Between.js @@ -0,0 +1,25 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Find the angle of a segment from (x1, y1) -> (x2, y2). + * + * @function Phaser.Math.Angle.Between + * @since 3.0.0 + * + * @param {number} x1 - The x coordinate of the first point. + * @param {number} y1 - The y coordinate of the first point. + * @param {number} x2 - The x coordinate of the second point. + * @param {number} y2 - The y coordinate of the second point. + * + * @return {number} The angle in radians. + */ +var Between = function (x1, y1, x2, y2) +{ + return Math.atan2(y2 - y1, x2 - x1); +}; + +export default Between; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/Normalize.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/Normalize.js new file mode 100644 index 000000000..bf465d168 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/Normalize.js @@ -0,0 +1,31 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Normalize an angle to the [0, 2pi] range. + * + * @function Phaser.Math.Angle.Normalize + * @since 3.0.0 + * + * @param {number} angle - The angle to normalize, in radians. + * + * @return {number} The normalized angle, in radians. + */ +var Normalize = function (angle) +{ + angle = angle % (2 * Math.PI); + + if (angle >= 0) + { + return angle; + } + else + { + return angle + 2 * Math.PI; + } +}; + +export default Normalize; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/ShortestBetween.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/ShortestBetween.js new file mode 100644 index 000000000..7d970d75f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/ShortestBetween.js @@ -0,0 +1,43 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Gets the shortest angle between `angle1` and `angle2`. + * + * Both angles must be in the range -180 to 180, which is the same clamped + * range that `sprite.angle` uses, so you can pass in two sprite angles to + * this method and get the shortest angle back between the two of them. + * + * The angle returned will be in the same range. If the returned angle is + * greater than 0 then it's a counter-clockwise rotation, if < 0 then it's + * a clockwise rotation. + * + * TODO: Wrap the angles in this function? + * + * @function Phaser.Math.Angle.ShortestBetween + * @since 3.0.0 + * + * @param {number} angle1 - The first angle in the range -180 to 180. + * @param {number} angle2 - The second angle in the range -180 to 180. + * + * @return {number} The shortest angle, in degrees. If greater than zero it's a counter-clockwise rotation. + */ +var ShortestBetween = function (angle1, angle2) +{ + var difference = angle2 - angle1; + + if (difference === 0) + { + return 0; + } + + var times = Math.floor((difference - (-180)) / 360); + + return difference - (times * 360); + +}; + +export default ShortestBetween; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/Wrap.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/Wrap.js new file mode 100644 index 000000000..a2b2e8518 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/Wrap.js @@ -0,0 +1,25 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import MathWrap from '../Wrap.js'; + +/** + * Wrap an angle. + * + * Wraps the angle to a value in the range of -PI to PI. + * + * @function Phaser.Math.Angle.Wrap + * @since 3.0.0 + * + * @param {number} angle - The angle to wrap, in radians. + * + * @return {number} The wrapped angle, in radians. + */ +var Wrap = function (angle) { + return MathWrap(angle, -Math.PI, Math.PI); +}; + +export default Wrap; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/angletodirections/AngleToDirections.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/angletodirections/AngleToDirections.js new file mode 100644 index 000000000..8ddc01f62 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/angletodirections/AngleToDirections.js @@ -0,0 +1,73 @@ +var AngleToDirections = function (angle, dirMode, out) { + if (out === undefined) { + out = {} + } else if (out === true) { + out = globOut; + } + + out.left = false; + out.right = false; + out.up = false; + out.down = false; + + angle = (angle + 360) % 360; + switch (dirMode) { + case 0: // up & down + if (angle < 180) { + out.down = true; + } else { + out.up = true; + } + break; + + case 1: // left & right + if ((angle > 90) && (angle <= 270)) { + out.left = true; + } else { + out.right = true; + } + break; + + case 2: // 4 dir + if ((angle > 45) && (angle <= 135)) { + out.down = true; + } else if ((angle > 135) && (angle <= 225)) { + out.left = true; + } else if ((angle > 225) && (angle <= 315)) { + out.up = true; + } else { + out.right = true; + } + break; + + case 3: // 8 dir + if ((angle > 22.5) && (angle <= 67.5)) { + out.down = true; + out.right = true; + } else if ((angle > 67.5) && (angle <= 112.5)) { + out.down = true; + } else if ((angle > 112.5) && (angle <= 157.5)) { + out.down = true; + out.left = true; + } else if ((angle > 157.5) && (angle <= 202.5)) { + out.left = true; + } else if ((angle > 202.5) && (angle <= 247.5)) { + out.left = true; + out.up = true; + } else if ((angle > 247.5) && (angle <= 292.5)) { + out.up = true; + } else if ((angle > 292.5) && (angle <= 337.5)) { + out.up = true; + out.right = true; + } else { + out.right = true; + } + break; + } + + return out; +}; + +var globOut = {}; + +export default AngleToDirections; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/angletodirections/Const.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/angletodirections/Const.js new file mode 100644 index 000000000..95ae728a1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/angletodirections/Const.js @@ -0,0 +1,6 @@ +export default { + 'up&down': 0, + 'left&right': 1, + '4dir': 2, + '8dir': 3 +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/angletodirections/RotationToDirection.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/angletodirections/RotationToDirection.js new file mode 100644 index 000000000..d10b35256 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/angle/angletodirections/RotationToDirection.js @@ -0,0 +1,7 @@ +import AngleToDirection from './AngleToDirections.js'; +import RadToDeg from '../../RadToDeg.js'; + +var RotationToDirection = function (rotation, dirMode, out) { + return AngleToDirection(RadToDeg(rotation), dirMode, out); +} +export default RotationToDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/color/Color32Methods.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/color/Color32Methods.js new file mode 100644 index 000000000..98176a754 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/color/Color32Methods.js @@ -0,0 +1,32 @@ +import InterpolateColor32 from './InterpolateColor32.js'; + +const Color = Phaser.Display.Color; + +export default { + rgbaToColor32(r, g, b, a) { + return (a << 24) | (r << 16) | (g << 8) | b; + }, + + color32ToColorInt(color32) { + return color32 & 0xffffff; + }, + + color32ToAlpha(color32) { + return color32 >>> 24; + }, + + color32ToColorObject(color32, out) { + var r = (color32 >> 16) & 0xff; + var g = (color32 >> 8) & 0xff; + var b = color32 & 0xff + var a = (color32 >> 24) & 0xff + if (out === undefined) { + out = new Color(r, b, g, a); + } else { + out.setTo(r, b, g, a); + } + return out; + }, + + interpolateColor32: InterpolateColor32, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/color/InterpolateColor32.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/color/InterpolateColor32.js new file mode 100644 index 000000000..2abf71762 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/color/InterpolateColor32.js @@ -0,0 +1,22 @@ +const Linear = Phaser.Math.Linear; + +var InterpolateColor32 = function (color0, color1, t) { + var r0 = (color0 >> 16) & 0xff; + var g0 = (color0 >> 8) & 0xff; + var b0 = color0 & 0xff + var a0 = (color0 >> 24) & 0xff + + var r1 = (color1 >> 16) & 0xff; + var g1 = (color1 >> 8) & 0xff; + var b1 = color1 & 0xff; + var a1 = (color1 >> 24) & 0xff; + + var r = Linear(r0, r1, t); + var g = Linear(g0, g1, t); + var b = Linear(b0, b1, t); + var a = Linear(a0, a1, t); + + return (a << 24) | (r << 16) | (g << 8) | b; +} + +export default InterpolateColor32; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/const.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/const.js new file mode 100644 index 000000000..e567410bc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/const.js @@ -0,0 +1,66 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +var MATH_CONST = { + + /** + * The value of PI * 2. + * + * @name Phaser.Math.PI2 + * @type {number} + * @since 3.0.0 + */ + PI2: Math.PI * 2, + + /** + * The value of PI * 0.5. + * + * @name Phaser.Math.TAU + * @type {number} + * @since 3.0.0 + */ + TAU: Math.PI * 0.5, + + /** + * An epsilon value (1.0e-6) + * + * @name Phaser.Math.EPSILON + * @type {number} + * @since 3.0.0 + */ + EPSILON: 1.0e-6, + + /** + * For converting degrees to radians (PI / 180) + * + * @name Phaser.Math.DEG_TO_RAD + * @type {number} + * @since 3.0.0 + */ + DEG_TO_RAD: Math.PI / 180, + + /** + * For converting radians to degrees (180 / PI) + * + * @name Phaser.Math.RAD_TO_DEG + * @type {number} + * @since 3.0.0 + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * An instance of the Random Number Generator. + * This is not set until the Game boots. + * + * @name Phaser.Math.RND + * @type {Phaser.Math.RandomDataGenerator} + * @since 3.0.0 + */ + RND: null + +}; + +export default MATH_CONST; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/coordinate/PolarToCartesian.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/coordinate/PolarToCartesian.js new file mode 100644 index 000000000..ae7b1c3ad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/coordinate/PolarToCartesian.js @@ -0,0 +1,16 @@ +var PolarToCartesian = function (ox, oy, rotation, radius, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globOut; + } + + out.x = (radius * Math.cos(rotation)) + ox; + out.y = (radius * Math.sin(rotation)) + oy; + + return out; +} + +var globOut = {}; + +export default PolarToCartesian; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/distance/DistanceBetween.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/distance/DistanceBetween.js new file mode 100644 index 000000000..3fa2fb728 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/distance/DistanceBetween.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Calculate the distance between two sets of coordinates (points). + * + * @function Phaser.Math.Distance.Between + * @since 3.0.0 + * + * @param {number} x1 - The x coordinate of the first point. + * @param {number} y1 - The y coordinate of the first point. + * @param {number} x2 - The x coordinate of the second point. + * @param {number} y2 - The y coordinate of the second point. + * + * @return {number} The distance between each point. + */ +var DistanceBetween = function (x1, y1, x2, y2) +{ + var dx = x1 - x2; + var dy = y1 - y2; + + return Math.sqrt(dx * dx + dy * dy); +}; + +export default DistanceBetween; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/Ceil.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/Ceil.js new file mode 100644 index 000000000..3d076fd90 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/Ceil.js @@ -0,0 +1,25 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Calculate the fuzzy ceiling of the given value. + * + * @function Phaser.Math.Fuzzy.Ceil + * @since 3.0.0 + * + * @param {number} value - The value. + * @param {number} [epsilon=0.0001] - The epsilon. + * + * @return {number} The fuzzy ceiling of the value. + */ +var Ceil = function (value, epsilon) +{ + if (epsilon === undefined) { epsilon = 0.0001; } + + return Math.ceil(value - epsilon); +}; + +export default Ceil; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/Equal.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/Equal.js new file mode 100644 index 000000000..bb3af086d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/Equal.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Check whether the given values are fuzzily equal. + * + * Two numbers are fuzzily equal if their difference is less than `epsilon`. + * + * @function Phaser.Math.Fuzzy.Equal + * @since 3.0.0 + * + * @param {number} a - The first value. + * @param {number} b - The second value. + * @param {number} [epsilon=0.0001] - The epsilon. + * + * @return {boolean} `true` if the values are fuzzily equal, otherwise `false`. + */ +var Equal = function (a, b, epsilon) +{ + if (epsilon === undefined) { epsilon = 0.0001; } + + return Math.abs(a - b) < epsilon; +}; + +export default Equal; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/Floor.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/Floor.js new file mode 100644 index 000000000..74b581b05 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/Floor.js @@ -0,0 +1,25 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Calculate the fuzzy floor of the given value. + * + * @function Phaser.Math.Fuzzy.Floor + * @since 3.0.0 + * + * @param {number} value - The value. + * @param {number} [epsilon=0.0001] - The epsilon. + * + * @return {number} The floor of the value. + */ +var Floor = function (value, epsilon) +{ + if (epsilon === undefined) { epsilon = 0.0001; } + + return Math.floor(value + epsilon); +}; + +export default Floor; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/GreaterThan.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/GreaterThan.js new file mode 100644 index 000000000..5bbd8935a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/GreaterThan.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Check whether `a` is fuzzily greater than `b`. + * + * `a` is fuzzily greater than `b` if it is more than `b - epsilon`. + * + * @function Phaser.Math.Fuzzy.GreaterThan + * @since 3.0.0 + * + * @param {number} a - The first value. + * @param {number} b - The second value. + * @param {number} [epsilon=0.0001] - The epsilon. + * + * @return {boolean} `true` if `a` is fuzzily greater than than `b`, otherwise `false`. + */ +var GreaterThan = function (a, b, epsilon) +{ + if (epsilon === undefined) { epsilon = 0.0001; } + + return a > b - epsilon; +}; + +export default GreaterThan; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/LessThan.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/LessThan.js new file mode 100644 index 000000000..7e8aabcec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/LessThan.js @@ -0,0 +1,28 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Check whether `a` is fuzzily less than `b`. + * + * `a` is fuzzily less than `b` if it is less than `b + epsilon`. + * + * @function Phaser.Math.Fuzzy.LessThan + * @since 3.0.0 + * + * @param {number} a - The first value. + * @param {number} b - The second value. + * @param {number} [epsilon=0.0001] - The epsilon. + * + * @return {boolean} `true` if `a` is fuzzily less than `b`, otherwise `false`. + */ +var LessThan = function (a, b, epsilon) +{ + if (epsilon === undefined) { epsilon = 0.0001; } + + return a < b + epsilon; +}; + +export default LessThan; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/BezierInterpolation.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/BezierInterpolation.js new file mode 100644 index 000000000..599b32908 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/BezierInterpolation.js @@ -0,0 +1,30 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ +import Bernstein from '../Bernstein.js'; + +/** + * A bezier interpolation method. + * + * @function Phaser.Math.Interpolation.Bezier + * @since 3.0.0 + * + * @param {number[]} v - The input array of values to interpolate between. + * @param {number} k - The percentage of interpolation, between 0 and 1. + * + * @return {number} The interpolated value. + */ +var BezierInterpolation = function (v, k) { + var b = 0; + var n = v.length - 1; + + for (var i = 0; i <= n; i++) { + b += Math.pow(1 - k, n - i) * Math.pow(k, i) * v[i] * Bernstein(n, i); + } + + return b; +}; + +export default BezierInterpolation; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/CatmullRomInterpolation.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/CatmullRomInterpolation.js new file mode 100644 index 000000000..59fca1868 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/CatmullRomInterpolation.js @@ -0,0 +1,45 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import CatmullRom from '../CatmullRom.js'; + +/** + * A Catmull-Rom interpolation method. + * + * @function Phaser.Math.Interpolation.CatmullRom + * @since 3.0.0 + * + * @param {number[]} v - The input array of values to interpolate between. + * @param {number} k - The percentage of interpolation, between 0 and 1. + * + * @return {number} The interpolated value. + */ +var CatmullRomInterpolation = function (v, k) { + var m = v.length - 1; + var f = m * k; + var i = Math.floor(f); + + if (v[0] === v[m]) { + if (k < 0) { + i = Math.floor(f = m * (1 + k)); + } + + return CatmullRom(f - i, v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m]); + } + else { + if (k < 0) { + return v[0] - (CatmullRom(-f, v[0], v[0], v[1], v[1]) - v[0]); + } + + if (k > 1) { + return v[m] - (CatmullRom(f - m, v[m], v[m], v[m - 1], v[m - 1]) - v[m]); + } + + return CatmullRom(f - i, v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2]); + } +}; + +export default CatmullRomInterpolation; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/CubicBezierInterpolation.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/CubicBezierInterpolation.js new file mode 100644 index 000000000..0ab4a4e1b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/CubicBezierInterpolation.js @@ -0,0 +1,59 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * @ignore + */ +function P0(t, p) { + var k = 1 - t; + + return k * k * k * p; +} + +/** + * @ignore + */ +function P1(t, p) { + var k = 1 - t; + + return 3 * k * k * t * p; +} + +/** + * @ignore + */ +function P2(t, p) { + return 3 * (1 - t) * t * t * p; +} + +/** + * @ignore + */ +function P3(t, p) { + return t * t * t * p; +} + +/** + * A cubic bezier interpolation method. + * + * https://medium.com/@adrian_cooney/bezier-interpolation-13b68563313a + * + * @function Phaser.Math.Interpolation.CubicBezier + * @since 3.0.0 + * + * @param {number} t - The percentage of interpolation, between 0 and 1. + * @param {number} p0 - The start point. + * @param {number} p1 - The first control point. + * @param {number} p2 - The second control point. + * @param {number} p3 - The end point. + * + * @return {number} The interpolated value. + */ +var CubicBezierInterpolation = function (t, p0, p1, p2, p3) { + return P0(t, p0) + P1(t, p1) + P2(t, p2) + P3(t, p3); +}; + +export default CubicBezierInterpolation; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/LinearInterpolation.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/LinearInterpolation.js new file mode 100644 index 000000000..5ed8887aa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/LinearInterpolation.js @@ -0,0 +1,37 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Linear from '../Linear.js'; + +/** + * A linear interpolation method. + * + * @function Phaser.Math.Interpolation.Linear + * @since 3.0.0 + * @see {@link https://en.wikipedia.org/wiki/Linear_interpolation} + * + * @param {number[]} v - The input array of values to interpolate between. + * @param {!number} k - The percentage of interpolation, between 0 and 1. + * + * @return {!number} The interpolated value. + */ +var LinearInterpolation = function (v, k) { + var m = v.length - 1; + var f = m * k; + var i = Math.floor(f); + + if (k < 0) { + return Linear(v[0], v[1], f); + } + else if (k > 1) { + return Linear(v[m], v[m - 1], m - f); + } + else { + return Linear(v[i], v[(i + 1 > m) ? m : i + 1], f - i); + } +}; + +export default LinearInterpolation; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/QuadraticBezierInterpolation.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/QuadraticBezierInterpolation.js new file mode 100644 index 000000000..607c69a1e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/QuadraticBezierInterpolation.js @@ -0,0 +1,49 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * @ignore + */ +function P0(t, p) { + var k = 1 - t; + + return k * k * p; +} + +/** + * @ignore + */ +function P1(t, p) { + return 2 * (1 - t) * t * p; +} + +/** + * @ignore + */ +function P2(t, p) { + return t * t * p; +} + +// https://github.com/mrdoob/three.js/blob/master/src/extras/core/Interpolations.js + +/** + * A quadratic bezier interpolation method. + * + * @function Phaser.Math.Interpolation.QuadraticBezier + * @since 3.2.0 + * + * @param {number} t - The percentage of interpolation, between 0 and 1. + * @param {number} p0 - The start point. + * @param {number} p1 - The control point. + * @param {number} p2 - The end point. + * + * @return {number} The interpolated value. + */ +var QuadraticBezierInterpolation = function (t, p0, p1, p2) { + return P0(t, p0) + P1(t, p1) + P2(t, p2); +}; + +export default QuadraticBezierInterpolation; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/SmoothStepInterpolation.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/SmoothStepInterpolation.js new file mode 100644 index 000000000..db582c8d4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/SmoothStepInterpolation.js @@ -0,0 +1,26 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import SmoothStep from '../SmoothStep.js'; + +/** + * A Smooth Step interpolation method. + * + * @function Phaser.Math.Interpolation.SmoothStep + * @since 3.9.0 + * @see {@link https://en.wikipedia.org/wiki/Smoothstep} + * + * @param {number} t - The percentage of interpolation, between 0 and 1. + * @param {number} min - The minimum value, also known as the 'left edge', assumed smaller than the 'right edge'. + * @param {number} max - The maximum value, also known as the 'right edge', assumed greater than the 'left edge'. + * + * @return {number} The interpolated value. + */ +var SmoothStepInterpolation = function (t, min, max) { + return min + (max - min) * SmoothStep(t, 0, 1); +}; + +export default SmoothStepInterpolation; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/SmootherStepInterpolation.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/SmootherStepInterpolation.js new file mode 100644 index 000000000..ffcab98e1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/SmootherStepInterpolation.js @@ -0,0 +1,26 @@ +/** + * @author Richard Davey + * @copyright 2022 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import SmootherStep from '../SmootherStep.js'; + +/** + * A Smoother Step interpolation method. + * + * @function Phaser.Math.Interpolation.SmootherStep + * @since 3.9.0 + * @see {@link https://en.wikipedia.org/wiki/Smoothstep#Variations} + * + * @param {number} t - The percentage of interpolation, between 0 and 1. + * @param {number} min - The minimum value, also known as the 'left edge', assumed smaller than the 'right edge'. + * @param {number} max - The maximum value, also known as the 'right edge', assumed greater than the 'left edge'. + * + * @return {number} The interpolated value. + */ +var SmootherStepInterpolation = function (t, min, max) { + return min + (max - min) * SmootherStep(t, 0, 1); +}; + +export default SmootherStepInterpolation; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/noise/Perlin.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/math/noise/Perlin.d.ts new file mode 100644 index 000000000..7671822c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/noise/Perlin.d.ts @@ -0,0 +1,11 @@ +export default class Noise { + constructor(seed?: number); + + perlin2(x: number, y: number): number; + perlin3(x: number, y: number, z: number): number; + + simplex2(x: number, y: number): number; + simplex3(x: number, y: number, z: number): number; + + setSeed(seed: number): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/noise/Perlin.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/noise/Perlin.js new file mode 100644 index 000000000..d38f4e033 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/noise/Perlin.js @@ -0,0 +1,312 @@ +// https://github.com/josephg/noisejs + +class Grad { + constructor(x, y, z) { + this.x = x; this.y = y; this.z = z; + } + + dot2(x, y) { + return this.x * x + this.y * y; + }; + + dot3(x, y, z) { + return this.x * x + this.y * y + this.z * z; + }; +} + +const grad3 = [ + new Grad(1, 1, 0), new Grad(-1, 1, 0), new Grad(1, -1, 0), new Grad(-1, -1, 0), + new Grad(1, 0, 1), new Grad(-1, 0, 1), new Grad(1, 0, -1), new Grad(-1, 0, -1), + new Grad(0, 1, 1), new Grad(0, -1, 1), new Grad(0, 1, -1), new Grad(0, -1, -1) +]; + +const p = [151, 160, 137, 91, 90, 15, + 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, + 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, + 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, + 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, + 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, + 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, + 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, + 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, + 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, + 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, + 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180]; + +// Skewing and unskewing factors for 2, 3, and 4 dimensions +const F2 = 0.5 * (Math.sqrt(3) - 1); +const G2 = (3 - Math.sqrt(3)) / 6; + +const F3 = 1 / 3; +const G3 = 1 / 6; + +// ##### Perlin noise stuff + +var fade = function (t) { + return t * t * t * (t * (t * 6 - 15) + 10); +} + +var lerp = function (a, b, t) { + return (1 - t) * a + t * b; +} + +class Noise { + constructor(seed) { + if (seed === undefined) { + seed = 0; + } + + // To remove the need for index wrapping, double the permutation table length + this.perm = new Array(512); + this.gradP = new Array(512); + this.setSeed(seed); + } + + setSeed(seed) { + var perm = this.perm; + var gradP = this.gradP; + + if (seed > 0 && seed < 1) { + // Scale the seed out + seed *= 65536; + } + + seed = Math.floor(seed); + if (seed < 256) { + seed |= seed << 8; + } + + for (var i = 0; i < 256; i++) { + var v; + if (i & 1) { + v = p[i] ^ (seed & 255); + } else { + v = p[i] ^ ((seed >> 8) & 255); + } + + perm[i] = perm[i + 256] = v; + gradP[i] = gradP[i + 256] = grad3[v % 12]; + } + } + + // 2D simplex noise + simplex2(xin, yin) { + var perm = this.perm; + var gradP = this.gradP; + + var n0, n1, n2; // Noise contributions from the three corners + // Skew the input space to determine which simplex cell we're in + var s = (xin + yin) * F2; // Hairy factor for 2D + var i = Math.floor(xin + s); + var j = Math.floor(yin + s); + var t = (i + j) * G2; + var x0 = xin - i + t; // The x,y distances from the cell origin, unskewed. + var y0 = yin - j + t; + // For the 2D case, the simplex shape is an equilateral triangle. + // Determine which simplex we are in. + var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords + if (x0 > y0) { // lower triangle, XY order: (0,0)->(1,0)->(1,1) + i1 = 1; j1 = 0; + } else { // upper triangle, YX order: (0,0)->(0,1)->(1,1) + i1 = 0; j1 = 1; + } + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords + var y1 = y0 - j1 + G2; + var x2 = x0 - 1 + 2 * G2; // Offsets for last corner in (x,y) unskewed coords + var y2 = y0 - 1 + 2 * G2; + // Work out the hashed gradient indices of the three simplex corners + i &= 255; + j &= 255; + var gi0 = gradP[i + perm[j]]; + var gi1 = gradP[i + i1 + perm[j + j1]]; + var gi2 = gradP[i + 1 + perm[j + 1]]; + // Calculate the contribution from the three corners + var t0 = 0.5 - x0 * x0 - y0 * y0; + if (t0 < 0) { + n0 = 0; + } else { + t0 *= t0; + n0 = t0 * t0 * gi0.dot2(x0, y0); // (x,y) of grad3 used for 2D gradient + } + var t1 = 0.5 - x1 * x1 - y1 * y1; + if (t1 < 0) { + n1 = 0; + } else { + t1 *= t1; + n1 = t1 * t1 * gi1.dot2(x1, y1); + } + var t2 = 0.5 - x2 * x2 - y2 * y2; + if (t2 < 0) { + n2 = 0; + } else { + t2 *= t2; + n2 = t2 * t2 * gi2.dot2(x2, y2); + } + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 70 * (n0 + n1 + n2); + } + + // 3D simplex noise + simplex3(xin, yin, zin) { + var perm = this.perm; + var gradP = this.gradP; + + var n0, n1, n2, n3; // Noise contributions from the four corners + + // Skew the input space to determine which simplex cell we're in + var s = (xin + yin + zin) * F3; // Hairy factor for 2D + var i = Math.floor(xin + s); + var j = Math.floor(yin + s); + var k = Math.floor(zin + s); + + var t = (i + j + k) * G3; + var x0 = xin - i + t; // The x,y distances from the cell origin, unskewed. + var y0 = yin - j + t; + var z0 = zin - k + t; + + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + // Determine which simplex we are in. + var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords + var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + if (x0 >= y0) { + if (y0 >= z0) { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 1; k2 = 0; } + else if (x0 >= z0) { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 0; k2 = 1; } + else { i1 = 0; j1 = 0; k1 = 1; i2 = 1; j2 = 0; k2 = 1; } + } else { + if (y0 < z0) { i1 = 0; j1 = 0; k1 = 1; i2 = 0; j2 = 1; k2 = 1; } + else if (x0 < z0) { i1 = 0; j1 = 1; k1 = 0; i2 = 0; j2 = 1; k2 = 1; } + else { i1 = 0; j1 = 1; k1 = 0; i2 = 1; j2 = 1; k2 = 0; } + } + // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z), + // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and + // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where + // c = 1/6. + var x1 = x0 - i1 + G3; // Offsets for second corner + var y1 = y0 - j1 + G3; + var z1 = z0 - k1 + G3; + + var x2 = x0 - i2 + 2 * G3; // Offsets for third corner + var y2 = y0 - j2 + 2 * G3; + var z2 = z0 - k2 + 2 * G3; + + var x3 = x0 - 1 + 3 * G3; // Offsets for fourth corner + var y3 = y0 - 1 + 3 * G3; + var z3 = z0 - 1 + 3 * G3; + + // Work out the hashed gradient indices of the four simplex corners + i &= 255; + j &= 255; + k &= 255; + var gi0 = gradP[i + perm[j + perm[k]]]; + var gi1 = gradP[i + i1 + perm[j + j1 + perm[k + k1]]]; + var gi2 = gradP[i + i2 + perm[j + j2 + perm[k + k2]]]; + var gi3 = gradP[i + 1 + perm[j + 1 + perm[k + 1]]]; + + // Calculate the contribution from the four corners + var t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0; + if (t0 < 0) { + n0 = 0; + } else { + t0 *= t0; + n0 = t0 * t0 * gi0.dot3(x0, y0, z0); // (x,y) of grad3 used for 2D gradient + } + var t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1; + if (t1 < 0) { + n1 = 0; + } else { + t1 *= t1; + n1 = t1 * t1 * gi1.dot3(x1, y1, z1); + } + var t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2; + if (t2 < 0) { + n2 = 0; + } else { + t2 *= t2; + n2 = t2 * t2 * gi2.dot3(x2, y2, z2); + } + var t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3; + if (t3 < 0) { + n3 = 0; + } else { + t3 *= t3; + n3 = t3 * t3 * gi3.dot3(x3, y3, z3); + } + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 32 * (n0 + n1 + n2 + n3); + + } + + // 2D Perlin Noise + perlin2(x, y) { + var perm = this.perm; + var gradP = this.gradP; + + // Find unit grid cell containing point + var X = Math.floor(x), Y = Math.floor(y); + // Get relative xy coordinates of point within that cell + x = x - X; y = y - Y; + // Wrap the integer cells at 255 (smaller integer period can be introduced here) + X = X & 255; Y = Y & 255; + + // Calculate noise contributions from each of the four corners + var n00 = gradP[X + perm[Y]].dot2(x, y); + var n01 = gradP[X + perm[Y + 1]].dot2(x, y - 1); + var n10 = gradP[X + 1 + perm[Y]].dot2(x - 1, y); + var n11 = gradP[X + 1 + perm[Y + 1]].dot2(x - 1, y - 1); + + // Compute the fade curve value for x + var u = fade(x); + + // Interpolate the four results + return lerp( + lerp(n00, n10, u), + lerp(n01, n11, u), + fade(y)); + } + + // 3D Perlin Noise + perlin3(x, y, z) { + var perm = this.perm; + var gradP = this.gradP; + + // Find unit grid cell containing point + var X = Math.floor(x), Y = Math.floor(y), Z = Math.floor(z); + // Get relative xyz coordinates of point within that cell + x = x - X; y = y - Y; z = z - Z; + // Wrap the integer cells at 255 (smaller integer period can be introduced here) + X = X & 255; Y = Y & 255; Z = Z & 255; + + // Calculate noise contributions from each of the eight corners + var n000 = gradP[X + perm[Y + perm[Z]]].dot3(x, y, z); + var n001 = gradP[X + perm[Y + perm[Z + 1]]].dot3(x, y, z - 1); + var n010 = gradP[X + perm[Y + 1 + perm[Z]]].dot3(x, y - 1, z); + var n011 = gradP[X + perm[Y + 1 + perm[Z + 1]]].dot3(x, y - 1, z - 1); + var n100 = gradP[X + 1 + perm[Y + perm[Z]]].dot3(x - 1, y, z); + var n101 = gradP[X + 1 + perm[Y + perm[Z + 1]]].dot3(x - 1, y, z - 1); + var n110 = gradP[X + 1 + perm[Y + 1 + perm[Z]]].dot3(x - 1, y - 1, z); + var n111 = gradP[X + 1 + perm[Y + 1 + perm[Z + 1]]].dot3(x - 1, y - 1, z - 1); + + // Compute the fade curve value for x, y, z + var u = fade(x); + var v = fade(y); + var w = fade(z); + + // Interpolate + return lerp( + lerp( + lerp(n000, n100, u), + lerp(n001, n101, u), w), + lerp( + lerp(n010, n110, u), + lerp(n011, n111, u), w), + v); + } +} + +export default Noise; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/noise/Simplex1.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/noise/Simplex1.js new file mode 100644 index 000000000..8c5b879eb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/noise/Simplex1.js @@ -0,0 +1,89 @@ +//https://github.com/SRombauts/SimplexNoise/blob/master/src/SimplexNoise.cpp +const p = [151, 160, 137, 91, 90, 15, + 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, + 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, + 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, + 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, + 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, + 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, + 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, + 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, + 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, + 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, + 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180]; +// global + +class Noise { + constructor() { + this.perm = new Uint8Array(256); + } + + setSeed(seed) { + if (seed > 0 && seed < 1) { + // Scale the seed out + seed *= 65536; + } + + seed = Math.floor(seed); + if (seed < 256) { + seed |= seed << 8; + } + + var v; + for (var i = 0; i < 256; i++) { + if (i & 1) { + v = p[i] ^ (seed & 255); + } + else { + v = p[i] ^ ((seed >> 8) & 255); + } + + this.perm[i] = v; + } + } + + hash(i) { + return this.perm[i & 255]; + } + + simplex1(x) { + var n0, n1; // Noise contributions from the two "corners" + + // No need to skew the input space in 1D + + // Corners coordinates (nearest integer values): + var i0 = Math.floor(x); + var i1 = i0 + 1.0; + // Distances to corners (between 0 and 1): + var x0 = x - i0; + var x1 = x0 - 1.0; + + // Calculate the contribution from the first corner + var t0 = 1.0 - x0 * x0; + // if(t0 < 0.0f) t0 = 0.0f; // not possible + t0 *= t0; + n0 = t0 * t0 * gradP(this.hash(i0), x0); // TODO + + // Calculate the contribution from the second corner + var t1 = 1.0 - x1 * x1; + // if(t1 < 0.0f) t1 = 0.0f; // not possible + t1 *= t1; + n1 = t1 * t1 * gradP(this.hash(i1), x1); // TODO + + // The maximum value of this noise is 8*(3/4)^4 = 2.53125 + // A factor of 0.395 scales to fit exactly within [-1,1] + // 0.9395123193338055 ~ -0.99984375 + return 0.395 * (n0 + n1); + }; +} + +var gradP = function (hash, x) { + var h = hash & 0x0F; // Convert low 4 bits of hash code + var grad = 1 + (h & 7); // Gradient value 1.0, 2.0, ..., 8.0 + if ((h & 8) !== 0) grad = -grad; // Set a random sign for the gradient + // float grad = gradients1D[h]; // NOTE : Test of Gradient look-up table instead of the above + return (grad * x); // Multiply the gradient with the distance +}; + +export default Noise; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/triangulate/ShatterRectangleToTriangles.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/triangulate/ShatterRectangleToTriangles.js new file mode 100644 index 000000000..145625427 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/triangulate/ShatterRectangleToTriangles.js @@ -0,0 +1,122 @@ +import Triangulate from './Triangulate.js'; +import IsFunction from '../../object/IsFunction.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Clamp = Phaser.Math.Clamp; +const DefaultRingRadiusList = [1 / 27, 3 / 27, 9 / 27]; + +var ShatterRectangleToTriangles = function (config) { + var left, right, top, bottom, width, height; + var rectangle = config.rectangle; + if (rectangle) { + left = rectangle.x; + top = rectangle.y; + width = rectangle.width; + height = rectangle.height; + } else { + left = 0; + top = 0; + width = config.width; + height = config.height; + } + right = left + width; + bottom = top + height; + + var center = config.center; + var centerX, centerY; + if (center === undefined) { + centerX = (left + right) / 2; + centerY = (top + bottom) / 2; + } else { + centerX = Clamp(center.x, left, right); + centerY = Clamp(center.y, top, bottom); + } + + var ringRadiusList = GetValue(config, 'ringRadiusList', DefaultRingRadiusList); + var ringSamples = GetValue(config, 'samplesPerRing', 12); + var variation = GetValue(config, 'variation', 0.25); + var triangleOutput = GetValue(config, 'triangleOutput', true); + + if (IsFunction(ringRadiusList)) { + ringRadiusList = ringRadiusList(width, height); + } + + var randMin = 1 - variation, + randMax = 1 + variation; + + for (var i = 0; i < 10; i++) { + // Can generate triangles 10 times + try { + var vertices = GenerateVertices( + centerX, centerY, + width, height, ringRadiusList, ringSamples, + randMin, randMax, + left, right, top, bottom + ) + return Triangulate(vertices, triangleOutput); + } catch (e) { + + } + } + + throw new Error("Generate triangles fail"); +} + +var GenerateVertices = function ( + centerX, centerY, + width, height, ringRadiusList, ringSamples, + randMin, randMax, + left, right, top, bottom +) { + var vertices = []; + vertices.push([centerX, centerY]); + + var radius = Math.min(width, height); + for (var i = 0, cnt = ringRadiusList.length; i < cnt; i++) { + AddRingVertices( + vertices, + centerX, centerY, (radius * ringRadiusList[i]), ringSamples, + randMin, randMax, + left, right, top, bottom + ) + } + + // Vertices outside of rectangle + var radius = Math.max(width, height) * 2; + AddRingVertices( + vertices, + centerX, centerY, radius, ringSamples, + randMin, randMax, + left, right, top, bottom + ) + + return vertices; +} + +const TWO_PI = Math.PI * 2; +var AddRingVertices = function ( + vertices, + centerX, centerY, radius, amount, + randMin, randMax, + leftBound, rightBound, topBound, bottomBound +) { + for (var i = 0; i < amount; i++) { + var rad = (i / amount) * TWO_PI; + var x = centerX + Math.cos(rad) * radius * RandomRange(randMin, randMax); + var y = centerY + Math.sin(rad) * radius * RandomRange(randMin, randMax); + x = Clamp(x, leftBound, rightBound); + y = Clamp(y, topBound, bottomBound); + vertices.push([x, y]); + } + return vertices; +} + +var RandomRange = function (min, max) { + if (min === max) { + return min; + } else { + return min + (max - min) * Math.random(); + } +} + +export default ShatterRectangleToTriangles; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/triangulate/Triangulate.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/triangulate/Triangulate.js new file mode 100644 index 000000000..f55a4616f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/triangulate/Triangulate.js @@ -0,0 +1,29 @@ +import Delaunay from './delaunay.js'; + +const Triangle = Phaser.Geom.Triangle; + +var Triangulate = function (vertices, triangleResult) { + if (triangleResult === undefined) { + triangleResult = true; + } + + var indices = Delaunay.triangulate(vertices); + if (triangleResult) { + var triangles = []; + for (var i = 0, cnt = indices.length; i < cnt; i += 3) { + var p0 = vertices[indices[i + 0]]; + var p1 = vertices[indices[i + 1]]; + var p2 = vertices[indices[i + 2]]; + var triangle = new Triangle(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]); + triangles.push(triangle); + } + return triangles; + } else { + return { + vertices: vertices, + indices: indices + } + } +} + +export default Triangulate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/math/triangulate/delaunay.js b/ui/src/phaser3-rex-plugins/plugins/utils/math/triangulate/delaunay.js new file mode 100644 index 000000000..875057eb3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/math/triangulate/delaunay.js @@ -0,0 +1,236 @@ +// https://github.com/darkskyapp/delaunay-fast/blob/master/delaunay.js + +var Delaunay; + +(function() { + "use strict"; + + var EPSILON = 1.0 / 1048576.0; + + function supertriangle(vertices) { + var xmin = Number.POSITIVE_INFINITY, + ymin = Number.POSITIVE_INFINITY, + xmax = Number.NEGATIVE_INFINITY, + ymax = Number.NEGATIVE_INFINITY, + i, dx, dy, dmax, xmid, ymid; + + for(i = vertices.length; i--; ) { + if(vertices[i][0] < xmin) xmin = vertices[i][0]; + if(vertices[i][0] > xmax) xmax = vertices[i][0]; + if(vertices[i][1] < ymin) ymin = vertices[i][1]; + if(vertices[i][1] > ymax) ymax = vertices[i][1]; + } + + dx = xmax - xmin; + dy = ymax - ymin; + dmax = Math.max(dx, dy); + xmid = xmin + dx * 0.5; + ymid = ymin + dy * 0.5; + + return [ + [xmid - 20 * dmax, ymid - dmax], + [xmid , ymid + 20 * dmax], + [xmid + 20 * dmax, ymid - dmax] + ]; + } + + function circumcircle(vertices, i, j, k) { + var x1 = vertices[i][0], + y1 = vertices[i][1], + x2 = vertices[j][0], + y2 = vertices[j][1], + x3 = vertices[k][0], + y3 = vertices[k][1], + fabsy1y2 = Math.abs(y1 - y2), + fabsy2y3 = Math.abs(y2 - y3), + xc, yc, m1, m2, mx1, mx2, my1, my2, dx, dy; + + /* Check for coincident points */ + if(fabsy1y2 < EPSILON && fabsy2y3 < EPSILON) + throw new Error("Eek! Coincident points!"); + + if(fabsy1y2 < EPSILON) { + m2 = -((x3 - x2) / (y3 - y2)); + mx2 = (x2 + x3) / 2.0; + my2 = (y2 + y3) / 2.0; + xc = (x2 + x1) / 2.0; + yc = m2 * (xc - mx2) + my2; + } + + else if(fabsy2y3 < EPSILON) { + m1 = -((x2 - x1) / (y2 - y1)); + mx1 = (x1 + x2) / 2.0; + my1 = (y1 + y2) / 2.0; + xc = (x3 + x2) / 2.0; + yc = m1 * (xc - mx1) + my1; + } + + else { + m1 = -((x2 - x1) / (y2 - y1)); + m2 = -((x3 - x2) / (y3 - y2)); + mx1 = (x1 + x2) / 2.0; + mx2 = (x2 + x3) / 2.0; + my1 = (y1 + y2) / 2.0; + my2 = (y2 + y3) / 2.0; + xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2); + yc = (fabsy1y2 > fabsy2y3) ? + m1 * (xc - mx1) + my1 : + m2 * (xc - mx2) + my2; + } + + dx = x2 - xc; + dy = y2 - yc; + return {i: i, j: j, k: k, x: xc, y: yc, r: dx * dx + dy * dy}; + } + + function dedup(edges) { + var i, j, a, b, m, n; + + for(j = edges.length; j; ) { + b = edges[--j]; + a = edges[--j]; + + for(i = j; i; ) { + n = edges[--i]; + m = edges[--i]; + + if((a === m && b === n) || (a === n && b === m)) { + edges.splice(j, 2); + edges.splice(i, 2); + break; + } + } + } + } + + Delaunay = { + triangulate: function(vertices, key) { + var n = vertices.length, + i, j, indices, st, open, closed, edges, dx, dy, a, b, c; + + /* Bail if there aren't enough vertices to form any triangles. */ + if(n < 3) + return []; + + /* Slice out the actual vertices from the passed objects. (Duplicate the + * array even if we don't, though, since we need to make a supertriangle + * later on!) */ + vertices = vertices.slice(0); + + if(key) + for(i = n; i--; ) + vertices[i] = vertices[i][key]; + + /* Make an array of indices into the vertex array, sorted by the + * vertices' x-position. */ + indices = new Array(n); + + for(i = n; i--; ) + indices[i] = i; + + indices.sort(function(i, j) { + return vertices[j][0] - vertices[i][0]; + }); + + /* Next, find the vertices of the supertriangle (which contains all other + * triangles), and append them onto the end of a (copy of) the vertex + * array. */ + st = supertriangle(vertices); + vertices.push(st[0], st[1], st[2]); + + /* Initialize the open list (containing the supertriangle and nothing + * else) and the closed list (which is empty since we havn't processed + * any triangles yet). */ + open = [circumcircle(vertices, n + 0, n + 1, n + 2)]; + closed = []; + edges = []; + + /* Incrementally add each vertex to the mesh. */ + for(i = indices.length; i--; edges.length = 0) { + c = indices[i]; + + /* For each open triangle, check to see if the current point is + * inside it's circumcircle. If it is, remove the triangle and add + * it's edges to an edge list. */ + for(j = open.length; j--; ) { + /* If this point is to the right of this triangle's circumcircle, + * then this triangle should never get checked again. Remove it + * from the open list, add it to the closed list, and skip. */ + dx = vertices[c][0] - open[j].x; + if(dx > 0.0 && dx * dx > open[j].r) { + closed.push(open[j]); + open.splice(j, 1); + continue; + } + + /* If we're outside the circumcircle, skip this triangle. */ + dy = vertices[c][1] - open[j].y; + if(dx * dx + dy * dy - open[j].r > EPSILON) + continue; + + /* Remove the triangle and add it's edges to the edge list. */ + edges.push( + open[j].i, open[j].j, + open[j].j, open[j].k, + open[j].k, open[j].i + ); + open.splice(j, 1); + } + + /* Remove any doubled edges. */ + dedup(edges); + + /* Add a new triangle for each edge. */ + for(j = edges.length; j; ) { + b = edges[--j]; + a = edges[--j]; + open.push(circumcircle(vertices, a, b, c)); + } + } + + /* Copy any remaining open triangles to the closed list, and then + * remove any triangles that share a vertex with the supertriangle, + * building a list of triplets that represent triangles. */ + for(i = open.length; i--; ) + closed.push(open[i]); + open.length = 0; + + for(i = closed.length; i--; ) + if(closed[i].i < n && closed[i].j < n && closed[i].k < n) + open.push(closed[i].i, closed[i].j, closed[i].k); + + /* Yay, we're done! */ + return open; + }, + contains: function(tri, p) { + /* Bounding box test first, for quick rejections. */ + if((p[0] < tri[0][0] && p[0] < tri[1][0] && p[0] < tri[2][0]) || + (p[0] > tri[0][0] && p[0] > tri[1][0] && p[0] > tri[2][0]) || + (p[1] < tri[0][1] && p[1] < tri[1][1] && p[1] < tri[2][1]) || + (p[1] > tri[0][1] && p[1] > tri[1][1] && p[1] > tri[2][1])) + return null; + + var a = tri[1][0] - tri[0][0], + b = tri[2][0] - tri[0][0], + c = tri[1][1] - tri[0][1], + d = tri[2][1] - tri[0][1], + i = a * d - b * c; + + /* Degenerate tri. */ + if(i === 0.0) + return null; + + var u = (d * (p[0] - tri[0][0]) - b * (p[1] - tri[0][1])) / i, + v = (a * (p[1] - tri[0][1]) - c * (p[0] - tri[0][0])) / i; + + /* If we're outside the tri, fail. */ + if(u < 0.0 || v < 0.0 || (u + v) > 1.0) + return null; + + return [u, v]; + } + }; + + if(typeof module !== "undefined") + module.exports = Delaunay; +})(); \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/midi-parser/midi-parser.js b/ui/src/phaser3-rex-plugins/plugins/utils/midi-parser/midi-parser.js new file mode 100644 index 000000000..8b683c177 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/midi-parser/midi-parser.js @@ -0,0 +1,304 @@ +/* + Project Name: midi-parser-js + Author: colxi + Author URI: http://www.colxi.info/ + Description: MIDIParser library reads .MID binary files, Base64 encoded MIDI Data, + or UInt8 Arrays, and outputs as a readable and structured JS object. + + --- Usage Methods --- + ------------------------------ + + * OPTION 1 NEW! (MIDIParser.parse) + Will autodetect the source and proccess the data, using the suitable method. + + * OPTION 2 (MIDIParser.addListener) + INPUT ELEMENT LISTENER : call MIDIParser.addListener(fileInputElement,callbacFunction) function, setting the + Input File HTML element that will handle the file.mid opening, and callback function + that will recieve the resulting Object formated, set of data. + + * OPTION 3 (MIDIParser.Uint8) + Provide your own UInt8 Array to MIDIParser.Uint8(), to get an Object formated, set of data + + * OPTION 4 (MIDIParser.Base64) + Provide a Base64 encoded Data to MIDIParser.Base64(), , to get an Object formated, set of data + + + --- Output Object Specs --- + ------------------------------ + + MIDIObject{ + formatType: 0|1|2, // Midi format type + timeDivision: (int), // song tempo (bpm) + tracks: (int), // total tracks count + track: Array[ + [0]: Object{ // TRACK 1! + event: Array[ // Midi events in track 1 + [0] : Object{ // EVENT 1 + data: (string), + deltaTime: (int), + metaType: (int), + type: (int), + }, + [1] : Object{...} // EVENT 2 + [2] : Object{...} // EVENT 3 + ... + ] + }, + [1] : Object{...} + [2] : Object{...} + ... + ] + } + +Data from Event 12 of Track 2 could be easilly readed with: +OutputObject.track[2].event[12].data; + +*/ + + +'use strict'; + +// CROSSBROWSER & NODEjs POLYFILL for ATOB() - By: https://github.com/MaxArt2501 (modified) +var _atob = function(string) { + // base64 character set, plus padding character (=) + var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + // Regular expression to check formal correctness of base64 encoded strings + var b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/; + // remove data type signatures at the begining of the string + // eg : "data:audio/mid;base64," + string = string.replace( /^.*?base64,/ , ""); + // atob can work with strings with whitespaces, even inside the encoded part, + // but only \t, \n, \f, \r and ' ', which can be stripped. + string = String(string).replace(/[\t\n\f\r ]+/g, ""); + if (!b64re.test(string)) + throw new TypeError("Failed to execute '_atob' : The string to be decoded is not correctly encoded."); + + // Adding the padding if missing, for semplicity + string += "==".slice(2 - (string.length & 3)); + var bitmap, result = "", r1, r2, i = 0; + for (; i < string.length;) { + bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12 + | (r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++))); + + result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255) + : r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255) + : String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255); + } + return result; +}; + + +var MIDIParser = { + // debug (bool), when enabled will log in console unimplemented events warnings and internal handled errors. + debug: false, + + parse: function(input, _callback){ + if(input instanceof Uint8Array) return MIDIParser.Uint8(input); + else if(typeof input === 'string') return MIDIParser.Base64(input); + else if(input instanceof HTMLElement && input.type === 'file') return MIDIParser.addListener(input , _callback); + else throw new Error('MIDIParser.parse() : Invalid input provided'); + }, + // addListener() should be called in order attach a listener to the INPUT HTML element + // that will provide the binary data automating the conversion, and returning + // the structured data to the provided callback function. + addListener: function(_fileElement, _callback){ + if(!File || !FileReader) throw new Error('The File|FileReader APIs are not supported in this browser. Use instead MIDIParser.Base64() or MIDIParser.Uint8()'); + + // validate provided element + if( _fileElement === undefined || + !(_fileElement instanceof HTMLElement) || + _fileElement.tagName !== 'INPUT' || + _fileElement.type.toLowerCase() !== 'file' ){ + console.warn('MIDIParser.addListener() : Provided element is not a valid FILE INPUT element'); + return false; + } + _callback = _callback || function(){}; + + _fileElement.addEventListener('change', function(InputEvt){ // set the 'file selected' event handler + if (!InputEvt.target.files.length) return false; // return false if no elements where selected + console.log('MIDIParser.addListener() : File detected in INPUT ELEMENT processing data..'); + var reader = new FileReader(); // prepare the file Reader + reader.readAsArrayBuffer(InputEvt.target.files[0]); // read the binary data + reader.onload = function(e){ + _callback( MIDIParser.Uint8(new Uint8Array(e.target.result))); // encode data with Uint8Array and call the parser + }; + }); + }, + + Base64 : function(b64String){ + b64String = String(b64String); + + var raw = _atob(b64String); + var rawLength = raw.length; + var array = new Uint8Array(new ArrayBuffer(rawLength)); + + for(var i=0; i 1){ + for(var i=1; i<= (_bytes-1); i++){ + value += this.data.getUint8(this.pointer) * Math.pow(256, (_bytes - i)); + this.pointer++; + } + } + value += this.data.getUint8(this.pointer); + this.pointer++; + return value; + }, + readStr: function(_bytes){ // read as ASCII chars, the followoing _bytes + var text = ''; + for(var char=1; char <= _bytes; char++) text += String.fromCharCode(this.readInt(1)); + return text; + }, + readIntVLV: function(){ // read a variable length value + var value = 0; + if ( this.pointer >= this.data.byteLength ){ + return -1; // EOF + }else if(this.data.getUint8(this.pointer) < 128){ // ...value in a single byte + value = this.readInt(1); + }else{ // ...value in multiple bytes + var FirstBytes = []; + while(this.data.getUint8(this.pointer) >= 128){ + FirstBytes.push(this.readInt(1) - 128); + } + var lastByte = this.readInt(1); + for(var dt = 1; dt <= FirstBytes.length; dt++){ + value = FirstBytes[FirstBytes.length - dt] * Math.pow(128, dt); + } + value += lastByte; + } + return value; + } + }; + + file.data = new DataView(FileAsUint8Array.buffer, FileAsUint8Array.byteOffset, FileAsUint8Array.byteLength); // 8 bits bytes file data array + // ** read FILE HEADER + if(file.readInt(4) !== 0x4D546864){ + console.warn('Header validation failed (not MIDI standard or file corrupt.)'); + return false; // Header validation failed (not MIDI standard or file corrupt.) + } + var headerSize = file.readInt(4); // header size (unused var), getted just for read pointer movement + var MIDI = {}; // create new midi object + MIDI.formatType = file.readInt(2); // get MIDI Format Type + MIDI.tracks = file.readInt(2); // get ammount of track chunks + MIDI.track = []; // create array key for track data storing + var timeDivisionByte1 = file.readInt(1); // get Time Division first byte + var timeDivisionByte2 = file.readInt(1); // get Time Division second byte + if(timeDivisionByte1 >= 128){ // discover Time Division mode (fps or tpf) + MIDI.timeDivision = []; + MIDI.timeDivision[0] = timeDivisionByte1 - 128; // frames per second MODE (1st byte) + MIDI.timeDivision[1] = timeDivisionByte2; // ticks in each frame (2nd byte) + }else MIDI.timeDivision = (timeDivisionByte1 * 256) + timeDivisionByte2;// else... ticks per beat MODE (2 bytes value) + // ** read TRACK CHUNK + for(var t=1; t <= MIDI.tracks; t++){ + MIDI.track[t-1] = {event: []}; // create new Track entry in Array + var headerValidation = file.readInt(4); + if ( headerValidation === -1 ) break; // EOF + if(headerValidation !== 0x4D54726B) return false; // Track chunk header validation failed. + file.readInt(4); // move pointer. get chunk size (bytes length) + var e = 0; // init event counter + var endOfTrack = false; // FLAG for track reading secuence breaking + // ** read EVENT CHUNK + var statusByte; + var laststatusByte; + while(!endOfTrack){ + e++; // increase by 1 event counter + MIDI.track[t-1].event[e-1] = {}; // create new event object, in events array + MIDI.track[t-1].event[e-1].deltaTime = file.readIntVLV(); // get DELTA TIME OF MIDI event (Variable Length Value) + statusByte = file.readInt(1); // read EVENT TYPE (STATUS BYTE) + if(statusByte === -1) break; // EOF + else if(statusByte >= 128) laststatusByte = statusByte; // NEW STATUS BYTE DETECTED + else{ // 'RUNNING STATUS' situation detected + statusByte = laststatusByte; // apply last loop, Status Byte + file.movePointer(-1); // move back the pointer (cause readed byte is not status byte) + } + // ** Identify EVENT + if(statusByte === 0xFF){ // Meta Event type + MIDI.track[t-1].event[e-1].type = 0xFF; // assign metaEvent code to array + MIDI.track[t-1].event[e-1].metaType = file.readInt(1); // assign metaEvent subtype + var metaEventLength = file.readIntVLV(); // get the metaEvent length + switch(MIDI.track[t-1].event[e-1].metaType){ + case 0x2F: // end of track, has no data byte + case -1: // EOF + endOfTrack = true; // change FLAG to force track reading loop breaking + break; + case 0x01: // Text Event + case 0x02: // Copyright Notice + case 0x03: // Sequence/Track Name (documentation: http://www.ta7.de/txt/musik/musi0006.htm) + case 0x06: // Marker + MIDI.track[t-1].event[e-1].data = file.readStr(metaEventLength); + break; + case 0x21: // MIDI PORT + case 0x59: // Key Signature + case 0x51: // Set Tempo + MIDI.track[t-1].event[e-1].data = file.readInt(metaEventLength); + break; + case 0x54: // SMPTE Offset + case 0x58: // Time Signature + MIDI.track[t-1].event[e-1].data = []; + MIDI.track[t-1].event[e-1].data[0] = file.readInt(1); + MIDI.track[t-1].event[e-1].data[1] = file.readInt(1); + MIDI.track[t-1].event[e-1].data[2] = file.readInt(1); + MIDI.track[t-1].event[e-1].data[3] = file.readInt(1); + break; + default : + file.readInt(metaEventLength); + MIDI.track[t-1].event[e-1].data = file.readInt(metaEventLength); + if (this.debug) console.info('Unimplemented 0xFF event! data block readed as Integer'); + } + }else{ // MIDI Control Events OR System Exclusive Events + statusByte = statusByte.toString(16).split(''); // split the status byte HEX representation, to obtain 4 bits values + if(!statusByte[1]) statusByte.unshift('0'); // force 2 digits + MIDI.track[t-1].event[e-1].type = parseInt(statusByte[0], 16);// first byte is EVENT TYPE ID + MIDI.track[t-1].event[e-1].channel = parseInt(statusByte[1], 16);// second byte is channel + switch(MIDI.track[t-1].event[e-1].type){ + case 0xF: // System Exclusive Events + var event_length = file.readIntVLV(); + MIDI.track[t-1].event[e-1].data = file.readInt(event_length); + if (this.debug) console.info('Unimplemented 0xF exclusive events! data block readed as Integer'); + break; + case 0xA: // Note Aftertouch + case 0xB: // Controller + case 0xE: // Pitch Bend Event + case 0x8: // Note off + case 0x9: // Note On + MIDI.track[t-1].event[e-1].data = []; + MIDI.track[t-1].event[e-1].data[0] = file.readInt(1); + MIDI.track[t-1].event[e-1].data[1] = file.readInt(1); + break; + case 0xC: // Program Change + case 0xD: // Channel Aftertouch + MIDI.track[t-1].event[e-1].data = file.readInt(1); + break; + case -1: // EOF + endOfTrack = true; // change FLAG to force track reading loop breaking + break; + default: + console.warn('Unknown EVENT detected.... reading cancelled!'); + return false; + } + } + } + } + return MIDI; + } +}; + + +if(typeof module !== 'undefined') module.exports = MIDIParser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/minmaxbounds/MinMaxBounds.js b/ui/src/phaser3-rex-plugins/plugins/utils/minmaxbounds/MinMaxBounds.js new file mode 100644 index 000000000..d1cca9d97 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/minmaxbounds/MinMaxBounds.js @@ -0,0 +1,35 @@ +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class MinMaxBounds { + constructor(min, max) { + if (IsPlainObject(min)) { + var config = min; + min = GetValue(config, 'min', undefined); + max = GetValue(config, 'max', undefined); + } + this.setMin(min); + this.setMax(max); + } + + setMin(value) { + this.min = value; + return this; + } + + setMax(value) { + this.max = value; + return this; + } + + clamp(value) { + if ((this.min !== undefined) && (value < this.min)) { + value = this.min; + } else if ((this.max !== undefined) && (value > this.max)) { + value = this.max; + } + return value; + } +} + +export default MinMaxBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/movement/MoveTo.js b/ui/src/phaser3-rex-plugins/plugins/utils/movement/MoveTo.js new file mode 100644 index 000000000..46d851602 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/movement/MoveTo.js @@ -0,0 +1,46 @@ +import Movement from './Movement.js'; + +class MoveTo { + constructor() { + this.start; + this.end; + this.value; + this.movement = new Movement(); + } + + init(start, end, speed) { + this.start = start; + this.end = end; + this.value = start; + this.movement + .setSpeed(speed) + .setAcceleration(0); + return this; + } + + stop() { + this.movement.reset(); + } + + update(delta) { + // delta in sec + var d = this.movement.getDeltaValue(delta); + if (this.start < this.end) { + this.value += d; + if (this.value >= this.end) { + this.value = this.end; + } + } else { + this.value -= d; + if (this.value <= this.end) { + this.value = this.end; + } + } + return this; + } + + get isMoving() { + return (this.value !== this.end); + } +} +export default MoveTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/movement/Movement.js b/ui/src/phaser3-rex-plugins/plugins/utils/movement/Movement.js new file mode 100644 index 000000000..ef2ab91f2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/movement/Movement.js @@ -0,0 +1,74 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +class Movement { + constructor(config) { + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.setValue(GetValue(o, 'value', 0)); + this.setSpeed(GetValue(o, 'speed', 0)); + this.setAcceleration(GetValue(o, 'acceleration', 0)); + return this; + } + + reset() { + this.setValue(0); + this.setSpeed(0); + this.setAcceleration(0); + } + + setValue(value) { + this.value = value; + return this; + } + + setSpeed(speed) { + // speed == 0 : stop + // speed > 0 : move + this.speed = speed; + return this; + } + + setAcceleration(acc) { + // acc == 0 : constant speed + // acc > 0 : acceleration + // acc < 0 : deceleration + this.acceleration = acc; + return this; + } + + updateSpeed(delta) { + // delta in sec + if (this.acceleration !== 0) { + this.speed += (this.acceleration * delta); + if (this.speed < 0) { + this.speed = 0; + } + } + return this; + } + + getDeltaValue(delta) { + // delta in sec + this.updateSpeed(delta); + if (this.speed <= 0) { + return 0; + } + return (this.speed * delta); + } + + update(delta) { + // delta in sec + this.updateSpeed(delta); + if (this.speed > 0) { + this.value += this.getDeltaValue(delta); + } + return this; + } + + get isMoving() { + return (this.speed > 0); + } +} +export default Movement; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/movement/SlowDown.js b/ui/src/phaser3-rex-plugins/plugins/utils/movement/SlowDown.js new file mode 100644 index 000000000..5c6fdca20 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/movement/SlowDown.js @@ -0,0 +1,61 @@ +import Movement from './Movement.js'; + +class SlowDown { + constructor() { + this.value; + this.dir; // true:+, false:- + this.movement = new Movement(); + } + + init(start, dir, speed, dec, end) { + this.value = start; + this.end = end; + if (end !== undefined) { + this.dir = (start < end); + } else { + this.dir = dir; + } + + this.movement + .setSpeed(speed) + .setAcceleration(-dec); + return this; + } + + stop() { + this.movement.reset(); + } + + update(delta) { + // delta in sec + var d = this.movement.getDeltaValue(delta); + if (!this.dir) { + d = -d; + } + + if (this.end === undefined) { + this.value += d; + } else { + if (d === 0) { + this.value = this.end; + } else { + this.value += d; + if (this.dir) { // + + if (this.value > this.end) { + this.value = this.end; + } + } else { // - + if (this.value < this.end) { + this.value = this.end; + } + } + } + } + return this; + } + + get isMoving() { + return this.movement.isMoving; + } +} +export default SlowDown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/mustache/mustache.min.js b/ui/src/phaser3-rex-plugins/plugins/utils/mustache/mustache.min.js new file mode 100644 index 000000000..3e5080309 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/mustache/mustache.min.js @@ -0,0 +1 @@ +(function defineMustache(global,factory){if(typeof exports==="object"&&exports&&typeof exports.nodeName!=="string"){factory(exports)}else if(typeof define==="function"&&define.amd){define(["exports"],factory)}else{global.Mustache={};factory(global.Mustache)}})(this,function mustacheFactory(mustache){var objectToString=Object.prototype.toString;var isArray=Array.isArray||function isArrayPolyfill(object){return objectToString.call(object)==="[object Array]"};function isFunction(object){return typeof object==="function"}function typeStr(obj){return isArray(obj)?"array":typeof obj}function escapeRegExp(string){return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}function hasProperty(obj,propName){return obj!=null&&typeof obj==="object"&&propName in obj}var regExpTest=RegExp.prototype.test;function testRegExp(re,string){return regExpTest.call(re,string)}var nonSpaceRe=/\S/;function isWhitespace(string){return!testRegExp(nonSpaceRe,string)}var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};function escapeHtml(string){return String(string).replace(/[&<>"'`=\/]/g,function fromEntityMap(s){return entityMap[s]})}var whiteRe=/\s*/;var spaceRe=/\s+/;var equalsRe=/\s*=/;var curlyRe=/\s*\}/;var tagRe=/#|\^|\/|>|\{|&|=|!/;function parseTemplate(template,tags){if(!template)return[];var sections=[];var tokens=[];var spaces=[];var hasTag=false;var nonSpace=false;function stripSpace(){if(hasTag&&!nonSpace){while(spaces.length)delete tokens[spaces.pop()]}else{spaces=[]}hasTag=false;nonSpace=false}var openingTagRe,closingTagRe,closingCurlyRe;function compileTags(tagsToCompile){if(typeof tagsToCompile==="string")tagsToCompile=tagsToCompile.split(spaceRe,2);if(!isArray(tagsToCompile)||tagsToCompile.length!==2)throw new Error("Invalid tags: "+tagsToCompile);openingTagRe=new RegExp(escapeRegExp(tagsToCompile[0])+"\\s*");closingTagRe=new RegExp("\\s*"+escapeRegExp(tagsToCompile[1]));closingCurlyRe=new RegExp("\\s*"+escapeRegExp("}"+tagsToCompile[1]))}compileTags(tags||mustache.tags);var scanner=new Scanner(template);var start,type,value,chr,token,openSection;while(!scanner.eos()){start=scanner.pos;value=scanner.scanUntil(openingTagRe);if(value){for(var i=0,valueLength=value.length;i0?sections[sections.length-1][4]:nestedTokens;break;default:collector.push(token)}}return nestedTokens}function Scanner(string){this.string=string;this.tail=string;this.pos=0}Scanner.prototype.eos=function eos(){return this.tail===""};Scanner.prototype.scan=function scan(re){var match=this.tail.match(re);if(!match||match.index!==0)return"";var string=match[0];this.tail=this.tail.substring(string.length);this.pos+=string.length;return string};Scanner.prototype.scanUntil=function scanUntil(re){var index=this.tail.search(re),match;switch(index){case-1:match=this.tail;this.tail="";break;case 0:match="";break;default:match=this.tail.substring(0,index);this.tail=this.tail.substring(index)}this.pos+=match.length;return match};function Context(view,parentContext){this.view=view;this.cache={".":this.view};this.parent=parentContext}Context.prototype.push=function push(view){return new Context(view,this)};Context.prototype.lookup=function lookup(name){var cache=this.cache;var value;if(cache.hasOwnProperty(name)){value=cache[name]}else{var context=this,names,index,lookupHit=false;while(context){if(name.indexOf(".")>0){value=context.view;names=name.split(".");index=0;while(value!=null&&index")value=this.renderPartial(token,context,partials,originalTemplate);else if(symbol==="&")value=this.unescapedValue(token,context);else if(symbol==="name")value=this.escapedValue(token,context);else if(symbol==="text")value=this.rawValue(token);if(value!==undefined)buffer+=value}return buffer};Writer.prototype.renderSection=function renderSection(token,context,partials,originalTemplate){var self=this;var buffer="";var value=context.lookup(token[1]);function subRender(template){return self.render(template,context,partials)}if(!value)return;if(isArray(value)){for(var j=0,valueLength=value.length;j (string | undefined), + + columns?: (number | undefined)[], + leftWidth?: number, + rightWidth?: number, + + rows?: (number | undefined)[], + topHeight?: number, + bottomHeight?: number, + + stretchMode?: 0 | 1 | 'scale' | 'repeat' | + { + edge?: 0 | 1 | 'scale' | 'repeat', + internal?: 0 | 1 | 'scale' | 'repeat', + }, + + maxFixedPartScale?: number, + maxFixedPartScaleX?: number, + maxFixedPartScaleY?: number, + + preserveRatio?: boolean, + } + +} + +declare class NinePatch extends Phaser.GameObjects.RenderTexture { + constructor( + scene: Phaser.Scene, + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + key: string, + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + key: string, + columns: (number | undefined)[], rows: (number | undefined)[], + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + key: string, baseFrame: string, + columns: (number | undefined)[], rows: (number | undefined)[], + config?: NinePatch.IConfig + ) + + resize(width: number, height: number): this; + + setBaseTexture( + key: string, + baseFrame: string | undefined, + columns: (number | undefined)[], + rows: (number | undefined)[] + ): this; + + setBaseTexture( + key: string, + baseFrame: string | undefined, + leftWidth: number, + rightWidth: number, + topHeight: number, + bottomHeight: number, + ): this; + + setStretchMode( + mode: 0 | 1 | 'scale' | 'repeat' | + { + edge?: 0 | 1 | 'scale' | 'repeat', + internal?: 0 | 1 | 'scale' | 'repeat', + } + ): this; + + setGetFrameNameCallback( + callback: (colIndex: number, rowIndex: number, baseFrame: string) => (string | undefined) + ): this; + + updateTexture(): this; + + setPreserveRatio(enable?: boolean): this; + preserveRatio: boolean; + + setMaxFixedPartScale(scaleX: number, scaleY?: number): this; + maxFixedPartScaleX: number; + maxFixedPartScaleY: number; + + readonly minWidth: number; + + readonly minHeight: number; + + readonly fixedPartScaleX: number; + + readonly fixedPartScaleY: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/NinePatch.js b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/NinePatch.js new file mode 100644 index 000000000..3ce3b926b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/NinePatch.js @@ -0,0 +1,131 @@ +import Methods from './Methods.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +var NinePatchBase = function (GOClass, type) { + class NinePatch extends GOClass { + constructor(scene, x, y, width, height, key, baseFrame, columns, rows, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 1); + height = GetValue(config, 'height', 1); + key = GetValue(config, 'key', undefined); + baseFrame = GetValue(config, 'baseFrame', undefined); + columns = GetValue(config, 'columns', undefined); + rows = GetValue(config, 'rows', undefined); + } else if (IsPlainObject(width)) { + config = width; + width = GetValue(config, 'width', 1); + height = GetValue(config, 'height', 1); + key = GetValue(config, 'key', undefined); + baseFrame = GetValue(config, 'baseFrame', undefined); + columns = GetValue(config, 'columns', undefined); + rows = GetValue(config, 'rows', undefined); + } else if (IsPlainObject(key)) { + config = key; + key = GetValue(config, 'key', undefined); + baseFrame = GetValue(config, 'baseFrame', undefined); + columns = GetValue(config, 'columns', undefined); + rows = GetValue(config, 'rows', undefined); + } else if (IsPlainObject(baseFrame)) { + config = baseFrame; + baseFrame = GetValue(config, 'baseFrame', undefined); + columns = GetValue(config, 'columns', undefined); + rows = GetValue(config, 'rows', undefined); + } else if (Array.isArray(baseFrame)) { + config = rows; + rows = columns; + columns = baseFrame; + baseFrame = GetValue(config, 'baseFrame', undefined); + } else if (IsPlainObject(columns)) { + config = columns; + columns = GetValue(config, 'columns', undefined); + rows = GetValue(config, 'rows', undefined); + } + + if (columns === undefined) { + var leftWidth = GetValue(config, 'leftWidth', undefined); + var rightWidth = GetValue(config, 'rightWidth', undefined); + if ((leftWidth !== undefined) && (rightWidth !== undefined)) { + columns = [leftWidth, undefined, rightWidth]; + } + } + + if (rows === undefined) { + var topHeight = GetValue(config, 'topHeight', undefined); + var bottomHeight = GetValue(config, 'bottomHeight', undefined); + if ((topHeight !== undefined) && (bottomHeight !== undefined)) { + rows = [topHeight, undefined, bottomHeight]; + } + } + + super(scene); + this.type = type; + this + .setPosition(x, y) + .setSize(width, height) + .setOrigin(0.5, 0.5); + + this.columns = {}; + this.rows = {}; + this.stretchMode = {}; + this._tileSprite = undefined; // Reserved for drawing image + this._image = undefined; // Reserved for drawing image + + this.setGetFrameNameCallback(GetValue(config, 'getFrameNameCallback', undefined)); + this.setStretchMode(GetValue(config, 'stretchMode', 0)); + this.setPreserveRatio(GetValue(config, 'preserveRatio', true)); + + var maxFixedPartScale = GetValue(config, 'maxFixedPartScale', 1); + var maxFixedPartScaleX = GetValue(config, 'maxFixedPartScaleX', maxFixedPartScale); + var maxFixedPartScaleY = GetValue(config, 'maxFixedPartScaleY', undefined); + this.setMaxFixedPartScale(maxFixedPartScaleX, maxFixedPartScaleY); + + this.setBaseTexture(key, baseFrame, columns, rows); + } + + get minWidth() { + return this.columns.minWidth; + } + + get minHeight() { + return this.rows.minHeight; + } + + get fixedPartScaleX() { + return this.columns.scale; + } + + get fixedPartScaleY() { + return this.rows.scale; + } + + resize(width, height) { + if ((this.width === width) && (this.height === height)) { + return this; + } + + if (super.resize) { + super.resize(width, height); + } else { + // Use setSize method for alternative + super.setSize(width, height); + } + this.updateTexture(); + + return this; + } + } + + Object.assign( + NinePatch.prototype, + Methods + ); + + return NinePatch; +} + +export default NinePatchBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/GetStretchMode.js b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/GetStretchMode.js new file mode 100644 index 000000000..149308cc1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/GetStretchMode.js @@ -0,0 +1,7 @@ +import IsEdge from '../utils/IsEdge.js'; + +var GetStretchMode = function(colIndex, rowIndex) { + return (IsEdge.call(this, colIndex, rowIndex)) ? this.stretchMode.edge : this.stretchMode.internal; +}; + +export default GetStretchMode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetBaseTexture.js b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetBaseTexture.js new file mode 100644 index 000000000..ff9540452 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetBaseTexture.js @@ -0,0 +1,139 @@ +import DeepClone from '../../object/DeepClone.js'; + +var SetBaseTexture = function (key, baseFrameName, columns, rows) { + if (Array.isArray(baseFrameName)) { + rows = columns; + columns = baseFrameName; + baseFrameName = undefined; + } + + if (baseFrameName == null) { + baseFrameName = '__BASE'; + } + + if ((typeof (columns) === 'number') && (arguments.length >= 6)) { + columns = [arguments[2], undefined, arguments[3]]; + rows = [arguments[4], undefined, arguments[5]]; + } else { + columns = DeepClone(columns); + rows = DeepClone(rows); + } + + this.textureKey = key; + this.baseFrameName = baseFrameName; + this.columns.data = columns; + this.columns.count = (columns) ? columns.length : 0; + this.columns.stretch = 0; + this.columns.minWidth = 0; + this.columns.scale = 1; + this.rows.data = rows; + this.rows.count = (rows) ? rows.length : 0; + this.rows.stretch = 0; + this.rows.minHeight = 0; + this.rows.scale = 1; + + var texture = this.scene.sys.textures.get(key); + if (!texture) { + this.clear(); + return this; + } + if (!columns || !rows) { + this.clear(); + return this; + } + + // Get remainder width/height for unknown width/height + var baseFrame = texture.get(baseFrameName); + var remainderTextureWidth = baseFrame.width; + var unknownColumnWidthCount = 0; + for (var i = 0, cnt = columns.length; i < cnt; i++) { + if (columns[i] === undefined) { + unknownColumnWidthCount++; + } else if (typeof (columns[i]) === 'number') { + remainderTextureWidth -= columns[i]; + } else { + remainderTextureWidth -= columns[i].width; + } + } + var unknownColumnWidth = remainderTextureWidth / unknownColumnWidthCount; + + var remainderTextureHeight = baseFrame.height; + var unknownRowHeightCount = 0; + for (var i = 0, cnt = rows.length; i < cnt; i++) { + if (rows[i] === undefined) { + unknownRowHeightCount++; + } else if (typeof (rows[i]) === 'number') { + remainderTextureHeight -= rows[i]; + } else { + remainderTextureHeight -= rows[i].width; + } + } + var unknownRowHeight = remainderTextureHeight / unknownRowHeightCount; + + var row, col, rowHeight, colWidth, frameName; + var offsetX = 0, offsetY = 0; + for (var j = 0, jcnt = rows.length; j < jcnt; j++) { + // Unknown height + if (rows[j] === undefined) { + rows[j] = unknownRowHeight; + } + + if (typeof (rows[j]) === 'number') { + rows[j] = { + height: rows[j], + stretch: (j % 2), + } + } + + row = rows[j]; + rowHeight = row.height; + + this.rows.stretch += (row.stretch | 0); + this.rows.minHeight += (row.stretch > 0) ? 0 : rowHeight; + + offsetX = 0; + for (var i = 0, icnt = columns.length; i < icnt; i++) { + // Unknown width + if (columns[i] === undefined) { + columns[i] = unknownColumnWidth; + } + + if (typeof (columns[i]) === 'number') { + columns[i] = { + width: columns[i], + stretch: (i % 2), + } + } + + col = columns[i]; + colWidth = col.width; + + if (j === 0) { + this.columns.stretch += (col.stretch | 0); + this.columns.minWidth += (col.stretch > 0) ? 0 : colWidth; + } + + if ((colWidth >= 1) && (rowHeight >= 1)) { + frameName = this.getFrameNameCallback(i, j, baseFrameName); + var frameNameType = typeof (frameName); + if ((frameNameType === 'string') || (frameNameType === 'number')) { + texture.add( + frameName, 0, + (offsetX + baseFrame.cutX), (offsetY + baseFrame.cutY), + colWidth, rowHeight + ); + // Do nothing if frameName is existed + } + } else { + // console.warn(`Size of Grid(${i},${j}) = ${colWidth}x${rowHeight}, which is invalid`); + } + offsetX += colWidth; + } + offsetY += rowHeight; + } + + this.updateTexture(); + return this; +} + +export default SetBaseTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetGetFrameNameCallback.js b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetGetFrameNameCallback.js new file mode 100644 index 000000000..5fdc76dbf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetGetFrameNameCallback.js @@ -0,0 +1,17 @@ +var SetGetFrameNameCallback = function(callback) { + if (callback === undefined) { + callback = DefaultGetFrameNameCallback; + } + this.getFrameNameCallback = callback; + return this; +} + +var DefaultGetFrameNameCallback = function (colIndex, rowIndex, baseFrameName) { + if (baseFrameName === '__BASE') { + return `${colIndex},${rowIndex}`; + } else { + return `${baseFrameName}_${colIndex},${rowIndex}`; + } +} + +export default SetGetFrameNameCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetMaxFixedPartScale.js b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetMaxFixedPartScale.js new file mode 100644 index 000000000..bf6bb4294 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetMaxFixedPartScale.js @@ -0,0 +1,11 @@ +var SetMaxFixedPartScale = function (scaleX, scaleY) { + if (scaleY === undefined) { + scaleY = scaleX; + } + + this.maxFixedPartScaleX = scaleX; + this.maxFixedPartScaleY = scaleY; + return this; +}; + +export default SetMaxFixedPartScale; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetPreserveRatio.js b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetPreserveRatio.js new file mode 100644 index 000000000..0850e1592 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetPreserveRatio.js @@ -0,0 +1,10 @@ +var SetPreserveRatio = function (enable) { + if (enable == undefined) { + enable = true; + } + + this.preserveRatio = enable; + return this; +} + +export default SetPreserveRatio; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetStretchMode.js b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetStretchMode.js new file mode 100644 index 000000000..be76c341d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetStretchMode.js @@ -0,0 +1,28 @@ +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +var SetStretchMode = function(mode) { + if (IsPlainObject(mode)) { + this.stretchMode.edge = parseMode(GetValue(mode, 'edge', 0)); + this.stretchMode.internal = parseMode(GetValue(mode, 'internal', 0)); + } else { + mode = parseMode(mode); + this.stretchMode.edge = mode; + this.stretchMode.internal = mode; + } + return this; +}; + +var parseMode = function (mode) { + if (typeof (mode) === 'string') { + mode = EXTENDMODE[mode]; + } + return mode; +} + +const EXTENDMODE = { + scale: 0, + repeat: 1, +} + +export default SetStretchMode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/UpdateTexture.js b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/UpdateTexture.js new file mode 100644 index 000000000..0b855eb8e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/UpdateTexture.js @@ -0,0 +1,106 @@ +var UpdateTexture = function () { + this.clear(); + + if (this.textureKey === undefined) { + return this; + } + var texture = this.scene.sys.textures.get(this.textureKey); + if (!texture) { + return this; + } + + var minWidth = this.columns.minWidth * this.maxFixedPartScaleX; // Fixed-part width + var minHeight = this.rows.minHeight * this.maxFixedPartScaleY; // Fixed-part height + var stretchWidth = this.width - minWidth; + var stretchHeight = this.height - minHeight; + var fixedPartScaleX = (stretchWidth >= 0) ? this.maxFixedPartScaleX : (this.width / minWidth); + var fixedPartScaleY = (stretchHeight >= 0) ? this.maxFixedPartScaleY : (this.height / minHeight); + + if (this.preserveRatio) { + var minScale = Math.min(fixedPartScaleX, fixedPartScaleY); + if (fixedPartScaleX > minScale) { + var compensationWidth = (fixedPartScaleX - minScale) * minWidth; + if (stretchWidth >= 0) { + stretchWidth += compensationWidth; + } else { + stretchWidth = compensationWidth; + } + fixedPartScaleX = minScale; + } + if (fixedPartScaleY > minScale) { + var compensationHeight = (fixedPartScaleY - minScale) * minHeight; + if (stretchHeight >= 0) { + stretchHeight += compensationHeight; + } else { + stretchHeight = compensationHeight; + } + fixedPartScaleY = minScale; + } + } + this.columns.scale = fixedPartScaleX; + this.rows.scale = fixedPartScaleY; + + var proportionWidth; + if (stretchWidth > 0) { + proportionWidth = (this.columns.stretch > 0) ? (stretchWidth / this.columns.stretch) : 0; + } else { + proportionWidth = 0; + } + + var proportionHeight; + if (stretchHeight > 0) { + proportionHeight = (this.rows.stretch > 0) ? (stretchHeight / this.rows.stretch) : 0; + } else { + proportionHeight = 0; + } + + var frameName, col, row, colWidth, rowHeight; + var offsetX = 0, offsetY = 0; + var imageType; + + this._beginDraw(); + for (var j = 0, jcnt = this.rows.count; j < jcnt; j++) { + row = this.rows.data[j]; + rowHeight = (row.stretch === 0) ? (row.height * fixedPartScaleY) : (proportionHeight * row.stretch); + + offsetX = 0; + for (var i = 0, icnt = this.columns.count; i < icnt; i++) { + col = this.columns.data[i]; + colWidth = (col.stretch === 0) ? (col.width * fixedPartScaleX) : (proportionWidth * col.stretch); + + frameName = this.getFrameNameCallback(i, j, this.baseFrameName); + if (texture.has(frameName) && (colWidth > 0) && (rowHeight > 0)) { + if ((row.stretch === 0) && (col.stretch === 0)) { // Fixed parts + imageType = 0; // Draw image + } else { // Stretchable parts + if (this.getStretchMode(i, j) === 0) { // Scaled image + imageType = 0; // Draw scaled image + } else { // Repeat tile-sprite + imageType = 1; // Draw tile-sprite + } + } + + if (imageType === 0) { + this._drawImage( + this.textureKey, frameName, + offsetX, offsetY, + colWidth, rowHeight + ); + } else { + this._drawTileSprite( + this.textureKey, frameName, + offsetX, offsetY, + colWidth, rowHeight + ); + } + } + + offsetX += colWidth; + } + + offsetY += rowHeight; + } + this._endDraw(); +} + +export default UpdateTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/utils/IsEdge.js b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/utils/IsEdge.js new file mode 100644 index 000000000..3f4f78048 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/utils/IsEdge.js @@ -0,0 +1,6 @@ +var IsEdge = function (colIndex, rowIndex) { + return (colIndex === 0) || (colIndex === (this.columns.count - 1)) || + (rowIndex === 0) || (rowIndex === (this.rows.count - 1)); +} + +export default IsEdge; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/AreValuesEqual.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/AreValuesEqual.js new file mode 100644 index 000000000..9fac86016 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/AreValuesEqual.js @@ -0,0 +1,32 @@ +var AreValuesEqual = function (obj1, obj2) { + if (!(IsObjectType(obj1)) || !IsObjectType(obj2)) { + return false; + } + + var keys1 = Object.keys(obj1), + keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) { + return false; + } + + return keys1.every(function (key) { + if (!obj2.hasOwnProperty(key)) { + return false; + } + + var value1 = obj1[key]; + var value2 = obj2[key]; + if (IsObjectType(value1)) { + return AreValuesEqual(value1, value2); + } + + return value1 === value2; + }) +} + +var IsObjectType = function (value) { + return (typeof (value) === 'object') && (value !== null); +} + +export default AreValuesEqual; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/Class.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/Class.js new file mode 100644 index 000000000..f40f5aae5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/Class.js @@ -0,0 +1,248 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +// Taken from klasse by mattdesl https://github.com/mattdesl/klasse + +function hasGetterOrSetter (def) +{ + return (!!def.get && typeof def.get === 'function') || (!!def.set && typeof def.set === 'function'); +} + +function getProperty (definition, k, isClassDescriptor) +{ + // This may be a lightweight object, OR it might be a property that was defined previously. + + // For simple class descriptors we can just assume its NOT previously defined. + var def = (isClassDescriptor) ? definition[k] : Object.getOwnPropertyDescriptor(definition, k); + + if (!isClassDescriptor && def.value && typeof def.value === 'object') + { + def = def.value; + } + + // This might be a regular property, or it may be a getter/setter the user defined in a class. + if (def && hasGetterOrSetter(def)) + { + if (typeof def.enumerable === 'undefined') + { + def.enumerable = true; + } + + if (typeof def.configurable === 'undefined') + { + def.configurable = true; + } + + return def; + } + else + { + return false; + } +} + +function hasNonConfigurable (obj, k) +{ + var prop = Object.getOwnPropertyDescriptor(obj, k); + + if (!prop) + { + return false; + } + + if (prop.value && typeof prop.value === 'object') + { + prop = prop.value; + } + + if (prop.configurable === false) + { + return true; + } + + return false; +} + +/** + * Extends the given `myClass` object's prototype with the properties of `definition`. + * + * @function extend + * @param {Object} ctor The constructor object to mix into. + * @param {Object} definition A dictionary of functions for the class. + * @param {boolean} isClassDescriptor Is the definition a class descriptor? + * @param {Object} [extend] The parent constructor object. + */ +function extend (ctor, definition, isClassDescriptor, extend) +{ + for (var k in definition) + { + if (!definition.hasOwnProperty(k)) + { + continue; + } + + var def = getProperty(definition, k, isClassDescriptor); + + if (def !== false) + { + // If Extends is used, we will check its prototype to see if the final variable exists. + + var parent = extend || ctor; + + if (hasNonConfigurable(parent.prototype, k)) + { + // Just skip the final property + if (Class.ignoreFinals) + { + continue; + } + + // We cannot re-define a property that is configurable=false. + // So we will consider them final and throw an error. This is by + // default so it is clear to the developer what is happening. + // You can set ignoreFinals to true if you need to extend a class + // which has configurable=false; it will simply not re-define final properties. + throw new Error('cannot override final property \'' + k + '\', set Class.ignoreFinals = true to skip'); + } + + Object.defineProperty(ctor.prototype, k, def); + } + else + { + ctor.prototype[k] = definition[k]; + } + } +} + +/** + * Applies the given `mixins` to the prototype of `myClass`. + * + * @function mixin + * @param {Object} myClass The constructor object to mix into. + * @param {Object|Array} mixins The mixins to apply to the constructor. + */ +function mixin (myClass, mixins) +{ + if (!mixins) + { + return; + } + + if (!Array.isArray(mixins)) + { + mixins = [ mixins ]; + } + + for (var i = 0; i < mixins.length; i++) + { + extend(myClass, mixins[i].prototype || mixins[i]); + } +} + +/** + * Creates a new class with the given descriptor. + * The constructor, defined by the name `initialize`, + * is an optional function. If unspecified, an anonymous + * function will be used which calls the parent class (if + * one exists). + * + * You can also use `Extends` and `Mixins` to provide subclassing + * and inheritance. + * + * @class Phaser.Class + * @constructor + * @param {Object} definition a dictionary of functions for the class + * @example + * + * var MyClass = new Phaser.Class({ + * + * initialize: function() { + * this.foo = 2.0; + * }, + * + * bar: function() { + * return this.foo + 5; + * } + * }); + */ +function Class (definition) +{ + if (!definition) + { + definition = {}; + } + + // The variable name here dictates what we see in Chrome debugger + var initialize; + var Extends; + + if (definition.initialize) + { + if (typeof definition.initialize !== 'function') + { + throw new Error('initialize must be a function'); + } + + initialize = definition.initialize; + + // Usually we should avoid 'delete' in V8 at all costs. + // However, its unlikely to make any performance difference + // here since we only call this on class creation (i.e. not object creation). + delete definition.initialize; + } + else if (definition.Extends) + { + var base = definition.Extends; + + initialize = function () + { + base.apply(this, arguments); + }; + } + else + { + initialize = function () {}; + } + + if (definition.Extends) + { + initialize.prototype = Object.create(definition.Extends.prototype); + initialize.prototype.constructor = initialize; + + // For getOwnPropertyDescriptor to work, we need to act directly on the Extends (or Mixin) + + Extends = definition.Extends; + + delete definition.Extends; + } + else + { + initialize.prototype.constructor = initialize; + } + + // Grab the mixins, if they are specified... + var mixins = null; + + if (definition.Mixins) + { + mixins = definition.Mixins; + delete definition.Mixins; + } + + // First, mixin if we can. + mixin(initialize, mixins); + + // Now we grab the actual definition which defines the overrides. + extend(initialize, definition, true, Extends); + + return initialize; +} + +Class.extend = extend; +Class.mixin = mixin; +Class.ignoreFinals = false; + +export default Class; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/Clear.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/Clear.js new file mode 100644 index 000000000..6dce08da0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/Clear.js @@ -0,0 +1,17 @@ +var Clear = function (obj) { + if ((typeof (obj) !== 'object') || (obj === null)) { + return obj; + } + + if (Array.isArray(obj)) { + obj.length = 0; + } else { + for (var key in obj) { + delete obj[key]; + } + } + + return obj; +} + +export default Clear; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/Clone.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/Clone.js new file mode 100644 index 000000000..4a4f0d3a3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/Clone.js @@ -0,0 +1,32 @@ +import Clear from './Clear.js'; + +/** + * Shallow Object Clone. Will not out nested objects. + * @param {object} obj JSON object + * @param {object} ret JSON object to return, set null to return a new object + * @returns {object} this object + */ +var Clone = function (obj, out) { + var objIsArray = Array.isArray(obj); + + if (out === undefined) { + out = (objIsArray) ? [] : {}; + } else { + Clear(out); + } + + if (objIsArray) { + out.length = obj.length; + for (var i = 0, cnt = obj.length; i < cnt; i++) { + out[i] = obj[i]; + } + } else { + for (var key in obj) { + out[key] = obj[key]; + } + } + + return out; +}; + +export default Clone; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/CopyProperty.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/CopyProperty.js new file mode 100644 index 000000000..f2515da4d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/CopyProperty.js @@ -0,0 +1,21 @@ +var CopyProperty = function (from, to, key) { + if (typeof (key) === 'string') { + if (from.hasOwnProperty(key)) { + to[key] = from[key]; + } + } else { + var keys = key; + if (Array.isArray(keys)) { + for (var i = 0, cnt = keys.length; i < cnt; i++) { + CopyProperty(from, to, keys[i]); + } + } else { + for (var key in keys) { + CopyProperty(from, to, key); + } + } + } +} + + +export default CopyProperty; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/DeepClone.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/DeepClone.js new file mode 100644 index 000000000..8a2c0e789 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/DeepClone.js @@ -0,0 +1,31 @@ +import IsPlainObject from './IsPlainObject.js'; + +var DeepClone = function (inObject) { + var outObject; + var value; + var key; + + if ((inObject == null) || (typeof inObject !== 'object')) { + // inObject is not an object + return inObject; + } + + // Create an array or object to hold the values + outObject = Array.isArray(inObject) ? [] : {}; + + if (IsPlainObject(inObject)) { + for (key in inObject) { + value = inObject[key]; + + // Recursively (deep) copy for nested objects, including arrays + outObject[key] = DeepClone(value); + } + + } else { + outObject = inObject; + } + + return outObject; +}; + +export default DeepClone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/DeepMerge.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/DeepMerge.js new file mode 100644 index 000000000..76ac6b527 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/DeepMerge.js @@ -0,0 +1,22 @@ +import DeepClone from './DeepClone.js'; + +var DeepMerge = function (toObj, fromObj) { + if (fromObj === undefined) { + return toObj; + } + + for (var key in fromObj) { + if (!toObj.hasOwnProperty(key)) { + // Only add nonexistent property + toObj[key] = DeepClone(fromObj[key]); + } else { + var value = toObj[key]; + if (value && (typeof (value) === 'object')) { + DeepMerge(value, fromObj[key]); + } + } + } + return toObj; +} + +export default DeepMerge; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/ExtractByPrefix.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/ExtractByPrefix.js new file mode 100644 index 000000000..9f6a1851c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/ExtractByPrefix.js @@ -0,0 +1,31 @@ +var ExtractByPrefix = function (obj, prefix, delimiter, out) { + if (delimiter === undefined) { + delimiter = '.'; + } + + if (out === undefined) { + out = {}; + } + + if (!obj) { + return out; + } + + if (prefix in obj) { + return Object.assign(out, obj[prefix]) + } + + prefix += delimiter; + + for (var key in obj) { + if (!key.startsWith(prefix)) { + continue; + } + + out[key.replace(prefix, '')] = obj[key]; + } + + return out; +} + +export default ExtractByPrefix; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/ForEachLeafNode.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/ForEachLeafNode.js new file mode 100644 index 000000000..2a78bf72d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/ForEachLeafNode.js @@ -0,0 +1,55 @@ +var ForEachLeafNode = function (root, callback, scope, dfs) { + if (dfs) { + DepthFirstSearch(root, null, null, root, callback, scope); + } else { + BreadthFirstSearch(root, callback, scope); + } +} + +var DepthFirstSearch = function (obj, key, parent, root, callback, scope) { + if (typeof (obj) === 'object') { + for (var k in obj) { + var skip = DepthFirstSearch(obj[k], k, obj, root, callback, scope); + if (skip) { + return skip; + } + } + return false; + } else { + var skip; + if (scope) { + skip = callback.call(scope, obj, key, parent, root); + } else { + skip = callback(obj, key, parent, root); + } + return skip; + } +} + +var BreadthFirstSearch = function (root, callback, scope) { + var queue = [{ parent: null, key: null, self: root }]; + while (queue.length > 0) { + var current = queue.shift(); + var obj = current.self; + var key = current.key; + var parent = current.parent; + if (typeof (obj) === 'object') { + for (var k in obj) { + queue.push({ parent: obj, key: k, self: obj[k] }); + } + } else { + var skip; + if (scope) { + skip = callback.call(scope, obj, key, parent, root); + } else { + skip = callback(obj, key, parent, root); + } + + if (skip) { + return; + } + } + } +} + +export default ForEachLeafNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/GetAdvancedValue.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/GetAdvancedValue.js new file mode 100644 index 000000000..19ccdaf3f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/GetAdvancedValue.js @@ -0,0 +1,74 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +import MATH from '../../math/const.js'; +import GetValue from './GetValue.js'; + +/** + * Retrieves a value from an object. Allows for more advanced selection options, including: + * + * Allowed types: + * + * Implicit + * { + * x: 4 + * } + * + * From function + * { + * x: function () + * } + * + * Randomly pick one element from the array + * { + * x: [a, b, c, d, e, f] + * } + * + * Random integer between min and max: + * { + * x: { randInt: [min, max] } + * } + * + * Random float between min and max: + * { + * x: { randFloat: [min, max] } + * } + * + * + * @function Phaser.Utils.Objects.GetAdvancedValue + * @since 3.0.0 + * + * @param {object} source - The object to retrieve the value from. + * @param {string} key - The name of the property to retrieve from the object. If a property is nested, the names of its preceding properties should be separated by a dot (`.`) - `banner.hideBanner` would return the value of the `hideBanner` property from the object stored in the `banner` property of the `source` object. + * @param {*} defaultValue - The value to return if the `key` isn't found in the `source` object. + * + * @return {*} The value of the requested key. + */ +var GetAdvancedValue = function (source, key, defaultValue) { + var value = GetValue(source, key, null); + + if (value === null) { + return defaultValue; + } + else if (Array.isArray(value)) { + return MATH.RND.pick(value); + } + else if (typeof value === 'object') { + if (value.hasOwnProperty('randInt')) { + return MATH.RND.integerInRange(value.randInt[0], value.randInt[1]); + } + else if (value.hasOwnProperty('randFloat')) { + return MATH.RND.realInRange(value.randFloat[0], value.randFloat[1]); + } + } + else if (typeof value === 'function') { + return value(key); + } + + return value; +}; + +export default GetAdvancedValue; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/GetFastValue.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/GetFastValue.js new file mode 100644 index 000000000..8e74fabe6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/GetFastValue.js @@ -0,0 +1,37 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Finds the key within the top level of the {@link source} object, or returns {@link defaultValue} + * + * @function Phaser.Utils.Objects.GetFastValue + * @since 3.0.0 + * + * @param {object} source - The object to search + * @param {string} key - The key for the property on source. Must exist at the top level of the source object (no periods) + * @param {*} [defaultValue] - The default value to use if the key does not exist. + * + * @return {*} The value if found; otherwise, defaultValue (null if none provided) + */ +var GetFastValue = function (source, key, defaultValue) +{ + var t = typeof(source); + + if (!source || t === 'number' || t === 'string') + { + return defaultValue; + } + else if (source.hasOwnProperty(key) && source[key] !== undefined) + { + return source[key]; + } + else + { + return defaultValue; + } +}; + +export default GetFastValue; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/GetPartialData.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/GetPartialData.js new file mode 100644 index 000000000..1b756731d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/GetPartialData.js @@ -0,0 +1,21 @@ +var GetPartialData = function (obj, keys, out) { + if (out === undefined) { + out = {}; + } + + if (Array.isArray(keys)) { + var key; + for (var i = 0, cnt = keys.length; i < cnt; i++) { + key = keys[i]; + out[key] = obj[key]; + } + } else { + for (var key in keys) { + out[key] = obj[key]; + } + } + + return out; +} + +export default GetPartialData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/GetValue.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/GetValue.js new file mode 100644 index 000000000..a4aaad262 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/GetValue.js @@ -0,0 +1,57 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +// Source object +// The key as a string, or an array of keys, i.e. 'banner', or 'banner.hideBanner' +// The default value to use if the key doesn't exist + +/** + * Retrieves a value from an object. + * + * @function Phaser.Utils.Objects.GetValue + * @since 3.0.0 + * + * @param {object} source - The object to retrieve the value from. + * @param {string} key - The name of the property to retrieve from the object. If a property is nested, the names of its preceding properties should be separated by a dot (`.`) - `banner.hideBanner` would return the value of the `hideBanner` property from the object stored in the `banner` property of the `source` object. + * @param {*} defaultValue - The value to return if the `key` isn't found in the `source` object. + * + * @return {*} The value of the requested key. + */ +var GetValue = function (source, key, defaultValue) { + if (!source || typeof source === 'number') { + return defaultValue; + } + else if (source.hasOwnProperty(key)) { + return source[key]; + } + else if (key.indexOf('.') !== -1) { + var keys = key.split('.'); + var parent = source; + var value = defaultValue; + + // Use for loop here so we can break early + for (var i = 0; i < keys.length; i++) { + if (parent.hasOwnProperty(keys[i])) { + // Yes it has a key property, let's carry on down + value = parent[keys[i]]; + + parent = parent[keys[i]]; + } + else { + // Can't go any further, so reset to default + value = defaultValue; + break; + } + } + + return value; + } + else { + return defaultValue; + } +}; + +export default GetValue; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/GetValueFromAliasKeys.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/GetValueFromAliasKeys.js new file mode 100644 index 000000000..02761d658 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/GetValueFromAliasKeys.js @@ -0,0 +1,17 @@ +import HasValue from './HasValue.js'; +import GetValue from './GetValue.js'; + +var GetValueFromAliasKeys = function (source, key0, key1, key2, defaultValue) { + if (HasValue(source, key0)) { + return GetValue(source, key0); + } else if (key1 && HasValue(source, key1)) { + return GetValue(source, key1); + } else if (key2 && HasValue(source, key2)) { + return GetValue(source, key2); + } else { + return defaultValue; + } + +} + +export default GetValueFromAliasKeys; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/HasAll.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/HasAll.js new file mode 100644 index 000000000..1233a1c4a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/HasAll.js @@ -0,0 +1,31 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Verifies that an object contains all requested keys + * + * @function Phaser.Utils.Objects.HasAll + * @since 3.0.0 + * + * @param {object} source - an object on which to check for key existence + * @param {string[]} keys - an array of keys to ensure the source object contains + * + * @return {boolean} true if the source object contains all keys, false otherwise. + */ +var HasAll = function (source, keys) +{ + for (var i = 0; i < keys.length; i++) + { + if (!source.hasOwnProperty(keys[i])) + { + return false; + } + } + + return true; +}; + +export default HasAll; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/HasAny.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/HasAny.js new file mode 100644 index 000000000..1c24fd182 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/HasAny.js @@ -0,0 +1,31 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Verifies that an object contains at least one of the requested keys + * + * @function Phaser.Utils.Objects.HasAny + * @since 3.0.0 + * + * @param {object} source - an object on which to check for key existence + * @param {string[]} keys - an array of keys to search the object for + * + * @return {boolean} true if the source object contains at least one of the keys, false otherwise + */ +var HasAny = function (source, keys) +{ + for (var i = 0; i < keys.length; i++) + { + if (source.hasOwnProperty(keys[i])) + { + return true; + } + } + + return false; +}; + +export default HasAny; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/HasValue.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/HasValue.js new file mode 100644 index 000000000..a7a3aa131 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/HasValue.js @@ -0,0 +1,30 @@ +var HasValue = function (source, key) { + if (!source || typeof source === 'number') { + return false; + } + else if (source.hasOwnProperty(key)) { + return true; + } + else if (key.indexOf('.') !== -1) { + var keys = key.split('.'); + var parent = source; + + // Use for loop here so we can break early + for (var i = 0; i < keys.length; i++) { + if (parent.hasOwnProperty(keys[i])) { + parent = parent[keys[i]]; + } + else { + // Can't go any further + return false; + } + } + + return true; + } + else { + return false; + } +}; + +export default HasValue; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/IndexOf.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/IndexOf.js new file mode 100644 index 000000000..1b92a45d8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/IndexOf.js @@ -0,0 +1,14 @@ +var IndexOf = function (obj, child) { + if (Array.isArray(obj)) { + return obj.indexOf(child); + } else { + for (var key in obj) { + if (obj[key] === child) { + return key; + } + } + return null; + } +} + +export default IndexOf; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/IsArray.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/IsArray.js new file mode 100644 index 000000000..40beae5d9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/IsArray.js @@ -0,0 +1,4 @@ +var IsArray = function(obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; +} +export default IsArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/IsEmpty.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/IsEmpty.js new file mode 100644 index 000000000..0519528be --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/IsEmpty.js @@ -0,0 +1,8 @@ +var IsEmpty = function (source) { + for (var k in source) { + return false; + } + return true; +}; + +export default IsEmpty; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/IsFunction.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/IsFunction.js new file mode 100644 index 000000000..81f2b2e1b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/IsFunction.js @@ -0,0 +1,5 @@ +var IsFunction = function (obj) { + return obj && (typeof(obj) === 'function'); +}; + +export default IsFunction; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/IsKeyValueEqual.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/IsKeyValueEqual.js new file mode 100644 index 000000000..4a864c2ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/IsKeyValueEqual.js @@ -0,0 +1,21 @@ +var IsKeyValueEqual = function (objA, objB) { + for (var key in objA) { + if (!(key in objB)) { + return false; + } + + if (objA[key] !== objB[key]) { + return false; + } + } + + for (var key in objB) { + if (!(key in objA)) { + return false; + } + } + + return true; +} + +export default IsKeyValueEqual; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/IsPlainObject.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/IsPlainObject.js new file mode 100644 index 000000000..40c5cfe57 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/IsPlainObject.js @@ -0,0 +1,50 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * This is a slightly modified version of jQuery.isPlainObject. + * A plain object is an object whose internal class property is [object Object]. + * + * @function Phaser.Utils.Objects.IsPlainObject + * @since 3.0.0 + * + * @param {object} obj - The object to inspect. + * + * @return {boolean} `true` if the object is plain, otherwise `false`. + */ +var IsPlainObject = function (obj) +{ + // Not plain objects: + // - Any object or value whose internal [[Class]] property is not "[object Object]" + // - DOM nodes + // - window + if (typeof(obj) !== 'object' || obj.nodeType || obj === obj.window) + { + return false; + } + + // Support: Firefox <20 + // The try/catch suppresses exceptions thrown when attempting to access + // the "constructor" property of certain host objects, ie. |window.location| + // https://bugzilla.mozilla.org/show_bug.cgi?id=814622 + try + { + if (obj.constructor && !({}).hasOwnProperty.call(obj.constructor.prototype, 'isPrototypeOf')) + { + return false; + } + } + catch (e) + { + return false; + } + + // If the function hasn't returned already, we're confident that + // |obj| is a plain object, created by {} or constructed with new Object + return true; +}; + +export default IsPlainObject; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/Merge.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/Merge.js new file mode 100644 index 000000000..0a4117b7a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/Merge.js @@ -0,0 +1,39 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Clone from './Clone.js'; + +/** + * Creates a new Object using all values from obj1 and obj2. + * If a value exists in both obj1 and obj2, the value in obj1 is used. + * + * This is only a shallow copy. Deeply nested objects are not cloned, so be sure to only use this + * function on shallow objects. + * + * @function Phaser.Utils.Objects.Merge + * @since 3.0.0 + * + * @param {object} obj1 - The first object. + * @param {object} obj2 - The second object. + * + * @return {object} A new object containing the union of obj1's and obj2's properties. + */ +var Merge = function (obj1, obj2) +{ + var clone = Clone(obj1); + + for (var key in obj2) + { + if (!clone.hasOwnProperty(key)) + { + clone[key] = obj2[key]; + } + } + + return clone; +}; + +export default Merge; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/MergeRight.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/MergeRight.js new file mode 100644 index 000000000..c96192be6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/MergeRight.js @@ -0,0 +1,37 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import Clone from './Clone.js'; + +/** + * Creates a new Object using all values from obj1. + * + * Then scans obj2. If a property is found in obj2 that *also* exists in obj1, the value from obj2 is used, otherwise the property is skipped. + * + * @function Phaser.Utils.Objects.MergeRight + * @since 3.0.0 + * + * @param {object} obj1 - The first object to merge. + * @param {object} obj2 - The second object to merge. Keys from this object which also exist in `obj1` will be copied to `obj1`. + * + * @return {object} The merged object. `obj1` and `obj2` are not modified. + */ +var MergeRight = function (obj1, obj2) +{ + var clone = Clone(obj1); + + for (var key in obj2) + { + if (clone.hasOwnProperty(key)) + { + clone[key] = obj2[key]; + } + } + + return clone; +}; + +export default MergeRight; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/NOOP.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/NOOP.js new file mode 100644 index 000000000..64f7394e7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/NOOP.js @@ -0,0 +1,5 @@ +var NOOP = function () { + // NOOP +}; + +export default NOOP; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/RemoveItem.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/RemoveItem.js new file mode 100644 index 000000000..0971ea2ec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/RemoveItem.js @@ -0,0 +1,19 @@ +import SpliceOne from '../array/SpliceOne.js'; + +var RemoveItem = function (obj, item) { + if (Array.isArray(obj)) { + var index = obj.indexOf(item); + if (index !== -1) { + SpliceOne(obj, index); + } + } else { + for (var key in obj) { + if (obj[key] === item) { + delete obj[key]; + return; + } + } + } +} + +export default RemoveItem; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/object/SetValue.js b/ui/src/phaser3-rex-plugins/plugins/utils/object/SetValue.js new file mode 100644 index 000000000..a866c6e94 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/object/SetValue.js @@ -0,0 +1,72 @@ +var IsInValidKey = function (keys) { + return (keys == null) || (keys === '') || (keys.length === 0); +}; + +var GetEntry = function (target, keys, defaultEntry) { + var entry = target; + if (IsInValidKey(keys)) { + //entry = root; + } else { + if (typeof (keys) === 'string') { + keys = keys.split('.'); + } + + var key; + for (var i = 0, cnt = keys.length; i < cnt; i++) { + key = keys[i]; + if ((entry[key] == null) || (typeof (entry[key]) !== 'object')) { + var newEntry; + if (i === cnt - 1) { + if (defaultEntry === undefined) { + newEntry = {}; + } else { + newEntry = defaultEntry; + } + } else { + newEntry = {}; + } + + entry[key] = newEntry; + } + + entry = entry[key]; + } + } + + return entry; +}; + +var SetValue = function (target, keys, value, delimiter) { + if (delimiter === undefined) { + delimiter = '.'; + } + + // no object + if (typeof (target) !== 'object') { + return; + } + + // invalid key + else if (IsInValidKey(keys)) { + // don't erase target + if (value == null) { + return; + } + // set target to another object + else if (typeof (value) === 'object') { + target = value; + } + } else { + if (typeof (keys) === 'string') { + keys = keys.split(delimiter); + } + + var lastKey = keys.pop(); + var entry = GetEntry(target, keys); + entry[lastKey] = value; + } + + return target; +}; + +export default SetValue; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/origin/ChangeOrigin.js b/ui/src/phaser3-rex-plugins/plugins/utils/origin/ChangeOrigin.js new file mode 100644 index 000000000..c3ac30aba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/origin/ChangeOrigin.js @@ -0,0 +1,22 @@ +const RotateAround = Phaser.Math.RotateAround; + +var ChangeOrigin = function (gameObject, originX, originY) { + if (originY === undefined) { + originY = originX; + } + + var deltaXY = { + x: (originX - gameObject.originX) * gameObject.displayWidth, + y: (originY - gameObject.originY) * gameObject.displayHeight + } + RotateAround(deltaXY, 0, 0, gameObject.rotation); + + gameObject.originX = originX; + gameObject.originY = originY; + gameObject.x = gameObject.x + deltaXY.x; + gameObject.y = gameObject.y + deltaXY.y; + + return gameObject; +} + +export default ChangeOrigin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/padding/PaddingMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/padding/PaddingMethods.js new file mode 100644 index 000000000..be6979705 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/padding/PaddingMethods.js @@ -0,0 +1,38 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +var GetPadding = function (padding, key) { + if (key === undefined) { + return padding; + } + return padding[key]; +} + +var SetPadding = function (padding, key, value) { + if (padding === undefined) { + padding = {}; + } + if (key === undefined) { + key = 0; + } + + var keyType = typeof (key); + if (keyType === 'string') { + padding[key] = value; + } else if (keyType === 'number') { + padding.left = key; + padding.right = key; + padding.top = key; + padding.bottom = key; + } else { + padding.left = GetValue(key, 'left', 0); + padding.right = GetValue(key, 'right', 0); + padding.top = GetValue(key, 'top', 0); + padding.bottom = GetValue(key, 'bottom', 0); + } + return padding; +} + +export { + GetPadding, + SetPadding +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/position/GameObjectLocalXYToWorldXY.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/position/GameObjectLocalXYToWorldXY.d.ts new file mode 100644 index 000000000..4e5a29bd5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/position/GameObjectLocalXYToWorldXY.d.ts @@ -0,0 +1,8 @@ +export default GameObjectLocalXYToWorldXY; + +declare function GameObjectLocalXYToWorldXY( + gameObject: Phaser.GameObjects.GameObject, + localX: number, + localY: number, + out?: { x: number, y: number } | true +): { x: number, y: number }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/position/GameObjectLocalXYToWorldXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/position/GameObjectLocalXYToWorldXY.js new file mode 100644 index 000000000..82644be0a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/position/GameObjectLocalXYToWorldXY.js @@ -0,0 +1,33 @@ +const TransformMatrix = Phaser.GameObjects.Components.TransformMatrix; + +var GameObjectLocalXYToWorldXY = function (gameObject, localX, localY, out) { + if (out === undefined) { + out = {} + } else if (out === true) { + out = globOut; + } + + var px = localX - (gameObject.width * gameObject.originX); + var py = localY - (gameObject.height * gameObject.originY); + + if (tempMatrix === undefined) { + tempMatrix = new TransformMatrix(); + parentMatrix = new TransformMatrix(); + } + + if (gameObject.parentContainer) { + gameObject.getWorldTransformMatrix(tempMatrix, parentMatrix); + } + else { + tempMatrix.applyITRS(gameObject.x, gameObject.y, gameObject.rotation, gameObject.scaleX, gameObject.scaleY); + } + + tempMatrix.transformPoint(px, py, out); + + return out; +} + +var tempMatrix, parentMatrix; +var globOut = {}; + +export default GameObjectLocalXYToWorldXY \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/position/ScreenXYToWorldXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/position/ScreenXYToWorldXY.js new file mode 100644 index 000000000..ecc102f9c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/position/ScreenXYToWorldXY.js @@ -0,0 +1,14 @@ +var ScreenXYToWorldXY = function (screenX, screenY, camera, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globalOut; + } + + camera.getWorldPoint(screenX, screenY, out); + return out; +} + +var globalOut = {}; + +export default ScreenXYToWorldXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/position/WorldXYToGameObjectLocalXY.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/position/WorldXYToGameObjectLocalXY.d.ts new file mode 100644 index 000000000..4b2d27010 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/position/WorldXYToGameObjectLocalXY.d.ts @@ -0,0 +1,9 @@ +export default WorldXYToGameObjectLocalXY; + +declare function WorldXYToGameObjectLocalXY( + gameObject: Phaser.GameObjects.GameObject, + worldX: number, + worldY: number, + camera?: Phaser.Cameras.Scene2D.Camera, + out?: { x: number, y: number } | true +): { x: number, y: number }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/position/WorldXYToGameObjectLocalXY.js b/ui/src/phaser3-rex-plugins/plugins/utils/position/WorldXYToGameObjectLocalXY.js new file mode 100644 index 000000000..31477c724 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/position/WorldXYToGameObjectLocalXY.js @@ -0,0 +1,41 @@ +const TransformMatrix = Phaser.GameObjects.Components.TransformMatrix; +const TransformXY = Phaser.Math.TransformXY; + +var WorldXYToGameObjectLocalXY = function (gameObject, worldX, worldY, camera, out) { + if (camera === undefined) { + camera = gameObject.scene.cameras.main; + } + + if (out === undefined) { + out = {} + } else if (out === true) { + out = globOut; + } + + var csx = camera.scrollX; + var csy = camera.scrollY; + var px = worldX + (csx * gameObject.scrollFactorX) - csx; + var py = worldY + (csy * gameObject.scrollFactorY) - csy; + if (gameObject.parentContainer) { + if (tempMatrix === undefined) { + tempMatrix = new TransformMatrix(); + parentMatrix = new TransformMatrix(); + } + + gameObject.getWorldTransformMatrix(tempMatrix, parentMatrix); + tempMatrix.applyInverse(px, py, out); + } + else { + TransformXY(px, py, gameObject.x, gameObject.y, gameObject.rotation, gameObject.scaleX, gameObject.scaleY, out); + } + + out.x += gameObject.displayOriginX; + out.y += gameObject.displayOriginY; + + return out; +} + +var tempMatrix, parentMatrix; +var globOut = {}; + +export default WorldXYToGameObjectLocalXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/progressbase/ProgressBase.js b/ui/src/phaser3-rex-plugins/plugins/utils/progressbase/ProgressBase.js new file mode 100644 index 000000000..4d8a3e365 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/progressbase/ProgressBase.js @@ -0,0 +1,52 @@ +import ProgressValueMethods from '../progressvalue/ProgressValueMethods.js'; +import EaseValueMethods from '../ease/EaseValueMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Clamp = Phaser.Math.Clamp; + +export default function (BaseClass) { + class ProgressBase extends BaseClass { + bootProgressBase(config) { + this.eventEmitter = GetValue(config, 'eventEmitter', this); + + var callback = GetValue(config, 'valuechangeCallback', null); + if (callback !== null) { + var scope = GetValue(config, 'valuechangeCallbackScope', undefined); + this.eventEmitter.on('valuechange', callback, scope); + } + + this + .setEaseValuePropName('value') + .setEaseValueDuration(GetValue(config, 'easeValue.duration', 0)) + .setEaseValueFunction(GetValue(config, 'easeValue.ease', 'Linear')); + + return this; + } + + get value() { + return this._value; + } + + set value(value) { + value = Clamp(value, 0, 1); + + var oldValue = this._value; + var valueChanged = (oldValue != value); + this.dirty = this.dirty || valueChanged; + this._value = value; + + if (valueChanged) { + this.eventEmitter.emit('valuechange', this._value, oldValue, this.eventEmitter); + } + } + } + + Object.assign( + ProgressBase.prototype, + ProgressValueMethods, + EaseValueMethods + ); + + return ProgressBase; +} + diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/progressvalue/ProgressValueMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/progressvalue/ProgressValueMethods.js new file mode 100644 index 000000000..3292b2204 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/progressvalue/ProgressValueMethods.js @@ -0,0 +1,32 @@ +const Linear = Phaser.Math.Linear; +const Percent = Phaser.Math.Percent; + +export default { + setValue(value, min, max) { + if ((value === undefined) || (value === null)) { + return this; + } + + if (min !== undefined) { + value = Percent(value, min, max); + } + this.value = value; + return this; + }, + + addValue(inc, min, max) { + if (min !== undefined) { + inc = Percent(inc, min, max); + } + this.value += inc; + return this; + }, + + getValue(min, max) { + var value = this.value; + if (min !== undefined) { + value = Linear(min, max, value); + } + return value; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/promise/Delay.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/promise/Delay.d.ts new file mode 100644 index 000000000..4d9fa5805 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/promise/Delay.d.ts @@ -0,0 +1,4 @@ +export default function ( + time: number, + result?: any +): Promise; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/promise/Delay.js b/ui/src/phaser3-rex-plugins/plugins/utils/promise/Delay.js new file mode 100644 index 000000000..5f584586b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/promise/Delay.js @@ -0,0 +1,12 @@ +var Delay = function (time, result) { + if (time === undefined) { + time = 0; + } + return new Promise(function (resolve, reject) { + setTimeout(function () { + resolve(result) + }, time); + }); +}; + +export default Delay; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/promise/DelaySceneTick.js b/ui/src/phaser3-rex-plugins/plugins/utils/promise/DelaySceneTick.js new file mode 100644 index 000000000..de327edbe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/promise/DelaySceneTick.js @@ -0,0 +1,10 @@ +var DelaySceneTick = function (scene, s, result) { + if (s === undefined) { + s = 0; + } + return new Promise(function (resolve, reject) { + scene.time.delayedCall(s, resolve, [result]); + }); +} + +export default DelaySceneTick; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/promise/WaitEvent.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/promise/WaitEvent.d.ts new file mode 100644 index 000000000..cc2a6c69e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/promise/WaitEvent.d.ts @@ -0,0 +1,10 @@ +// import * as Phaser from 'phaser'; + +export function WaitEvent( + eventEmitter: Phaser.Events.EventEmitter, + eventName: string +): Promise; + +export function WaitComplete( + eventEmitter: Phaser.Events.EventEmitter, +): Promise; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/promise/WaitEvent.js b/ui/src/phaser3-rex-plugins/plugins/utils/promise/WaitEvent.js new file mode 100644 index 000000000..25a1af19c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/promise/WaitEvent.js @@ -0,0 +1,13 @@ +var WaitEvent = function (eventEmitter, eventName) { + return new Promise(function (resolve, reject) { + eventEmitter.once(eventName, function () { + resolve(); + }); + }); +} + +var WaitComplete = function (eventEmitter) { + return WaitEvent(eventEmitter, 'complete'); +} + +export { WaitEvent, WaitComplete }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/proxy/createproxycontext/CreateProxyContext.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/createproxycontext/CreateProxyContext.d.ts new file mode 100644 index 000000000..842145d2d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/createproxycontext/CreateProxyContext.d.ts @@ -0,0 +1,13 @@ +export default CreateProxyContext; + +declare namespace CreateProxyContext { + interface IConfig { + has: (target: Object, key: string) => boolean; + get: (target: Object, key: string) => any; + } +} + +declare var CreateProxyContext: ( + config: CreateProxyContext.IConfig, + baseContext?: Object +) => typeof Proxy; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/proxy/createproxycontext/CreateProxyContext.js b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/createproxycontext/CreateProxyContext.js new file mode 100644 index 000000000..5fdac2422 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/createproxycontext/CreateProxyContext.js @@ -0,0 +1,18 @@ +var CreateProxyContext = function (config, baseContext) { + if (!config.hasOwnProperty('has')) { + throw 'Need has(target, key):boolean handler'; + } + if (!config.hasOwnProperty('get')) { + throw 'Need get(target, key):any handler'; + } + + if (baseContext === undefined) { + baseContext = {}; + } + return new Proxy(baseContext, { + has: config.has, + get: config.get + }); +} + +export default CreateProxyContext; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/AddDataMonitor.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/AddDataMonitor.d.ts new file mode 100644 index 000000000..74167ef27 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/AddDataMonitor.d.ts @@ -0,0 +1,5 @@ +export default function AddDataMonitor( + eventEmitter: Phaser.Events.EventEmitter, + data?: T, +): T; + diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/AddDataMonitor.js b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/AddDataMonitor.js new file mode 100644 index 000000000..88d275489 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/AddDataMonitor.js @@ -0,0 +1,10 @@ +import AddMonitor from './AddMonitor.js'; + +var AddDataMonitor = function (eventEmitter, data) { + if (data === undefined) { + data = {}; + } + return AddMonitor(eventEmitter, data, ''); +} + +export default AddDataMonitor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/AddMonitor.js b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/AddMonitor.js new file mode 100644 index 000000000..ed9e8c62e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/AddMonitor.js @@ -0,0 +1,71 @@ +import { EmitAddKeyEvents, EmitSetValueEvents, EmitDeleteKeyEvents } from './EmitEvents.js'; +import GetPropertyPath from './GetPropertyPath.js'; + +var AddMonitor = function (eventEmitter, data, parentPath) { + var monitor; + if (Array.isArray(data)) { + monitor = AddArrayMonitor(eventEmitter, data, parentPath); + } else { + monitor = AddDictionaryMonitor(eventEmitter, data, parentPath); + } + + for (var property in data) { + var child = data[property]; + if ((child !== null) && (typeof (child) === 'object')) { + var propertyPath = GetPropertyPath(parentPath, property); + monitor[property] = AddMonitor(eventEmitter, child, propertyPath); + } else { + EmitAddKeyEvents(eventEmitter, parentPath, property, child, undefined); + } + } + + return monitor; +} + +var AddDictionaryMonitor = function (eventEmitter, data, parentPath) { + return new Proxy(data, { + set(target, property, value) { + if (Reflect.has(target, property)) { + var prevValue = Reflect.get(target, property); + Reflect.set(target, property, value); + EmitSetValueEvents(eventEmitter, parentPath, property, value, prevValue); + } else { + Reflect.set(target, property, value); + EmitAddKeyEvents(eventEmitter, parentPath, property, value); + } + return true; + }, + + deleteProperty(target, property) { + if (Reflect.has(target, property)) { + Reflect.deleteProperty(target, property); + EmitDeleteKeyEvents(eventEmitter, parentPath, property); + } + return true; + } + }); +} + +var AddArrayMonitor = function (eventEmitter, data, parentPath) { + return new Proxy(data, { + set(target, property, value) { + if (property === 'length') { // Skip length property + return true; + } + + var prevValue = Reflect.get(target, property); + Reflect.set(target, property, value); + EmitSetValueEvents(eventEmitter, parentPath, property, value, prevValue); + return true; + }, + + deleteProperty(target, property) { + Reflect.deleteProperty(target, property); + target.splice(property, 1); + EmitDeleteKeyEvents(eventEmitter, parentPath, property); + return true; + } + }); +} + +export default AddMonitor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/EmitEvents.js b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/EmitEvents.js new file mode 100644 index 000000000..1e6dda4f8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/EmitEvents.js @@ -0,0 +1,29 @@ +import GetPropertyPath from './GetPropertyPath.js'; + +var EmitEvents = function (eventEmitter, op, parentPath, property, value, prevValue) { + var propertyPath = GetPropertyPath(parentPath, property); + eventEmitter.emit(`${op}-${propertyPath}`, value, prevValue); + + var parentPath = (parentPath === '') ? '*' : `${parentPath}.*` + eventEmitter.emit(`${op}-${parentPath}`, property, value, prevValue); + + eventEmitter.emit(`${op}`, propertyPath, value, prevValue); +} + +var EmitAddKeyEvents = function (eventEmitter, parentPath, property, value) { + EmitEvents(eventEmitter, 'add', parentPath, property, value, undefined); +} + +var EmitSetValueEvents = function (eventEmitter, parentPath, property, value, prevValue) { + EmitEvents(eventEmitter, 'set', parentPath, property, value, prevValue); +} + +var EmitDeleteKeyEvents = function (eventEmitter, parentPath, property) { + EmitEvents(eventEmitter, 'del', parentPath, property, undefined, undefined); +} + +export { + EmitAddKeyEvents, + EmitSetValueEvents, + EmitDeleteKeyEvents +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/GetPropertyPath.js b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/GetPropertyPath.js new file mode 100644 index 000000000..864c45211 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/GetPropertyPath.js @@ -0,0 +1,4 @@ +var GetPropertyPath = function (parentPath, property) { + return (parentPath === '') ? property : `${parentPath}.${property}`; +} +export default GetPropertyPath; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/AddPostFxPipelineInstance.js b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/AddPostFxPipelineInstance.js new file mode 100644 index 000000000..c055d316d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/AddPostFxPipelineInstance.js @@ -0,0 +1,17 @@ +var AddPostFxPipelineInstance = function (gameObject, PostFxPipelineClass, config) { + if (config === undefined) { + config = {}; + } + + gameObject.setPostPipeline(PostFxPipelineClass); + var pipeline = gameObject.postPipelines[gameObject.postPipelines.length - 1]; + pipeline.resetFromJSON(config); + + if (config.name) { + pipeline.name = config.name; + } + + return pipeline; +} + +export default AddPostFxPipelineInstance; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/BasePostFxPipelineBehavior.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/BasePostFxPipelineBehavior.d.ts new file mode 100644 index 000000000..d3fd49e2d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/BasePostFxPipelineBehavior.d.ts @@ -0,0 +1,22 @@ +export default BasePostFxPipelineBehavior; + +declare namespace BasePostFxPipelineBehavior { +} + +declare class BasePostFxPipelineBehavior { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: {} + ); + + constructor( + gameObject: Phaser.GameObjects.GameObject, + PostFxPipelineClass?: Phaser.Renderer.WebGL.Pipelines.PostFXPipeline + ); + + getPipeline( + config?: {} + ): Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; + + freePipeline(): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/BasePostFxPipelineBehavior.js b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/BasePostFxPipelineBehavior.js new file mode 100644 index 000000000..7920a5001 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/BasePostFxPipelineBehavior.js @@ -0,0 +1,76 @@ +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; +const RemoveIte = Phaser.Utils.Array.Remove; + +class PostFxPipelineBehaviorBase { + constructor(gameObject, config) { + this.gameObject = gameObject; + this.scene = gameObject.scene; + + // Can inject PipelineClass at runtime + var PipelineClass; + if (IsPostFxPipelineClass(config)) { + PipelineClass = config; + config = undefined; + } else { + PipelineClass = GetValue(config, 'PipelineClass'); + } + if (PipelineClass) { + this.createPipeline = function (game) { + return new PipelineClass(game); + } + } + + var enable = GetValue(config, 'enable', !!config) + + if (enable) { + this.getPipeline(config); + } + + // Will destroy pipeline when gameObject destroying + } + + getPipeline(config) { + if (!this.pipeline) { + var pipeline = this.createPipeline(this.scene.game); + var gameObject = this.gameObject; + var postPipelines = gameObject.postPipelines; + pipeline.gameObject = gameObject; + postPipelines.push(pipeline); + gameObject.hasPostPipeline = (postPipelines.length > 0); + + this.pipeline = pipeline; + } + if (config && this.pipeline.resetFromJSON) { + this.pipeline.resetFromJSON(config); + } + return this.pipeline; + } + + freePipeline() { + if (!this.pipeline) { + return this; + } + + var gameObject = this.gameObject; + var postPipelines = gameObject.postPipelines; + RemoveIte(postPipelines, this.pipeline); + gameObject.hasPostPipeline = (postPipelines.length > 0); + + this.pipeline.destroy(); + this.pipeline = undefined; + return this; + } + + // Override + createPipeline(game) { + + } +} + +var IsPostFxPipelineClass = function (object) { + return object && object.prototype && + (object.prototype instanceof PostFXPipeline); +} + +export default PostFxPipelineBehaviorBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js new file mode 100644 index 000000000..ea289a7c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js @@ -0,0 +1,34 @@ +import RegisterPostPipeline from './RegisterPostPipeline.js'; +import AddPostFxPipelineInstance from './AddPostFxPipelineInstance.js'; +import RemovePostFxPipelineInstance from './RemovePostFxPipelineInstance.js'; +import GetPostFxPipelineInstance from './GetPostFxPipelineInstance.js' + +class BasePostFxPipelinePlugin extends Phaser.Plugins.BasePlugin { + setPostPipelineClass(PostFxPipelineClass, postFxPipelineName) { + this.PostFxPipelineClass = PostFxPipelineClass; + this.postFxPipelineName = postFxPipelineName; + return this; + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.once('destroy', this.destroy, this); + + RegisterPostPipeline(this.game, this.postFxPipelineName, this.PostFxPipelineClass); + } + + add(gameObject, config) { + return AddPostFxPipelineInstance(gameObject, this.PostFxPipelineClass, config); + } + + remove(gameObject, name) { + RemovePostFxPipelineInstance(gameObject, this.PostFxPipelineClass, name); + return this; + } + + get(gameObject, name) { + return GetPostFxPipelineInstance(gameObject, this.PostFxPipelineClass, name); + } +} + +export default BasePostFxPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/GetPostFxPipelineInstance.js b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/GetPostFxPipelineInstance.js new file mode 100644 index 000000000..ab83bd030 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/GetPostFxPipelineInstance.js @@ -0,0 +1,23 @@ +var GetPostFxPipelineInstance = function (gameObject, PostFxPipelineClass, name) { + if (name === undefined) { + var result = []; + var pipelines = gameObject.postPipelines; + for (var i = 0, cnt = pipelines.length; i < cnt; i++) { + var instance = pipelines[i]; + if (instance instanceof PostFxPipelineClass) { + result.push(instance) + } + } + return result; + } else { + var pipelines = gameObject.postPipelines; + for (var i = 0, cnt = pipelines.length; i < cnt; i++) { + var instance = pipelines[i]; + if ((instance instanceof PostFxPipelineClass) && (instance.name === name)) { + return instance; + } + } + } +} + +export default GetPostFxPipelineInstance; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/RegisterPostPipeline.js b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/RegisterPostPipeline.js new file mode 100644 index 000000000..8b29202d9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/RegisterPostPipeline.js @@ -0,0 +1,7 @@ +import GetGame from '../../system/GetGame.js'; + +var RegisterPostPipeline = function (game, postFxPipelineName, PostFxPipelineClass) { + GetGame(game).renderer.pipelines.addPostPipeline(postFxPipelineName, PostFxPipelineClass); +} + +export default RegisterPostPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/RemovePostFxPipelineInstance.js b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/RemovePostFxPipelineInstance.js new file mode 100644 index 000000000..7209aca5b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/RemovePostFxPipelineInstance.js @@ -0,0 +1,25 @@ +const SpliceOne = Phaser.Utils.Array.SpliceOne; + +var RemovePostFxPipelineInstance = function(gameObject, PostFxPipelineClass, name) { + if (name === undefined) { + var pipelines = gameObject.postPipelines; + for (var i = (pipelines.length - 1); i >= 0; i--) { + var instance = pipelines[i]; + if (instance instanceof PostFxPipelineClass) { + instance.destroy(); + SpliceOne(pipelines, i); + } + } + } else { + var pipelines = gameObject.postPipelines; + for (var i = 0, cnt = pipelines.length; i < cnt; i++) { + var instance = pipelines[i]; + if ((instance instanceof PostFxPipelineClass) && (instance.name === name)) { + instance.destroy(); + SpliceOne(pipelines, i); + } + } + } +} + +export default RemovePostFxPipelineInstance; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/CreateDynamicTexture.js b/ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/CreateDynamicTexture.js new file mode 100644 index 000000000..f71fdcef9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/CreateDynamicTexture.js @@ -0,0 +1,14 @@ +const DynamicTexture = Phaser.Textures.DynamicTexture; + +var CreateDynamicTexture = function (scene, width, height) { + if (width === undefined) { + width = 2; + } + if (height === undefined) { + height = 2; + } + var dt = new DynamicTexture(scene.sys.textures, null, width, height); + return dt; +} + +export default CreateDynamicTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/FitToViewport.js b/ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/FitToViewport.js new file mode 100644 index 000000000..b8f726911 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/FitToViewport.js @@ -0,0 +1,28 @@ +import GetViewport from '../system/GetViewport.js'; + +var FitToViewport = function (renderTexture, camera) { + if (camera === undefined) { + camera = renderTexture.scene.cameras.main; + } + + renderTexture.setOrigin(0); + + var viewport = GetViewport(renderTexture.scene, camera); + var x = viewport.x, + y = viewport.y, + w = viewport.width, + h = viewport.height; + + if ((w !== renderTexture.width) || (h !== renderTexture.height)) { + renderTexture.setSize(w, h); + } else { + renderTexture.clear(); + } + + renderTexture.setPosition(x, y); + renderTexture.camera.setScroll(x, y); + + return renderTexture; +} + +export default FitToViewport \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/Snapshot.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/Snapshot.d.ts new file mode 100644 index 000000000..0a99c0701 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/Snapshot.d.ts @@ -0,0 +1,19 @@ +export default Snapshot; + +declare namespace Snapshot { + interface IConfig { + gameObjects: Phaser.GameObjects.GameObject[], + renderTexture?: Phaser.GameObjects.RenderTexture, + + x?: number, y?: number, + width?: number, height?: number, + padding?: number, + originX?: number, originY?: number, + + saveTexture?: string, + } +} + +declare function Snapshot( + config: Snapshot.IConfig +): Phaser.GameObjects.RenderTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/Snapshot.js b/ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/Snapshot.js new file mode 100644 index 000000000..f48746c3e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/Snapshot.js @@ -0,0 +1,108 @@ +import GetBoundsOfGameObjects from '../bounds/GetBoundsOfGameObjects.js'; +import Clone from '../object/Clone.js'; +import SortGameObjectsByDepth from '../system/SortGameObjectsByDepth.js'; +import IsGameObject from '../system/IsGameObject.js'; + +var GetValue = Phaser.Utils.Objects.GetValue; +var DynamicTexture = Phaser.Textures.DynamicTexture; +var UUID = Phaser.Utils.String.UUID; + +var Snapshot = function (config) { + if (!config) { + return; + } + + var gameObjects = config.gameObjects; + var renderTexture = config.renderTexture; // renderTexture, or dynamicTexture + var x = GetValue(config, 'x', undefined); + var y = GetValue(config, 'y', undefined); + var width = GetValue(config, 'width', undefined); + var height = GetValue(config, 'height', undefined); + var originX = GetValue(config, 'originX', 0); + var originY = GetValue(config, 'originY', 0); + var padding = GetValue(config, 'padding', 0); + var scrollX, scrollY; + if ((width === undefined) || (height === undefined) || (x === undefined) || (y === undefined)) { + // Union bounds of gameObjects + var bounds = GetBoundsOfGameObjects(gameObjects, true); + var isCenterOrigin = (x !== undefined) && (y !== undefined); + if (isCenterOrigin) { + width = Math.max((x - bounds.left), (bounds.right - x)) * 2; + height = Math.max((y - bounds.top), (bounds.bottom - y)) * 2; + originX = 0.5; + originY = 0.5; + } else { + x = bounds.x; + y = bounds.y; + width = bounds.width; + height = bounds.height; + originX = 0; + originY = 0; + } + scrollX = bounds.x; + scrollY = bounds.y; + } else { + scrollX = x + ((0 - originX) * width); + scrollY = y + ((0 - originY) * height); + } + + scrollX -= padding; + scrollY -= padding; + width += (padding * 2); + height += (padding * 2); + + var scene = gameObjects[0].scene; + + // Snapshot on dynamicTexture directly + if (saveTexture && !renderTexture) { + renderTexture = new DynamicTexture(scene.sys.textures, UUID(), width, height); + } + + // Return a renderTexture + if (!renderTexture) { + renderTexture = scene.add.renderTexture(0, 0, width, height); + } + + if (renderTexture.setPosition) { + renderTexture.setPosition(x, y); + } + + if ((renderTexture.width !== width) || (renderTexture.height !== height)) { + renderTexture.setSize(width, height); + } + + if (renderTexture.setOrigin) { + renderTexture.setOrigin(originX, originY); + } + + renderTexture.camera.setScroll(scrollX, scrollY); + + // Draw gameObjects + gameObjects = SortGameObjectsByDepth(Clone(gameObjects)); + renderTexture.draw(gameObjects); + + // Save render result to texture + var saveTexture = config.saveTexture; + if (saveTexture) { + if (IsGameObject(renderTexture)) { + renderTexture.saveTexture(saveTexture); + } else { + var dynamicTexture = renderTexture; + var textureManager = dynamicTexture.manager; + if (textureManager.exists(dynamicTexture.key)) { + // Rename texture + textureManager.renameTexture(dynamicTexture.key, key); + } else { + // Add texture to texture manager + dynamicTexture.key = key; + textureManager.list[key] = dynamicTexture; + textureManager.emit('addtexture', key, dynamicTexture); + textureManager.emit(`addtexture-${key}`, dynamicTexture); + } + } + } + + return renderTexture; +} + +export default Snapshot; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/size/FitTo.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/size/FitTo.d.ts new file mode 100644 index 000000000..e5f2e380f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/size/FitTo.d.ts @@ -0,0 +1,11 @@ +export default FitTo; + +declare namespace FitTo { + type SizeType = { width: number, height: number }; +} + +declare function FitTo( + child: FitTo.SizeType, + parent: FitTo.SizeType, + out?: FitTo.SizeType | true +): FitTo.SizeType; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/size/FitTo.js b/ui/src/phaser3-rex-plugins/plugins/utils/size/FitTo.js new file mode 100644 index 000000000..c95e0a117 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/size/FitTo.js @@ -0,0 +1,30 @@ +var FitTo = function (child, parent, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globalSize; + } + + if ((child.width <= parent.width) && (child.height <= parent.height)) { + out.width = child.width; + out.height = child.height; + return out; + } + + var childRatio = child.width / child.height; + out.width = Math.min(child.width, parent.width); + out.height = Math.min(child.height, parent.height); + var ratio = out.width / out.height; + + if (ratio < childRatio) { + out.height = out.width / childRatio; + } else if (ratio > childRatio) { + out.width = out.height * childRatio; + } + + return out; +} + +var globalSize = {}; + +export default FitTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/size/GetDisplaySize.js b/ui/src/phaser3-rex-plugins/plugins/utils/size/GetDisplaySize.js new file mode 100644 index 000000000..caed230d1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/size/GetDisplaySize.js @@ -0,0 +1,20 @@ +var GetDisplayWidth = function (gameObject) { + if (gameObject.displayWidth !== undefined) { + return gameObject.displayWidth; + } else { + return gameObject.width; + } +} + +var GetDisplayHeight = function (gameObject) { + if (gameObject.displayHeight !== undefined) { + return gameObject.displayHeight; + } else { + return gameObject.height; + } +} + +export { + GetDisplayWidth, + GetDisplayHeight +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/size/ResizeGameObject.js b/ui/src/phaser3-rex-plugins/plugins/utils/size/ResizeGameObject.js new file mode 100644 index 000000000..52a428787 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/size/ResizeGameObject.js @@ -0,0 +1,28 @@ +var ResizeGameObject = function (gameObject, newWidth, newHeight) { + if (!gameObject || ((newWidth === undefined) && (newHeight === undefined))) { + return; + } + if (gameObject.resize || gameObject.setSize) { // Has `resize`, or `setSize` method + if (newWidth === undefined) { + newWidth = gameObject.width; + } + if (newHeight === undefined) { + newHeight = gameObject.height; + } + + if (gameObject.resize) { + gameObject.resize(newWidth, newHeight); + } else { + gameObject.setSize(newWidth, newHeight); + } + } else { // Set display width/height + if (newWidth !== undefined) { + gameObject.displayWidth = newWidth; + } + if (newHeight !== undefined) { + gameObject.displayHeight = newHeight; + } + } +} + +export default ResizeGameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/size/SetDisplaySize.js b/ui/src/phaser3-rex-plugins/plugins/utils/size/SetDisplaySize.js new file mode 100644 index 000000000..945bacb51 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/size/SetDisplaySize.js @@ -0,0 +1,32 @@ +var SetDisplaySize = function (gameObject, width, height) { + if (!gameObject) { + return; + } + + var unknownWidth = (width == null); + var unknownHeight = (height == null); + + if (unknownWidth && unknownHeight) { + return gameObject; + } + + if (!unknownWidth) { + gameObject.displayWidth = width; + } + + if (!unknownHeight) { + gameObject.displayHeight = height; + } + + if (unknownWidth) { + gameObject.scaleX = gameObject.scaleY; + } + + if (unknownHeight) { + gameObject.scaleY = gameObject.scaleX; + } + + return gameObject; +} + +export default SetDisplaySize; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/speedmonitor/SpeedMonitor.js b/ui/src/phaser3-rex-plugins/plugins/utils/speedmonitor/SpeedMonitor.js new file mode 100644 index 000000000..0a734363c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/speedmonitor/SpeedMonitor.js @@ -0,0 +1,31 @@ +const Vector2 = Phaser.Math.Vector2; +class SpeedMonitor { + constructor() { + this.position = new Vector2(); + this.velocity = new Vector2(); + } + + init(x, y) { + this.velocity.reset(); + this.position.set(x, y); + return this; + } + + update(x, y, delta) { + // delta in sec + this.velocity.set( + x - this.position.x, + y - this.position.y + ); + if ((this.velocity.x !== 0) || (this.velocity.y !== 0)) { + this.velocity.setToPolar( + this.velocity.angle(), + this.velocity.length() / delta + ); + } + this.position.set(x, y); + return this; + } +}; + +export default SpeedMonitor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/SpriteBob.js b/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/SpriteBob.js new file mode 100644 index 000000000..4c50e2f02 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/SpriteBob.js @@ -0,0 +1,36 @@ +import BobBase from '../../gameobject/gomanager/bobbase/BobBase.js'; + +class SpriteBob extends BobBase { + playAnimation(key) { + this.gameObject.anims.timeScale = this.timeScale; + this.gameObject.play(key); + return this; + } + + stopAnimation() { + this.gameObject.stop(); + return this; + } + + chainAnimation(keys) { + this.gameObject.chain(keys); + return this; + } + + pauseAnimation() { + this.gameObject.anims.pause(); + return this; + } + + setTimeScale(timeScale) { + super.setTimeScale(timeScale); + + if (this.gameObject.anims) { + this.gameObject.anims.timeScale = timeScale; + } + + return this; + } +} + +export default SpriteBob; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/SpriteManager.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/SpriteManager.d.ts new file mode 100644 index 000000000..3d415daca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/SpriteManager.d.ts @@ -0,0 +1,53 @@ +import GOManager from '../../gameobject/gomanager/GOManager'; + +export default SpriteManager; + +declare namespace SpriteManager { + + type CreateCallbackType = ( + scene: Phaser.Scene, + textureKey: string, + frameName: string | number + ) => Phaser.GameObjects.GameObject; + + interface IConfig extends GOManager.IConfig { + createCallback?: 'sprite' | 'image' | CreateCallbackType, + } +} + +declare class SpriteManager extends GOManager { + constructor( + scene: Phaser.Scene, + config?: SpriteManager.IConfig + ) + + add( + name: string, + textureKey: string, + frameName?: string | number + ): this; + + setCreateGameObjectCallback( + callback?: 'sprite' | 'image' | SpriteManager.CreateCallbackType + ): this; + + playAnimation( + name: string, + key: string, + ): this; + + stopAnimation(name: string): this; + + chainAnimation( + name: string, + keys: string | string[] | Phaser.Types.Animations.PlayAnimationConfig | Phaser.Types.Animations.PlayAnimationConfig[] + ): this; + + pauseAnimation(name: string): this; + + setTexture( + name: string, + textureKey: string, + frameName: string | number + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/SpriteManager.js b/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/SpriteManager.js new file mode 100644 index 000000000..a2ebb0ceb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/SpriteManager.js @@ -0,0 +1,47 @@ +import GOManager from '../../gameobject/gomanager/GOManager.js'; +import SpriteBob from './SpriteBob.js'; +import Methods from './methods/Methods.js'; + +class SpriteManager extends GOManager { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + config.BobClass = SpriteBob; + + super(scene, config); + } + + setCreateGameObjectCallback(callback, scope) { + if (!callback || (callback === 'sprite')) { + callback = CreateSprite; + } else if (callback === 'image') { + callback = CreateImage; + } + super.setCreateGameObjectCallback(callback, scope); + return this; + } + +} + +var CreateSprite = function (scene, textureKey, frameName) { + if ((typeof (frameName) !== 'string') && (typeof (frameName) !== 'number')) { + frameName = undefined; + } + return scene.add.sprite(0, 0, textureKey, frameName); +} + +var CreateImage = function (scene, textureKey, frameName) { + if ((typeof (frameName) !== 'string') && (typeof (frameName) !== 'number')) { + frameName = undefined; + } + return scene.add.image(0, 0, textureKey, frameName); +} + +Object.assign( + SpriteManager.prototype, + Methods +); + +export default SpriteManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/methods/AnimationMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/methods/AnimationMethods.js new file mode 100644 index 000000000..2f74882d4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/methods/AnimationMethods.js @@ -0,0 +1,37 @@ +export default { + playAnimation(name, key) { + if (!this.has(name)) { + this.add(name); + } + + this.get(name).playAnimation(key); + return this; + }, + + stopAnimation(name) { + if (!this.has(name)) { + return this; + } + + this.get(name).stopAnimation(); + return this; + }, + + chainAnimation(name, keys) { + if (!this.has(name)) { + return this; + } + + this.get(name).chainAnimation(keys); + return this; + }, + + pauseAnimation(name) { + if (!this.has(name)) { + return this; + } + + this.get(name).pauseAnimation(); + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/methods/Methods.js new file mode 100644 index 000000000..d515eceb5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/methods/Methods.js @@ -0,0 +1,9 @@ +import AnimationMethods from './AnimationMethods.js'; + +var Methods = {} +Object.assign( + Methods, + AnimationMethods +) + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/string/EscapeRegex.js b/ui/src/phaser3-rex-plugins/plugins/utils/string/EscapeRegex.js new file mode 100644 index 000000000..b889cee06 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/string/EscapeRegex.js @@ -0,0 +1,12 @@ +// https://github.com/sindresorhus/escape-string-regexp/blob/master/index.js + +var EscapeRegex = function (s) { + return s + .replace(re0, '\\$&') + .replace(re1, '\\x2d'); +} + +var re0 = /[|\\{}()[\]^$+*?.]/g; +var re1 = /-/g; + +export default EscapeRegex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/string/GetRandomWord.js b/ui/src/phaser3-rex-plugins/plugins/utils/string/GetRandomWord.js new file mode 100644 index 000000000..38f437bf5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/string/GetRandomWord.js @@ -0,0 +1,17 @@ +import RandomInt from '../math/Between.js'; +import RandomItem from '../array/GetRandom.js'; + +const CANDIDATES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +var GetRandomWord = function (min, max, candidates) { + if (candidates === undefined) { + candidates = CANDIDATES; + } + var count = (max === undefined) ? min : RandomInt(min, max); + var word = ''; + for (var j = 0; j < count; j++) { + word += RandomItem(candidates); + } + return word; +} + +export default GetRandomWord; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/string/GetTimeStamp.js b/ui/src/phaser3-rex-plugins/plugins/utils/string/GetTimeStamp.js new file mode 100644 index 000000000..b0e1ad675 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/string/GetTimeStamp.js @@ -0,0 +1,5 @@ +var GetTimeStamp = function () { + return (new Date()).getTime().toString(); +} + +export default GetTimeStamp; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/string/NumberToColorString.js b/ui/src/phaser3-rex-plugins/plugins/utils/string/NumberToColorString.js new file mode 100644 index 000000000..33ebace55 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/string/NumberToColorString.js @@ -0,0 +1,8 @@ +var NumberToColorString = function (value) { + if (typeof (value) === 'number') { + value = `#${value.toString(16)}`; + } + return value; +} + +export default NumberToColorString; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/string/TypeConvert.js b/ui/src/phaser3-rex-plugins/plugins/utils/string/TypeConvert.js new file mode 100644 index 000000000..ac33bf4e0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/string/TypeConvert.js @@ -0,0 +1,28 @@ +var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i; +var HEX = /^0x[0-9A-F]+$/i; + +var TypeConvert = function (s) { + if (typeof (s) !== 'string') { + return s; + } + + if (s === '') { + s = null; + + } else if (FLOAT.test(s)) { + s = parseFloat(s); + + } else if (HEX.test(s)) { + s = parseInt(s, 16); + + } else { + if (s === 'false') { + s = false; + } else if (s === 'true') { + s = true; + } + } + + return s; +}; +export default TypeConvert; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/string/UUID.js b/ui/src/phaser3-rex-plugins/plugins/utils/string/UUID.js new file mode 100644 index 000000000..4cf9d30e4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/string/UUID.js @@ -0,0 +1,33 @@ +/** + * @author Richard Davey + * @copyright 2020 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Creates and returns an RFC4122 version 4 compliant UUID. + * + * The string is in the form: `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx` where each `x` is replaced with a random + * hexadecimal digit from 0 to f, and `y` is replaced with a random hexadecimal digit from 8 to b. + * + * @function Phaser.Utils.String.UUID + * @since 3.12.0 + * + * @return {string} The UUID string. + */ +const HasBuiltRandomUUID = (window.crypto && window.crypto.randomUUID); + +var UUID = function () { + if (HasBuiltRandomUUID) { + return window.crypto.randomUUID(); + } + + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0; + var v = (c === 'x') ? r : (r & 0x3 | 0x8); + + return v.toString(16); + }); +}; + +export default UUID; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/struct/Stack.js b/ui/src/phaser3-rex-plugins/plugins/utils/struct/Stack.js new file mode 100644 index 000000000..fe9b36d48 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/struct/Stack.js @@ -0,0 +1,32 @@ +class Stack { + constructor() { + this.items = []; + } + + destroy() { + this.clear(); + this.items = undefined; + } + + pop() { + return (this.items.length > 0) ? this.items.pop() : null; + } + + push(l) { + this.items.push(l); + return this; + } + + pushMultiple(arr) { + this.items.push.apply(this.items, arr); + arr.length = 0; + return this; + } + + clear() { + this.items.length = 0; + return this; + } +} + +export default Stack; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/struct/Tree.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/struct/Tree.d.ts new file mode 100644 index 000000000..6662f8d0b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/struct/Tree.d.ts @@ -0,0 +1,30 @@ +export default Tree; + +declare namespace Tree { + type DataType = { [name: string]: any }; +} +declare class Tree { + constructor(data?: Tree.DataType); + + getFullPath(keys?: string): string; + getFullPath(keys?: string[]): string[]; + + setRefPath(keys?: string): this; + + setValue(keys: string, value: any): this; + setValue(data: Tree.DataType): this; + setValue(): this; + + getValue(keys?: string | string[]): any; + + cloneValue(keys?: string | string[]): Tree.DataType; + + removeKey(keys?: string | string[]): this; + + hasKey(keys?: string | string[]): boolean; + + clear(): this; + + clone(cloneData?: boolean): Tree; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/struct/Tree.js b/ui/src/phaser3-rex-plugins/plugins/utils/struct/Tree.js new file mode 100644 index 000000000..1da81dcce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/struct/Tree.js @@ -0,0 +1,140 @@ +import SetValue from '../object/SetValue.js'; +import Clear from '../object/Clear.js'; +import DeepClone from '../object/DeepClone.js'; + +class Tree { + constructor(data) { + if (data === undefined) { + data = {}; + } + this.data = data; + this.refPath = ''; + } + + getFullPath(keys) { + if (typeof (keys) === 'string') { + if (keys === '.') { + keys = this.refPath; + } else if (keys.startsWith('..')) { + if (this.refPath !== '') { + var refPathKeys = this.refPath.split('.'); + refPathKeys.pop() + keys = `${refPathKeys.join('.')}${keys.substring(1)}`; + } else { // this.refPath === '' + keys = keys.substring(2); + } + + } else if (keys.startsWith('.')) { + if (this.refPath !== '') { + keys = `${this.refPath}${keys}`; + } else { // this.refPath === '' + keys = keys.substring(1); + } + } + } + + return keys; + } + + setRefPath(keys) { + if (keys === undefined) { + keys = ''; + } + this.refPath = this.getFullPath(keys); + return this; + } + + setValue(keys, value) { + if (keys === undefined) { + this.clear(); // No argument + } else if (value === undefined) { + this.data = keys; // JSON keys + } else { + SetValue(this.data, this.getFullPath(keys), value); + } + return this; + } + + getValue(keys) { + if (keys === undefined) { + return this.data; + } else { + if (typeof (keys) === 'string') { + keys = this.getFullPath(keys).split('.'); + } + + return GetEntry(this.data, keys); + } + } + + cloneValue(keys) { + return DeepClone(this.getValue(keys)); + } + + removeKey(keys) { + if (keys === undefined) { + this.clear(); + } else { + if (typeof (keys) === 'string') { + keys = this.getFullPath(keys).split('.'); + } + + var lastKey = keys.pop(); + var entry = GetEntry(this.data, keys); + + if (IsObject(entry)) { + delete entry[lastKey]; + } + } + + return this; + } + + hasKey(keys) { + if (typeof (keys) === 'string') { + keys = this.getFullPath(keys).split('.'); + } + + var lastKey = keys.pop(); + var entry = GetEntry(this.data, keys); + if (!IsObject(entry)) { + return false; + } + + return entry.hasOwnProperty(lastKey) + } + + clear() { + Clear(this.data); + return this; + } + + clone(cloneData) { + var data = (cloneData) ? this.cloneValue() : this.data; + var tree = new Tree(data); + tree.setRefPath(this.refPath); + return tree; + } +} + +var IsObject = function (obj) { + return (obj != null) && (typeof (obj) === 'object') +} + +var GetEntry = function (data, keys) { + if (keys[0] === '') { + return data; + } + + var entry = data; + for (var i = 0, cnt = keys.length; i < cnt; i++) { + if (!IsObject(entry)) { + return undefined; + } + entry = entry[keys[i]]; + } + return entry; +} + + +export default Tree; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/GetCache.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetCache.js new file mode 100644 index 000000000..aaa3e525d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetCache.js @@ -0,0 +1,40 @@ +import GetGame from './GetGame.js'; + +var GetCache = function (game, loaderType, cacheType) { + if (cacheType === undefined) { + switch (loaderType) { + case 'image': + case 'svg': + cacheType = 'textures'; + break; + + case 'animation': + cacheType = 'json'; + break; + + case 'tilemapTiledJSON': + case 'tilemapCSV': + cacheType = 'tilemap'; + break; + + case 'glsl': + cacheType = 'shader'; + break; + + default: + cacheType = loaderType; + break; + } + } + + game = GetGame(game); + var cache; + if (cacheType === 'textures') { + cache = game.textures; + } else { + cache = game.cache[cacheType]; + } + return cache; +} + +export default GetCache; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/GetEventEmitter.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetEventEmitter.js new file mode 100644 index 000000000..05f855287 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetEventEmitter.js @@ -0,0 +1,11 @@ +import IsSceneObject from './IsSceneObject.js'; + +var GetEventEmitter = function (object) { + if (IsSceneObject(object)) { + return object.events; + } else if (object.on) { + return object; + } +} + +export default GetEventEmitter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/GetGLTexture.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetGLTexture.js new file mode 100644 index 000000000..9482f1efc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetGLTexture.js @@ -0,0 +1,9 @@ +var GetGLtexture = function (gameObject) { + if (gameObject.glTexture) { + return gameObject.glTexture; + } else if (gameObject.frame) { + return gameObject.frame.glTexture; + } +} + +export default GetGLtexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/GetGame.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetGame.js new file mode 100644 index 000000000..dc105758b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetGame.js @@ -0,0 +1,18 @@ +import IsGame from './IsGame.js'; +import IsSceneObject from './IsSceneObject.js'; + +var GetGame = function (object) { + if ((object == null) || (typeof (object) !== 'object')) { + return null; + } else if (IsGame(object)) { + return object; + } else if (IsGame(object.game)) { + return object.game; + } else if (IsSceneObject(object)) { // object = scene object + return object.sys.game; + } else if (IsSceneObject(object.scene)) { // object = game object + return object.scene.sys.game; + } +} + +export default GetGame; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/GetGameObjectByName.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetGameObjectByName.js new file mode 100644 index 000000000..fe2781c3d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetGameObjectByName.js @@ -0,0 +1,38 @@ +import IsArray from '../object/IsArray.js'; + +var GetGameObjectByName = function (children, name) { + if (!children) { + return null; + + } else if (IsArray(children)) { + var child; + for (var i = 0, cnt = children.length; i < cnt; i++) { + child = TestName(children[i], name); + if (child) { + return child; + } + } + + } else { // Is plain object + var child; + for (var key in children) { + child = TestName(children[key], name); + if (child) { + return child; + } + } + + } +} + +var TestName = function (gameObject, name) { + if (!gameObject) { + return null; + } else if (gameObject.hasOwnProperty('name')) { + return (gameObject.name === name) ? gameObject : null; + } else { // Array, or plain object + return GetElementByName(gameObject, name); + } +} + +export default GetGameObjectByName; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/GetSceneObject.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetSceneObject.js new file mode 100644 index 000000000..9e7a89c96 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetSceneObject.js @@ -0,0 +1,17 @@ +import IsSceneObject from './IsSceneObject.js'; + +var GetSceneObject = function (object) { + if ((object == null) || (typeof (object) !== 'object')) { + return null; + } else if (IsSceneObject(object)) { // object = scene + return object; + } else if (object.scene && IsSceneObject(object.scene)) { // object = game object + return object.scene; + } else if (object.parent && object.parent.scene && IsSceneObject(object.parent.scene)) { // parent = bob object + return object.parent.scene; + } else { + return null; + } +} + +export default GetSceneObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/GetSoundManager.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetSoundManager.js new file mode 100644 index 000000000..02297e278 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetSoundManager.js @@ -0,0 +1,10 @@ +import IsSceneObject from './IsSceneObject.js'; + +var GetSoundManager = function (game) { + if (IsSceneObject(game)) { + return game.sys.sound; + } + return game.sound; +} + +export default GetSoundManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/GetTickDelta.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetTickDelta.js new file mode 100644 index 000000000..68554dab6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetTickDelta.js @@ -0,0 +1,7 @@ +import GetGame from './GetGame.js'; + +var GetTickDelta = function (game) { + return GetGame(game).loop.delta; +} + +export default GetTickDelta; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/GetViewport.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetViewport.d.ts new file mode 100644 index 000000000..bca33ed7b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetViewport.d.ts @@ -0,0 +1,12 @@ +// import * as Phaser from 'phaser'; + +export default function GetViewport( + scene: Phaser.Scene, + camera: Phaser.Cameras.Scene2D.BaseCamera, + out?: Phaser.Geom.Rectangle | true +): Phaser.Geom.Rectangle; + +export default function GetViewport( + scene: Phaser.Scene, + out?: Phaser.Geom.Rectangle | true +): Phaser.Geom.Rectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/GetViewport.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetViewport.js new file mode 100644 index 000000000..86777f22f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/GetViewport.js @@ -0,0 +1,25 @@ +import IsCameraObject from './IsCameraObject.js'; +const Rectangle = Phaser.Geom.Rectangle; + +var GetViewport = function (scene, camera, out) { + if (!IsCameraObject(camera)) { + out = camera; + camera = undefined; + } + + if (out === undefined) { + out = new Rectangle(); + } else if (out === true) { + out = globRect; + } + + if (camera) { + return scene.scale.getViewPort(camera, out); + } else { + return scene.scale.getViewPort(out); + } +} + +var globRect = new Rectangle(); + +export default GetViewport; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/IsCameraObject.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/IsCameraObject.js new file mode 100644 index 000000000..aa08779f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/IsCameraObject.js @@ -0,0 +1,7 @@ +const CameraClass = Phaser.Cameras.Scene2D.BaseCamera; + +var IsCameraObject = function (object) { + return (object instanceof CameraClass); +} + +export default IsCameraObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/IsEventEmitter.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/IsEventEmitter.js new file mode 100644 index 000000000..d50f26a79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/IsEventEmitter.js @@ -0,0 +1,8 @@ +var IsEventEmitter = function (obj) { + if (obj && typeof obj === 'object') { + return !!obj.on; + } + return false; +} + +export default IsEventEmitter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/IsGame.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/IsGame.js new file mode 100644 index 000000000..47f050258 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/IsGame.js @@ -0,0 +1,5 @@ +const GameClass = Phaser.Game; +var IsGame = function (object) { + return (object instanceof GameClass); +} +export default IsGame; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/IsGameObject.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/IsGameObject.js new file mode 100644 index 000000000..76a90a9a1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/IsGameObject.js @@ -0,0 +1,5 @@ +const GameObjectClass = Phaser.GameObjects.GameObject; +var IsGameObject = function (object) { + return (object instanceof GameObjectClass); +} +export default IsGameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/IsSceneObject.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/IsSceneObject.js new file mode 100644 index 000000000..9732202cb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/IsSceneObject.js @@ -0,0 +1,5 @@ +const SceneClass = Phaser.Scene; +var IsSceneObject = function (object) { + return (object instanceof SceneClass); +} +export default IsSceneObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/IsSoundObject.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/IsSoundObject.js new file mode 100644 index 000000000..26fddb53c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/IsSoundObject.js @@ -0,0 +1,6 @@ +const SoundObjectClass = Phaser.Sound.BaseSound; +var IsSoundObject = function (object) { + return (object instanceof SoundObjectClass); +} + +export default IsSoundObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/LogMaxDelta.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/LogMaxDelta.js new file mode 100644 index 000000000..573b09511 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/LogMaxDelta.js @@ -0,0 +1,16 @@ +var prevTime, maxDelta; +var LogMaxDelta = function (time) { + if (prevTime === undefined) { + prevTime = time; + maxDelta = 0; + } else { + var dt = time - prevTime; + prevTime = time; + if (maxDelta < dt) { + maxDelta = dt; + console.log(dt); + } + } +} + +export default LogMaxDelta; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/MaxDelta.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/MaxDelta.js new file mode 100644 index 000000000..cc7eb701d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/MaxDelta.js @@ -0,0 +1,57 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +class MaxDelta { + constructor(config) { + this.logCallback = GetValue(config, 'logCallback', DefaultCallback); + this.logCallbackScope = GetValue(config, 'logCallbackScope', undefined); + this.clear(); + this.setEnable(GetValue(config, 'enable', true)); + } + + clear() { + this.prevTime = undefined; + this.maxDelta = undefined; + return this; + } + + setEnable(enabled) { + if (enabled === undefined) { + enabled = true; + } + this.enable = enabled; + return this; + } + + log(time) { + if (!this.enable) { + return this; + } + + if (this.prevTime === undefined) { + this.prevTime = time; + this.maxDelta = 0; + } else { + var dt = time - this.prevTime; + this.prevTime = time; + if (this.maxDelta < dt) { + this.maxDelta = dt; + + if (this.logCallback) { + if (this.logCallbackScope) { + this.logCallback.call(this.logCallbackScope, dt); + } else { + this.logCallback(dt); + } + } + } + } + + return this; + } +} + +var DefaultCallback = function (dt) { + console.log(dt); +} + +export default MaxDelta; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/OS.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/OS.js new file mode 100644 index 000000000..3ea244eef --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/OS.js @@ -0,0 +1,165 @@ +/** + * @author Richard Davey + * @copyright 2020 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Determines the operating system of the device running this Phaser Game instance. + * These values are read-only and populated during the boot sequence of the game. + * They are then referenced by internal game systems and are available for you to access + * via `this.sys.game.device.os` from within any Scene. + * + * @typedef {object} Phaser.Device.OS + * @since 3.0.0 + * + * @property {boolean} android - Is running on android? + * @property {boolean} chromeOS - Is running on chromeOS? + * @property {boolean} cordova - Is the game running under Apache Cordova? + * @property {boolean} crosswalk - Is the game running under the Intel Crosswalk XDK? + * @property {boolean} desktop - Is running on a desktop? + * @property {boolean} ejecta - Is the game running under Ejecta? + * @property {boolean} electron - Is the game running under GitHub Electron? + * @property {boolean} iOS - Is running on iOS? + * @property {boolean} iPad - Is running on iPad? + * @property {boolean} iPhone - Is running on iPhone? + * @property {boolean} kindle - Is running on an Amazon Kindle? + * @property {boolean} linux - Is running on linux? + * @property {boolean} macOS - Is running on macOS? + * @property {boolean} node - Is the game running under Node.js? + * @property {boolean} nodeWebkit - Is the game running under Node-Webkit? + * @property {boolean} webApp - Set to true if running as a WebApp, i.e. within a WebView + * @property {boolean} windows - Is running on windows? + * @property {boolean} windowsPhone - Is running on a Windows Phone? + * @property {number} iOSVersion - If running in iOS this will contain the major version number. + * @property {number} pixelRatio - PixelRatio of the host device? + */ +var OS = { + + android: false, + chromeOS: false, + cordova: false, + crosswalk: false, + desktop: false, + ejecta: false, + electron: false, + iOS: false, + iOSVersion: 0, + iPad: false, + iPhone: false, + kindle: false, + linux: false, + macOS: false, + node: false, + nodeWebkit: false, + pixelRatio: 1, + webApp: false, + windows: false, + windowsPhone: false + +}; + +function init() { + if (typeof importScripts === 'function') { + return OS; + } + + var ua = navigator.userAgent; + + if ((/Windows/).test(ua)) { + OS.windows = true; + } + else if ((/Mac OS/).test(ua) && !((/like Mac OS/).test(ua))) { + // Because iOS 13 identifies as Mac OS: + if (navigator.maxTouchPoints && navigator.maxTouchPoints > 2) { + OS.iOS = true; + OS.iPad = true; + + (navigator.appVersion).match(/Version\/(\d+)/); + + OS.iOSVersion = parseInt(RegExp.$1, 10); + } + else { + OS.macOS = true; + } + } + else if ((/Android/).test(ua)) { + OS.android = true; + } + else if ((/Linux/).test(ua)) { + OS.linux = true; + } + else if ((/iP[ao]d|iPhone/i).test(ua)) { + OS.iOS = true; + + (navigator.appVersion).match(/OS (\d+)/); + + OS.iOSVersion = parseInt(RegExp.$1, 10); + + OS.iPhone = ua.toLowerCase().indexOf('iphone') !== -1; + OS.iPad = ua.toLowerCase().indexOf('ipad') !== -1; + } + else if ((/Kindle/).test(ua) || (/\bKF[A-Z][A-Z]+/).test(ua) || (/Silk.*Mobile Safari/).test(ua)) { + OS.kindle = true; + + // This will NOT detect early generations of Kindle Fire, I think there is no reliable way... + // E.g. "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-80) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true" + } + else if ((/CrOS/).test(ua)) { + OS.chromeOS = true; + } + + if ((/Windows Phone/i).test(ua) || (/IEMobile/i).test(ua)) { + OS.android = false; + OS.iOS = false; + OS.macOS = false; + OS.windows = true; + OS.windowsPhone = true; + } + + var silk = (/Silk/).test(ua); + + if (OS.windows || OS.macOS || (OS.linux && !silk) || OS.chromeOS) { + OS.desktop = true; + } + + // Windows Phone / Table reset + if (OS.windowsPhone || (((/Windows NT/i).test(ua)) && ((/Touch/i).test(ua)))) { + OS.desktop = false; + } + + // WebApp mode in iOS + if (navigator.standalone) { + OS.webApp = true; + } + + if (typeof importScripts !== 'function') { + if (window.cordova !== undefined) { + OS.cordova = true; + } + + if (window.ejecta !== undefined) { + OS.ejecta = true; + } + } + + if (typeof process !== 'undefined' && process.versions && process.versions.node) { + OS.node = true; + } + + if (OS.node && typeof process.versions === 'object') { + OS.nodeWebkit = !!process.versions['node-webkit']; + + OS.electron = !!process.versions.electron; + } + + if ((/Crosswalk/).test(ua)) { + OS.crosswalk = true; + } + + OS.pixelRatio = window['devicePixelRatio'] || 1; + + return OS; +} + +export default init(); diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/system/SortGameObjectsByDepth.js b/ui/src/phaser3-rex-plugins/plugins/utils/system/SortGameObjectsByDepth.js new file mode 100644 index 000000000..4556a71c6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/system/SortGameObjectsByDepth.js @@ -0,0 +1,27 @@ +var SortGameObjectsByDepth = function (gameObjects, descending) { + if (gameObjects.length <= 1) { + return gameObjects; + } + + if (descending === undefined) { + descending = false; + } + + var scene = gameObjects[0].scene; + var displayList = scene.sys.displayList; + displayList.depthSort(); + + if (descending) { + gameObjects.sort(function (childA, childB) { + return displayList.getIndex(childB) - displayList.getIndex(childA); + }) + } else { + gameObjects.sort(function (childA, childB) { + return displayList.getIndex(childA) - displayList.getIndex(childB); + }) + } + + return gameObjects; +} + +export default SortGameObjectsByDepth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/AppendText.js b/ui/src/phaser3-rex-plugins/plugins/utils/text/AppendText.js new file mode 100644 index 000000000..8df62c892 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/AppendText.js @@ -0,0 +1,28 @@ +var AppendText = function (value, addCR) { + if (!value && value !== 0) { + value = ''; + } + + if (addCR === undefined) { + addCR = true; + } + + if (Array.isArray(value)) { + value = value.join('\n'); + } + + var newText; + if (addCR) { + newText = `${this.text}\n${value}`; + } else { + newText = `${this.text}${value}`; + } + + if (newText != this.text) { + this.setText(newText); + } + + return this; +} + +export default AppendText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/FullFill.js b/ui/src/phaser3-rex-plugins/plugins/utils/text/FullFill.js new file mode 100644 index 000000000..c8245b1c9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/FullFill.js @@ -0,0 +1,22 @@ +var FullFill = function (textObject, width, height) { + textObject.setFixedSize(width, height); + // Remove padding + var padding = textObject.padding; + width -= (padding.left + padding.right); + height -= (padding.top + padding.bottom); + + var style = textObject.style; + // Set wrap width + style.wordWrapWidth = Math.max(width, 0); + + // Set max lines + // height = (maxLines * (lineHeight + lineSpacing)) - lineSpacing + var lineHeight = style.metrics.fontSize + style.strokeThickness; + var lineSpacing = textObject.lineSpacing; + var maxLines = Math.floor((height - lineSpacing) / (lineHeight + lineSpacing)); + style.maxLines = Math.max(maxLines, 0); + + // Redraw text + textObject.updateText(); +} +export default FullFill; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/GetTextObjectType.js b/ui/src/phaser3-rex-plugins/plugins/utils/text/GetTextObjectType.js new file mode 100644 index 000000000..05124fe3e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/GetTextObjectType.js @@ -0,0 +1,25 @@ +import IsTextGameObject from './IsTextGameObject.js'; +import IsBitmapTextGameObject from '../bitmaptext/IsBitmapTextGameObject.js'; + +const TextType = 0; +const TagTextType = 1; +const BitmapTextType = 2; + +export { + TextType, TagTextType, BitmapTextType +} + +var GetTextObjectType = function (textObject) { + var textObjectType; + if (IsBitmapTextGameObject(textObject)) { + textObjectType = BitmapTextType; + } else if (IsTextGameObject(textObject)) { + textObjectType = TextType; + } else { + textObjectType = TagTextType; + } + + return textObjectType; +} + +export default GetTextObjectType; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/GetWrapText.js b/ui/src/phaser3-rex-plugins/plugins/utils/text/GetWrapText.js new file mode 100644 index 000000000..bd533f107 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/GetWrapText.js @@ -0,0 +1,23 @@ +import { + TextType, TagTextType, BitmapTextType +} from './GetTextObjectType.js'; +import GetTextObjectType from './GetTextObjectType.js'; + +var GetWrapText = function (textObject, text) { + var textObjectType = GetTextObjectType(textObject); + switch (textObjectType) { + case TextType: + textObject.style.syncFont(textObject.canvas, textObject.context); + text = textObject.runWordWrap(text); + break; + case TagTextType: + text = textObject.getText(text, undefined, undefined, true); + break; + case BitmapTextType: + text = textObject.setText(text).getTextBounds().wrappedText; + break; + } + return text; +} + +export default GetWrapText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/IsTextGameObject.js b/ui/src/phaser3-rex-plugins/plugins/utils/text/IsTextGameObject.js new file mode 100644 index 000000000..9b022f23f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/IsTextGameObject.js @@ -0,0 +1,7 @@ +const TextKlass = Phaser.GameObjects.Text; + +var IsTextGameObject = function (gameObject) { + return (gameObject instanceof TextKlass); +} + +export default IsTextGameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/SetNoWrapText.js b/ui/src/phaser3-rex-plugins/plugins/utils/text/SetNoWrapText.js new file mode 100644 index 000000000..f62f0dbed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/SetNoWrapText.js @@ -0,0 +1,49 @@ +import { + TextType, TagTextType, BitmapTextType +} from './GetTextObjectType.js'; +import GetTextObjectType from './GetTextObjectType.js'; + +var SetNoWrapText = function (textObject, text) { + var textObjectType = GetTextObjectType(textObject); + switch (textObjectType) { + case TextType: + // Store wrap properties + var style = textObject.style; + var wordWrapWidth = style.wordWrapWidth; + var wordWrapCallback = style.wordWrapCallback; + // Disable wrap + style.wordWrapWidth = 0; + style.wordWrapCallback = undefined; + // Set text + textObject.setText(text); + // Restore wrap + style.wordWrapWidth = wordWrapWidth; + style.wordWrapCallback = wordWrapCallback; + break; + + case TagTextType: + // Store wrap properties + var style = textObject.style; + var wrapMode = style.wrapMode; + // Disable wrap + style.wrapMode = 0; + // Set text + textObject.setText(text); + // Restore wrap + style.wrapMode = wrapMode; + break; + + case BitmapTextType: + // Store wrap properties + var maxWidth = textObject._maxWidth; + // Disable wrap + textObject._maxWidth = 0; + // Set text + textObject.setText(text); + // Restore wrap + textObject._maxWidth = maxWidth; + break; + } +} + +export default SetNoWrapText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/TextToLines.js b/ui/src/phaser3-rex-plugins/plugins/utils/text/TextToLines.js new file mode 100644 index 000000000..22a5c1de2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/TextToLines.js @@ -0,0 +1,27 @@ +import { + TextType, TagTextType, BitmapTextType +} from './GetTextObjectType.js'; +import GetTextObjectType from './GetTextObjectType.js'; + +var TextToLines = function (textObject, text, lines) { + var textObjectType = GetTextObjectType(textObject); + switch (textObjectType) { + case TextType: + lines = textObject.getWrappedText(text); // Array of string + break; + case TagTextType: + lines = textObject.getPenManager(text, lines); // Pens-manager + break; + case BitmapTextType: + if (textObject.maxWidth > 0) { + lines = textObject.setText(text).getTextBounds().wrappedText.split('\n'); + } else { + lines = text.split('\n'); + } + + break; + } + return lines; +} + +export default TextToLines; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/setfontsizetofitwidth/SetFontSizeToFitWidth.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/text/setfontsizetofitwidth/SetFontSizeToFitWidth.d.ts new file mode 100644 index 000000000..9be055883 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/setfontsizetofitwidth/SetFontSizeToFitWidth.d.ts @@ -0,0 +1,5 @@ +export default function ( + textObject: Phaser.GameObjects.GameObject, + width: number, + height?: number +): Phaser.GameObjects.GameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/setfontsizetofitwidth/SetFontSizeToFitWidth.js b/ui/src/phaser3-rex-plugins/plugins/utils/text/setfontsizetofitwidth/SetFontSizeToFitWidth.js new file mode 100644 index 000000000..fb6568068 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/setfontsizetofitwidth/SetFontSizeToFitWidth.js @@ -0,0 +1,99 @@ +const MaxTestCount = 65535; + +var SetFontSizeToFitWidth = function (textObject, width, height) { + if (width == null) { + // Do nothing if invalid width input + return textObject; + } + if (width === 0) { + SetTextWidth(textObject, 0, height); + return textObject; + } + + var textLength = textObject.text.length; + if (textLength === 0) { + SetTextWidth(textObject, width, height); + return textObject; + } + + var fontSize = Math.floor(width * 1.5 / textLength); + + var sizeData = {}; + var testResult = TestFontSize(textObject, fontSize, width, height, sizeData); + for (var i = 0; i <= MaxTestCount; i++) { + if (testResult === 0) { + break; + } else { + fontSize += testResult; + if (fontSize < 0) { + fontSize = 0; + break; + } + } + testResult = TestFontSize(textObject, fontSize, width, height, sizeData); + // console.log(fontSize, testResult) + } + + if (i === MaxTestCount) { + console.warn(`SetFontSizeToFitWidth: Test count exceeds ${MaxTestCount}`); + } + + textObject.setFontSize(fontSize); + SetTextWidth(textObject, width, height); + + return textObject; +} + +var GetTextSize = function (textObject, fontSize, sizeData) { + if (sizeData[fontSize] === undefined) { + textObject.setFontSize(fontSize) + sizeData[fontSize] = { + width: textObject.width, + height: textObject.height + } + } + + return sizeData[fontSize] +} + +var TestFontSize = function (textObject, fontSize, width, height, sizeData) { + var textSize = GetTextSize(textObject, fontSize, sizeData); + var textSize1 = GetTextSize(textObject, fontSize + 1, sizeData); + + if (height !== undefined) { + // Clamp by height + if ((textSize.height <= height) && (textSize1.height > height)) { + return 0; + + } else if (textSize.height > height) { // Reduce text size + return -1; + } + } + + // Clamp by width + if ((textSize.width <= width) && (textSize1.width > width)) { + return 0; + + } else if (textSize.width > width) { // Reduce text size + return -1; + + } else { // Increase text size + return Math.floor(width - textSize.width); + } +} + +var SetTextWidth = function (textObject, width, height) { + var style = textObject.style; + + style.fixedWidth = width; + style.parent.width = width; + + if (height !== undefined) { + style.fixedHeight = height; + style.parent.height = height; + } + + style.update(false); +} + +export default SetFontSizeToFitWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/TextBob.js b/ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/TextBob.js new file mode 100644 index 000000000..3835c03f6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/TextBob.js @@ -0,0 +1,63 @@ +import BobBase from '../../gameobject/gomanager/bobbase/BobBase.js'; +import TextTyping from '../../../behaviors/texttyping/TextTyping.js'; + +const IsTyping = false; + +class TextBob extends BobBase { + setGO(gameObject, name) { + super.setGO(gameObject, name); + gameObject.setData('typing', !IsTyping); + return this; + } + + clearText() { + this.gameObject.setText(''); + return this; + } + + appendText(text) { + this.gameObject.setText(this.gameObject.text + text); + return this; + } + + setTypingSpeed(speed) { + var gameObject = this.gameObject; + if (!gameObject.typing) { + gameObject.typing = new TextTyping(gameObject); + } + gameObject.typing.setTypingSpeed(speed); + return this; + } + + clearTyping() { + var gameObject = this.gameObject; + if (!gameObject.typing) { + gameObject.typing = new TextTyping(gameObject); + } else { + gameObject.typing.start(''); + } + return this; + } + + typing(text, speed) { + var gameObject = this.gameObject; + if (!gameObject.typing) { + gameObject.typing = new TextTyping(gameObject); + } + + if (speed !== undefined) { + gameObject.typing.setTypingSpeed(speed); + } + + gameObject.setData('typing', IsTyping); + gameObject.typing + .once('complete', function () { + gameObject.setData('typing', !IsTyping); + }) + .appendText(text) + + return this; + } +} + +export default TextBob; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/TextManager.js b/ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/TextManager.js new file mode 100644 index 000000000..b3afc58cb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/TextManager.js @@ -0,0 +1,35 @@ +import GOManager from '../../gameobject/gomanager/GOManager.js'; +import TextBob from './TextBob.js'; +import Methods from './methods/Methods.js'; + +class TextManager extends GOManager { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + config.BobClass = TextBob; + + super(scene, config); + } + + setCreateGameObjectCallback(callback, scope) { + if ((!callback) || (callback === 'text')) { + callback = CreateTextObject; + } + super.setCreateGameObjectCallback(callback, scope); + return this; + } + +} + +var CreateTextObject = function (scene) { + return scene.add.text(0, 0, ''); +} + +Object.assign( + TextManager.prototype, + Methods +); + +export default TextManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/methods/Methods.js new file mode 100644 index 000000000..525939f2f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/methods/Methods.js @@ -0,0 +1,9 @@ +import SetTextMethods from './SetTextMethods.js'; + +var Methods = {} +Object.assign( + Methods, + SetTextMethods +) + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/methods/SetTextMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/methods/SetTextMethods.js new file mode 100644 index 000000000..ba67c0f93 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/methods/SetTextMethods.js @@ -0,0 +1,55 @@ +export default { + clearText(name) { + if (!this.has(name)) { + return this; + } + + this.get(name).clearText(); + return this; + }, + + appendText(name, text) { + if (!this.has(name)) { + return this; + } + + this.get(name).appendText(text); + return this; + }, + + clearTyping(name) { + if (!this.has(name)) { + return this; + } + + this.get(name).clearTyping(); + return this; + }, + + typing(name, text) { + if (!this.has(name)) { + return this; + } + + this.get(name).typing(text); + return this; + }, + + appendTyping(name, text) { + if (!this.has(name)) { + return this; + } + + this.get(name).appendTyping(text); + return this; + }, + + setTypingSpeed(name, speed) { + if (!this.has(name)) { + return this; + } + + this.get(name).setTypingSpeed(speed); + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/CopyCanvasToTexture.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CopyCanvasToTexture.js new file mode 100644 index 000000000..1df205c1a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CopyCanvasToTexture.js @@ -0,0 +1,41 @@ +var CopyCanvasToTexture = function (scene, srcCanvas, key, x, y, width, height) { + var textures = scene.sys.textures; + var renderer = scene.renderer; + + if (x === undefined) { + x = 0; + } + if (y === undefined) { + y = 0; + } + if (width === undefined) { + width = srcCanvas.width; + } + if (height === undefined) { + height = srcCanvas.height; + } + + var texture; + if (textures.exists(key)) { + texture = textures.get(key); + } else { + texture = textures.createCanvas(key, width, height); + } + + var destCanvas = texture.getSourceImage(); + if (destCanvas.width !== width) { + destCanvas.width = width; + } + if (destCanvas.height !== height) { + destCanvas.height = height; + } + + var destCtx = destCanvas.getContext('2d', { willReadFrequently: true }); + destCtx.clearRect(0, 0, width, height); + destCtx.drawImage(srcCanvas, x, y, width, height); + if (renderer.gl && texture) { + renderer.canvasToTexture(destCanvas, texture.source[0].glTexture, true, 0); + } +} + +export default CopyCanvasToTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateCircleTexture.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateCircleTexture.js new file mode 100644 index 000000000..be7066889 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateCircleTexture.js @@ -0,0 +1,48 @@ +import GetStyle from '../canvas/GetStyle.js'; +import DrawCircle from '../canvas/DrawCircle.js'; + +var CreateCircleTexture = function ( + scene, + key, + width, + fillStyle, + strokeStyle, lineWidth, + expandSize +) { + + if ((fillStyle === undefined) && (strokeStyle === undefined)) { + fillStyle = 0xffffff; + } + if (strokeStyle === undefined) { + lineWidth = 0; + } else if (lineWidth === undefined) { + lineWidth = 2; + } + if (expandSize === undefined) { + expandSize = false; + } + + var r, x; + if (!expandSize) { + x = width / 2; + r = x - (lineWidth / 2); + } else { + r = width / 2; + width += lineWidth; + x = width / 2; + } + + var texture = scene.sys.textures.createCanvas(key, width, width); + var canvas = texture.getCanvas(); + var context = texture.getContext(); + + DrawCircle( + canvas, context, + x, x, r, r, + GetStyle(fillStyle), + GetStyle(strokeStyle), lineWidth + ); + + texture.refresh(); +} +export default CreateCircleTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateDashedTexture.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateDashedTexture.js new file mode 100644 index 000000000..9ba5a1dc1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateDashedTexture.js @@ -0,0 +1,21 @@ +var CreateDashedTexture = function (scene, key, width, k, color, height) { + if (width === undefined) { + width = 10; + } + if (k === undefined) { + k = 0.5; + } + if (color === undefined) { + color = 0xffffff; + } + if (height === undefined) { + height = 2; + } + + scene.add.graphics() + .fillStyle(color) + .fillRect(0, 0, (width * k), height) + .generateTexture(key, width, height) + .destroy(); +} +export default CreateDashedTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreatePolygonTexture.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreatePolygonTexture.js new file mode 100644 index 000000000..8eef2e899 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreatePolygonTexture.js @@ -0,0 +1,135 @@ +import GetStyle from '../canvas/GetStyle.js'; +import DrawPolygon from '../canvas/DrawPolygon.js'; + +var CreatePolygonTexture = function ( + scene, + key, + points, + fillStyle, + strokeStyle, lineWidth, + expandSize, + lineJoin +) { + + if ((fillStyle === undefined) && (strokeStyle === undefined)) { + fillStyle = 0xffffff; + } + + if (strokeStyle === undefined) { + lineWidth = 0; + } else if (lineWidth === undefined) { + lineWidth = 2; + } + + if (lineJoin === undefined) { + lineJoin = 'round'; + } + + if (expandSize === undefined) { + expandSize = false; + } + + globBounds = GetBounds(points, globBounds); + OffsetPoints(points, -globBounds.left, -globBounds.top); + + var width = globBounds.right - globBounds.left; + var height = globBounds.bottom - globBounds.top; + + if (!expandSize) { + IndentPoints(points, globBounds, lineWidth); + } else { + width += lineWidth; + height += lineWidth; + OffsetPoints(points, lineWidth / 2); + } + + var texture = scene.sys.textures.createCanvas(key, Math.ceil(width), Math.ceil(height)); + var canvas = texture.getCanvas(); + var context = texture.getContext(); + + DrawPolygon( + canvas, context, + points, + GetStyle(fillStyle, canvas, context), + GetStyle(strokeStyle, canvas, context), + lineWidth, + lineJoin + ) + + texture.refresh(); +} + +var GetBounds = function (points, out) { + if (out === undefined) { + out = {}; + } + + var left = Infinity, + top = Infinity, + right = -Infinity, + bottom = -Infinity; + for (var i = 0, cnt = points.length; i < cnt; i++) { + var p = points[i], px = p.x, py = p.y; + left = Math.min(left, px); + top = Math.min(top, py); + right = Math.max(right, px); + bottom = Math.max(bottom, py); + } + + out.left = left; + out.top = top; + out.right = right; + out.bottom = bottom; + + return out; +} + +var IndentPoints = function (points, bounds, lineWidth) { + if (lineWidth === 0) { + return points; + } + + var width = bounds.right - bounds.left; + var height = bounds.bottom - bounds.top; + var halfW = width / 2; + var halfH = height / 2; + var halfLW = lineWidth / 2; + for (var i = 0, cnt = points.length; i < cnt; i++) { + var p = points[i]; + p.x = Indent(p.x, halfW, halfLW); + p.y = Indent(p.y, halfH, halfLW); + } + + return points; +} + +var Indent = function (value, halfBound, offset) { + if (value < halfBound) { + return (value + offset); + } else if (value > halfBound) { + return (value - offset); + } else { + return value; + } +} + +var OffsetPoints = function (points, x, y) { + if (y === undefined) { + y = x; + } + + if ((x === 0) && (y === 0)) { + return points; + } + + for (var i = 0, cnt = points.length; i < cnt; i++) { + var p = points[i]; + p.x += x; + p.y += y; + } + return points; +} + +var globBounds; + +export default CreatePolygonTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateRectangleTexture.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateRectangleTexture.js new file mode 100644 index 000000000..a9b83e699 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateRectangleTexture.js @@ -0,0 +1,56 @@ +import GetStyle from '../canvas/GetStyle.js'; +import DrawRectangle from '../canvas/DrawRectangle.js'; + +var CreateRectangleTexture = function ( + scene, + key, + width, height, + fillStyle, + strokeStyle, lineWidth, + fillColor2, + isHorizontalGradient, + expandSize +) { + + if (height === undefined) { + height = width; + } + if (isHorizontalGradient === undefined) { + isHorizontalGradient = true; + } + if ((fillStyle === undefined) && (strokeStyle === undefined)) { + fillStyle = 0xffffff; + } + if (strokeStyle === undefined) { + lineWidth = 0; + } else if (lineWidth === undefined) { + lineWidth = 2; + } + + if (expandSize === undefined) { + expandSize = false; + } + + if (!expandSize) { + width -= lineWidth; + height -= lineWidth; + } + + var texture = scene.sys.textures.createCanvas(key, (width + lineWidth), (height + lineWidth)); + var canvas = texture.getCanvas(); + var context = texture.getContext(); + + // Draw canvas + var x = lineWidth / 2; + DrawRectangle( + canvas, context, + x, x, + width, height, + GetStyle(fillStyle), + GetStyle(strokeStyle), lineWidth, + GetStyle(fillColor2), isHorizontalGradient + ); + + texture.refresh(); +} +export default CreateRectangleTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateRoundRectangleTexture.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateRoundRectangleTexture.js new file mode 100644 index 000000000..87bcbbb62 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateRoundRectangleTexture.js @@ -0,0 +1,60 @@ +import GetStyle from '../canvas/GetStyle.js'; +import DrawRoundRectangle from '../canvas/DrawRoundRectangle.js'; + +var CreateRectangleTexture = function ( + scene, + key, + width, height, + radiusConfig, + fillStyle, + strokeStyle, lineWidth, + fillColor2, + isHorizontalGradient, + iteration, + expandSize +) { + + if (height === undefined) { + height = width; + } + if (isHorizontalGradient === undefined) { + isHorizontalGradient = true; + } + if ((fillStyle === undefined) && (strokeStyle === undefined)) { + fillStyle = 0xffffff; + } + if (strokeStyle === undefined) { + lineWidth = 0; + } else if (lineWidth === undefined) { + lineWidth = 2; + } + + if (expandSize === undefined) { + expandSize = false; + } + + if (!expandSize) { + width -= lineWidth; + height -= lineWidth; + } + + var texture = scene.sys.textures.createCanvas(key, (width + lineWidth), (height + lineWidth)); + var canvas = texture.getCanvas(); + var context = texture.getContext(); + + // Draw canvas + var x = lineWidth / 2; + DrawRoundRectangle( + canvas, context, + x, x, + width, height, + radiusConfig, + GetStyle(fillStyle), + GetStyle(strokeStyle), lineWidth, + GetStyle(fillColor2), isHorizontalGradient, + iteration + ); + + texture.refresh(); +} +export default CreateRectangleTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateTriangleTexture.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateTriangleTexture.js new file mode 100644 index 000000000..0fe318880 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateTriangleTexture.js @@ -0,0 +1,71 @@ +var CreateTriangleTexture = function ( + scene, + key, + width, height, + color, + direction +) { + + if (height === undefined) { + height = width; + } + if (color === undefined) { + color = 0xffffff; + } + if (direction === undefined) { + direction = 0; + } + if (typeof (direction) === 'string') { + direction = DIRMODE[direction]; + } + + var x1, y1, x2, y2, x3, y3; + switch (direction) { + case 1: // down + x1 = 0; + y1 = 0; + x2 = width; + y2 = 0; + x3 = width / 2; + y3 = height; + break; + case 2: // right + x1 = 0; + y1 = height / 2; + x2 = width; + y2 = 0; + x3 = width; + y3 = height; + break; + case 3: // up + x1 = 0; + y1 = height; + x2 = width / 2; + y2 = 0; + x3 = width; + y3 = height; + break; + default: // 0, right + x1 = 0; + y1 = 0; + x2 = 0; + y2 = height; + x3 = width; + y3 = height / 2; + break; + } + + scene.add.graphics() + .fillStyle(color) + .fillTriangle(x1, y1, x2, y2, x3, y3) + .generateTexture(key, width, height) + .destroy(); +} + +const DIRMODE = { + 'right': 0, + 'down': 1, + 'left': 2, + 'up': 3, +} +export default CreateTriangleTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/DrawFrameToCanvas.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/DrawFrameToCanvas.js new file mode 100644 index 000000000..2d190c3cf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/DrawFrameToCanvas.js @@ -0,0 +1,9 @@ +var DrawFrame = function (frame, canvas) { + canvas.width = frame.cutWidth; + canvas.height = frame.cutHeight; + var context = canvas.getContext('2d', { willReadFrequently: true }); + context.drawImage(frame.source.image, frame.cutX, frame.cutY, frame.cutWidth, frame.cutHeight); + return canvas; +} + +export default DrawFrame; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/HasTexture.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/HasTexture.js new file mode 100644 index 000000000..79334c6a8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/HasTexture.js @@ -0,0 +1,15 @@ +import GetGame from '../system/GetGame.js'; + +var HasTexture = function (game, key, frame) { + game = GetGame(game); + var cache = game.textures; + + var hasTexture = cache.exists(key); + if (frame === undefined) { + return hasTexture; + } + + return cache.get(key).has(frame); +} + +export default HasTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/LocalXYToColor.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/LocalXYToColor.js new file mode 100644 index 000000000..e6bad50e4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/LocalXYToColor.js @@ -0,0 +1,9 @@ +var LocalXYToColor = function (gameObject, localX, localY) { + var textureKey = gameObject.texture.key; + var frameName = gameObject.frame.name; + var textureManager = gameObject.scene.sys.textures; + var color = textureManager.getPixel(localX, localY, textureKey, frameName); + return color; +} + +export default LocalXYToColor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/ToBase64.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/ToBase64.js new file mode 100644 index 000000000..7e733291f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/ToBase64.js @@ -0,0 +1,28 @@ +const CanvasPool = Phaser.Display.Canvas.CanvasPool; + +var ToBase64 = function (gameObject, type, encoderOptions) { + var frame = gameObject.frame; + var cd = frame.canvasData; + var canvas = CanvasPool.create2D(this, cd.width, cd.height); + var ctx = canvas.getContext('2d', { willReadFrequently: true }); + + ctx.drawImage( + frame.source.image, + cd.x, + cd.y, + cd.width, + cd.height, + 0, + 0, + cd.width, + cd.height + ); + + var data = canvas.toDataURL(type, encoderOptions); + + CanvasPool.remove(canvas); + + return data; +} + +export default ToBase64; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/gridcut/GetFrameNameCallback.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/gridcut/GetFrameNameCallback.js new file mode 100644 index 000000000..b9a44a3b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/gridcut/GetFrameNameCallback.js @@ -0,0 +1,19 @@ +var GetFrameNameCallback = function (baseFrameName, delimiter) { + if (delimiter === undefined) { + delimiter = ','; + } + + var callback; + if (baseFrameName === '__BASE') { + callback = function (colIndex, rowIndex) { + return `${colIndex}${delimiter}${rowIndex}`; + } + } else { + callback = function (colIndex, rowIndex) { + return `${baseFrameName}_${colIndex}${delimiter}${rowIndex}`; + } + } + + return callback; +} +export default GetFrameNameCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/gridcut/GridCut.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/gridcut/GridCut.js new file mode 100644 index 000000000..b5791e1f3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/gridcut/GridCut.js @@ -0,0 +1,52 @@ +import GetFrameNameCallback from "./GetFrameNameCallback"; + +var GridCut = function (scene, key, frame, columns, rows, getFrameNameCallback) { + if (frame == null) { + frame = '__BASE'; + } + + if (!getFrameNameCallback) { + getFrameNameCallback = GetFrameNameCallback(frame, getFrameNameCallback); + } + + var texture = scene.sys.textures.get(key); + var baseFrame = (typeof (frame) === 'object') ? frame : texture.get(frame); + + var baseWidth = baseFrame.width, + baseHeight = baseFrame.height; + + var cellX, cellY, cellName; + var cellWidth = baseWidth / columns, + cellHeight = baseHeight / rows; + + var offsetX = 0, + offsetY = 0; + for (var y = 0; y < rows; y++) { + offsetX = 0; + for (var x = 0; x < columns; x++) { + cellName = getFrameNameCallback(x, y); + + cellX = offsetX + baseFrame.cutX; + cellY = offsetY + baseFrame.cutY; + + texture.add( + cellName, 0, + cellX, cellY, + cellWidth, cellHeight + ); + + offsetX += cellWidth; + } + offsetY += cellHeight; + } + + return { + getFrameNameCallback: getFrameNameCallback, + cellWidth: cellWidth, + cellHeight: cellHeight, + columns: columns, + rows: rows + } +} + +export default GridCut; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/imagemanager/AddImage.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/imagemanager/AddImage.js new file mode 100644 index 000000000..8fc4468b2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/imagemanager/AddImage.js @@ -0,0 +1,48 @@ +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +var AddImage = function (key, config) { + if (IsPlainObject(key)) { + config = key; + key = config.key; + } else if (config === undefined) { + config = { + key: key + } + } + + if (!config.hasOwnProperty('key')) { + config.key = key; + } + + var textureKey = config.key, frameKey = config.frame; + var width = config.width, height = config.height; + + if ((width === undefined) || (height === undefined)) { + var frame = this.textureManager.getFrame(textureKey, frameKey); + var frameWidth = (frame) ? frame.cutWidth : 0; + var frameHeight = (frame) ? frame.cutHeight : 0; + if ((width === undefined) && (height === undefined)) { + width = frameWidth; + height = frameHeight; + } else if (width === undefined) { + width = frameWidth * (height / frameHeight); + } else if (height === undefined) { + height = frameHeight * (width / frameWidth); + } + } + + this.images[key] = { + key: textureKey, + frame: frameKey, + width: width, + height: height, + y: GetValue(config, 'y', 0), + left: GetValue(config, 'left', 0), + right: GetValue(config, 'right', 0), + originX: GetValue(config, 'originX', 0), + originY: GetValue(config, 'originY', 0), + } +} + +export default AddImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/imagemanager/DrawImage.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/imagemanager/DrawImage.js new file mode 100644 index 000000000..a94f53fee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/imagemanager/DrawImage.js @@ -0,0 +1,21 @@ +var DrawImage = function (key, context, x, y, autoRound) { + var imgData = this.get(key); + var frame = this.textureManager.getFrame(imgData.key, imgData.frame); + + var width = imgData.width, + height = imgData.height; + x += imgData.left - (imgData.originX * width); + y += imgData.y - (imgData.originY * height); + if (autoRound) { + x = Math.round(x); + y = Math.round(y); + } + + context.drawImage( + frame.source.image, + frame.cutX, frame.cutY, frame.cutWidth, frame.cutHeight, + x, y, width, height + ); +} + +export default DrawImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/texture/imagemanager/ImageManager.js b/ui/src/phaser3-rex-plugins/plugins/utils/texture/imagemanager/ImageManager.js new file mode 100644 index 000000000..05e238b44 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/texture/imagemanager/ImageManager.js @@ -0,0 +1,77 @@ +import AddImage from './AddImage.js'; +import DrawImage from './DrawImage.js'; + +class ImageManager { + constructor(scene) { + this.textureManager = scene.sys.textures; + this.images = {}; + } + + destroy() { + this.textureManager = undefined; + this.images = undefined; + } + + add(key, config) { + if (typeof (key) === 'string') { + AddImage.call(this, key, config); + } else if (Array.isArray(key)) { + var data = key; + for (var i = 0, cnt = data.length; i < cnt; i++) { + AddImage.call(this, data[i]); + } + } else { + var data = key; + for (var key in data) { + AddImage.call(this, key, data[key]); + } + } + return this; + } + + has(key) { + return this.images.hasOwnProperty(key); + } + + remove(key) { + if (this.has(key)) { + delete this.images[key]; + } + return this; + } + + get(key) { + if (!this.has(key)) { + if (this.textureManager.exists(key)) { + this.add(key); + } + } + return this.images[key]; + } + + getOuterWidth(key) { + var data = this.get(key); + return (data) ? (data.width + data.left + data.right) : 0; + } + + getFrame(key) { + var data = this.get(key); + return (data) ? this.textureManager.getFrame(data.key, data.frame) : undefined; + } + + hasTexture(key) { + return !!this.getFrame(key); + } +} + +var methods = { + draw: DrawImage +} + +Object.assign( + ImageManager.prototype, + methods +); + + +export default ImageManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/time/GetPeriodMS.js b/ui/src/phaser3-rex-plugins/plugins/utils/time/GetPeriodMS.js new file mode 100644 index 000000000..2ffcc39cd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/time/GetPeriodMS.js @@ -0,0 +1,20 @@ +var GetPeriodMS = function (period) { + if (typeof (period) === 'number') { + return period; + } + + var config = period; + var days = config.day || config.d || 0; + var hours = config.hour || config.h || 0; + var minutes = config.minute || config.m || 0; + var seconds = config.second || config.s || 0; + + hours += days * 24; + minutes += hours * 60; + seconds += minutes * 60; + period = seconds * 1000; + + return period; +} + +export default GetPeriodMS; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/time/NextTick.js b/ui/src/phaser3-rex-plugins/plugins/utils/time/NextTick.js new file mode 100644 index 000000000..cc2aa8622 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/time/NextTick.js @@ -0,0 +1,5 @@ +var NextTick = function (scene, callback, scope) { + return scene.time.delayedCall(0, callback, [], scope); +} + +export default NextTick; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/time/PostStepDelayCall.js b/ui/src/phaser3-rex-plugins/plugins/utils/time/PostStepDelayCall.js new file mode 100644 index 000000000..8381ca105 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/time/PostStepDelayCall.js @@ -0,0 +1,14 @@ +import GetSceneObject from '../system/GetSceneObject.js'; + +var PostStepDelayCall = function (gameObject, delay, callback, scope, args) { + // Invoke callback under game's 'poststep' event + var scene = GetSceneObject(gameObject); + var timer = scene.time.delayedCall(delay, function () { + scene.game.events.once('poststep', function () { + callback.call(scope, args); + }); + }) + return timer; +} + +export default PostStepDelayCall; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/time/PostUpdateDelayCall.js b/ui/src/phaser3-rex-plugins/plugins/utils/time/PostUpdateDelayCall.js new file mode 100644 index 000000000..72360d11b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/time/PostUpdateDelayCall.js @@ -0,0 +1,14 @@ +import GetSceneObject from '../system/GetSceneObject.js'; + +var PostUpdateDelayCall = function (gameObject, delay, callback, scope, args) { + // Invoke callback under scene's 'postupdate' event + var scene = GetSceneObject(gameObject); + var timer = scene.time.delayedCall(delay, function () { + scene.sys.events.once('postupdate', function () { + callback.call(scope, args); + }) + }) + return timer; +} + +export default PostUpdateDelayCall; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/time/PreUpdateDelayCall.js b/ui/src/phaser3-rex-plugins/plugins/utils/time/PreUpdateDelayCall.js new file mode 100644 index 000000000..d2a7e97d6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/time/PreUpdateDelayCall.js @@ -0,0 +1,14 @@ +import GetSceneObject from '../system/GetSceneObject.js'; + +var PreUpdateDelayCall = function (gameObject, delay, callback, scope, args) { + // Invoke callback under scene's 'preupdate' event + var scene = GetSceneObject(gameObject); + var timer = scene.time.delayedCall(delay, function () { + scene.sys.events.once('preupdate', function () { + callback.call(scope, args); + }) + }) + return timer; +} + +export default PreUpdateDelayCall; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/time/UpdateDelayCall.js b/ui/src/phaser3-rex-plugins/plugins/utils/time/UpdateDelayCall.js new file mode 100644 index 000000000..6d97105f8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/time/UpdateDelayCall.js @@ -0,0 +1,14 @@ +import GetSceneObject from '../system/GetSceneObject.js'; + +var UpdateDelayCall = function (gameObject, delay, callback, scope, args) { + // Invoke callback under scene's 'update' event + var scene = GetSceneObject(gameObject); + var timer = scene.time.delayedCall(delay, function () { + scene.sys.events.once('update', function () { + callback.call(scope, args); + }) + }) + return timer; +} + +export default UpdateDelayCall; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/time/cooldown/Cooldown.js b/ui/src/phaser3-rex-plugins/plugins/utils/time/cooldown/Cooldown.js new file mode 100644 index 000000000..344e95355 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/time/cooldown/Cooldown.js @@ -0,0 +1,52 @@ +import FSM from '../../../logic/fsm/FSM.js'; +class Cooldown extends FSM { + constructor() { + super({ + eventEmitter: false + }) + + this.goto('IDLE'); + } + + setCooldownTime(time) { + this.cooldownTime = time; + this.cooldownMode = (time !== undefined); + return this; + } + + request() { + return this.runMethod('request'); + } + + // IDLE state + update_IDLE() { + this.compensationTime = 0; + } + request_IDLE() { + this.next(); + return true; + } + next_IDLE() { + if (this.cooldownMode) { + return 'COOLDOWN'; + } + } + + // COOLDOWN state + enter_COOLDOWN() { + this.remainderTime = this.cooldownTime + this.compensationTime; + } + update_COOLDOWN(time, delta) { + this.remainderTime -= delta; + if (this.remainderTime < 0) { + this.compensationTime = (this.cooldownTime > delta) ? (-this.remainderTime) : 0; + this.goto('IDLE'); + } + } + request_COOLDOWN() { + return false; + } + +} + +export default Cooldown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/tween/AutoRemoveTween.js b/ui/src/phaser3-rex-plugins/plugins/utils/tween/AutoRemoveTween.js new file mode 100644 index 000000000..7fb03806e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/tween/AutoRemoveTween.js @@ -0,0 +1,14 @@ +var AutoRemoveTween = function (gameObject, config) { + var scene = gameObject.scene; + config.targets = gameObject; + var tween = scene.tweens.add(config); + + gameObject.once('destroy', tween.remove, tween); + tween.once('complete', function () { + gameObject.off('destroy', tween.remove, tween); + }) + + return tween; +} + +export default AutoRemoveTween; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/types/CanvasGameObjectBase.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/types/CanvasGameObjectBase.d.ts new file mode 100644 index 000000000..3e00a3215 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/types/CanvasGameObjectBase.d.ts @@ -0,0 +1,128 @@ +// import * as Phaser from 'phaser'; + +export default class CanvasGameObjectBase extends Phaser.GameObjects.GameObject { + // Components + + clearAlpha(): this; + setAlpha(topLeft?: number, topRight?: number, bottomLeft?: number, bottomRight?: number): this; + alpha: number; + alphaTopLeft: number; + alphaTopRight: number; + alphaBottomLeft: number; + alphaBottomRight: number; + + blendMode: Phaser.BlendModes | string; + setBlendMode(value: string | Phaser.BlendModes): this; + + width: number; + height: number; + displayWidth: number; + displayHeight: number; + setSize(width: number, height: number): this; + setDisplaySize(width: number, height: number): this; + texture: Phaser.Textures.Texture | Phaser.Textures.CanvasTexture; + frame: Phaser.Textures.Frame; + isCropped: boolean; + setCrop(x?: number | Phaser.Geom.Rectangle, y?: number, width?: number, height?: number): this; + + depth: number; + setDepth(value: number): this; + + flipX: boolean; + flipY: boolean; + toggleFlipX(): this; + toggleFlipY(): this; + setFlipX(value: boolean): this; + setFlipY(value: boolean): this; + setFlip(x: boolean, y: boolean): this; + resetFlip(): this; + + getCenter(output?: O): O; + getTopLeft(output?: O, includeParent?: boolean): O; + getTopCenter(output?: O, includeParent?: boolean): O; + getTopRight(output?: O, includeParent?: boolean): O; + getLeftCenter(output?: O, includeParent?: boolean): O; + getRightCenter(output?: O, includeParent?: boolean): O; + getBottomLeft(output?: O, includeParent?: boolean): O; + getBottomCenter(output?: O, includeParent?: boolean): O; + getBottomRight(output?: O, includeParent?: boolean): O; + getBounds(output?: O): O; + + mask: Phaser.Display.Masks.BitmapMask | Phaser.Display.Masks.GeometryMask; + setMask(mask: Phaser.Display.Masks.BitmapMask | Phaser.Display.Masks.GeometryMask): this; + clearMask(destroyMask?: boolean): this; + createBitmapMask(renderable?: Phaser.GameObjects.GameObject): Phaser.Display.Masks.BitmapMask; + createGeometryMask(graphics?: Phaser.GameObjects.Graphics): Phaser.Display.Masks.GeometryMask; + + originX: number; + originY: number; + displayOriginX: number; + displayOriginY: number; + setOrigin(x?: number, y?: number): this; + setOriginFromFrame(): this; + setDisplayOrigin(x?: number, y?: number): this; + updateDisplayOrigin(): this; + + defaultPipeline: Phaser.Renderer.WebGL.WebGLPipeline; + pipeline: Phaser.Renderer.WebGL.WebGLPipeline; + pipelineData: object; + initPipeline(pipeline?: string | Phaser.Renderer.WebGL.WebGLPipeline): boolean; + setPipeline(pipeline: string | Phaser.Renderer.WebGL.WebGLPipeline, pipelineData?: object, copyData?: boolean): this; + setPipelineData(key: string, value?: any): this; + resetPipeline(resetData?: boolean): boolean; + getPipelineName(): string; + hasPostPipeline: boolean; + postPipelines: Phaser.Renderer.WebGL.Pipelines.PostFXPipeline[]; + postPipelineData: object; + preFX: Phaser.GameObjects.Components.FX | null; + postFX: Phaser.GameObjects.Components.FX; + initPostPipeline(preFX?: boolean): void; + setPostPipeline(pipelines: string | string[] | Function | Function[] | Phaser.Renderer.WebGL.Pipelines.PostFXPipeline | Phaser.Renderer.WebGL.Pipelines.PostFXPipeline[], pipelineData?: object, copyData?: boolean): this; + setPostPipelineData(key: string, value?: any): this; + getPostPipeline(pipeline: string | Function | Phaser.Renderer.WebGL.Pipelines.PostFXPipeline): Phaser.Renderer.WebGL.Pipelines.PostFXPipeline | Phaser.Renderer.WebGL.Pipelines.PostFXPipeline[]; + resetPostPipeline(resetData?: boolean): void; + removePostPipeline(pipeline: string | Phaser.Renderer.WebGL.Pipelines.PostFXPipeline): this; + clearFX(): this; + + scrollFactorX: number; + scrollFactorY: number; + setScrollFactor(x: number, y?: number): this; + + tintTopLeft: number; + tintTopRight: number; + tintBottomLeft: number; + tintBottomRight: number; + tintFill: boolean; + clearTint(): this; + setTint(topLeft?: number, topRight?: number, bottomLeft?: number, bottomRight?: number): this; + setTintFill(topLeft?: number, topRight?: number, bottomLeft?: number, bottomRight?: number): this; + tint: number; + readonly isTinted: boolean; + + x: number; + y: number; + z: number; + w: number; + scale: number; + scaleX: number; + scaleY: number; + angle: number; + rotation: number; + setPosition(x?: number, y?: number, z?: number, w?: number): this; + copyPosition(source: Phaser.Types.Math.Vector2Like | Phaser.Types.Math.Vector3Like | Phaser.Types.Math.Vector4Like): this; + setRandomPosition(x?: number, y?: number, width?: number, height?: number): this; + setRotation(radians?: number): this; + setAngle(degrees?: number): this; + setScale(x: number, y?: number): this; + setX(value?: number): this; + setY(value?: number): this; + setZ(value?: number): this; + setW(value?: number): this; + getLocalTransformMatrix(tempMatrix?: Phaser.GameObjects.Components.TransformMatrix): Phaser.GameObjects.Components.TransformMatrix; + getWorldTransformMatrix(tempMatrix?: Phaser.GameObjects.Components.TransformMatrix, parentMatrix?: Phaser.GameObjects.Components.TransformMatrix): Phaser.GameObjects.Components.TransformMatrix; + getLocalPoint(x: number, y: number, point?: Phaser.Math.Vector2, camera?: Phaser.Cameras.Scene2D.Camera): Phaser.Math.Vector2; + getParentRotation(): number; + + visible: boolean; + setVisible(value: boolean): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/webfontloader/webfontloader.js b/ui/src/phaser3-rex-plugins/plugins/utils/webfontloader/webfontloader.js new file mode 100644 index 000000000..d1e856a2e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/webfontloader/webfontloader.js @@ -0,0 +1,17 @@ +/* Web Font Loader v1.6.28 - (c) Adobe Systems, Google. License: Apache 2.0 */(function(){function aa(a,b,c){return a.call.apply(a.bind,arguments)}function ba(a,b,c){if(!a)throw Error();if(2=b.f?e():a.fonts.load(fa(b.a),b.h).then(function(a){1<=a.length?d():setTimeout(f,25)},function(){e()})}f()}),e=null,f=new Promise(function(a,d){e=setTimeout(d,b.f)});Promise.race([f,d]).then(function(){e&&(clearTimeout(e),e=null);b.g(b.a)},function(){b.j(b.a)})};function Q(a,b,c,d,e,f,g){this.v=a;this.B=b;this.c=c;this.a=d;this.s=g||"BESbswy";this.f={};this.w=e||3E3;this.u=f||null;this.m=this.j=this.h=this.g=null;this.g=new M(this.c,this.s);this.h=new M(this.c,this.s);this.j=new M(this.c,this.s);this.m=new M(this.c,this.s);a=new G(this.a.c+",serif",J(this.a));a=O(a);this.g.a.style.cssText=a;a=new G(this.a.c+",sans-serif",J(this.a));a=O(a);this.h.a.style.cssText=a;a=new G("serif",J(this.a));a=O(a);this.j.a.style.cssText=a;a=new G("sans-serif",J(this.a));a= +O(a);this.m.a.style.cssText=a;N(this.g);N(this.h);N(this.j);N(this.m)}var R={D:"serif",C:"sans-serif"},S=null;function T(){if(null===S){var a=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent);S=!!a&&(536>parseInt(a[1],10)||536===parseInt(a[1],10)&&11>=parseInt(a[2],10))}return S}Q.prototype.start=function(){this.f.serif=this.j.a.offsetWidth;this.f["sans-serif"]=this.m.a.offsetWidth;this.A=q();U(this)}; +function la(a,b,c){for(var d in R)if(R.hasOwnProperty(d)&&b===a.f[R[d]]&&c===a.f[R[d]])return!0;return!1}function U(a){var b=a.g.a.offsetWidth,c=a.h.a.offsetWidth,d;(d=b===a.f.serif&&c===a.f["sans-serif"])||(d=T()&&la(a,b,c));d?q()-a.A>=a.w?T()&&la(a,b,c)&&(null===a.u||a.u.hasOwnProperty(a.a.c))?V(a,a.v):V(a,a.B):ma(a):V(a,a.v)}function ma(a){setTimeout(p(function(){U(this)},a),50)}function V(a,b){setTimeout(p(function(){v(this.g.a);v(this.h.a);v(this.j.a);v(this.m.a);b(this.a)},a),0)};function W(a,b,c){this.c=a;this.a=b;this.f=0;this.m=this.j=!1;this.s=c}var X=null;W.prototype.g=function(a){var b=this.a;b.g&&w(b.f,[b.a.c("wf",a.c,J(a).toString(),"active")],[b.a.c("wf",a.c,J(a).toString(),"loading"),b.a.c("wf",a.c,J(a).toString(),"inactive")]);K(b,"fontactive",a);this.m=!0;na(this)}; +W.prototype.h=function(a){var b=this.a;if(b.g){var c=y(b.f,b.a.c("wf",a.c,J(a).toString(),"active")),d=[],e=[b.a.c("wf",a.c,J(a).toString(),"loading")];c||d.push(b.a.c("wf",a.c,J(a).toString(),"inactive"));w(b.f,d,e)}K(b,"fontinactive",a);na(this)};function na(a){0==--a.f&&a.j&&(a.m?(a=a.a,a.g&&w(a.f,[a.a.c("wf","active")],[a.a.c("wf","loading"),a.a.c("wf","inactive")]),K(a,"active")):L(a.a))};function oa(a){this.j=a;this.a=new ja;this.h=0;this.f=this.g=!0}oa.prototype.load=function(a){this.c=new ca(this.j,a.context||this.j);this.g=!1!==a.events;this.f=!1!==a.classes;pa(this,new ha(this.c,a),a)}; +function qa(a,b,c,d,e){var f=0==--a.h;(a.f||a.g)&&setTimeout(function(){var a=e||null,m=d||null||{};if(0===c.length&&f)L(b.a);else{b.f+=c.length;f&&(b.j=f);var h,l=[];for(h=0;h ChessSymbol; + + type CreateChessCallbackType = ( + board: Board + ) => Phaser.GameObjects.GameObject; + + type SwapActionType = ( + chess1: Phaser.GameObjects.GameObject, + chess2: Phaser.GameObjects.GameObject, + board: Board, + bejeweled: Bejeweled, + ) => void; + + type EliminatingActionType = ( + chessArray: Phaser.GameObjects.GameObject[], + board: Board, + bejeweled: Bejeweled, + ) => void; + + type FallingActionType = ( + board: Board, + bejeweled: Bejeweled, + ) => void; + + interface IConfig { + rexBoard?: string, + + board: Board.IConfig, + match?: Match.IConfig, + + chess: { + symbols: ChessSymbol[] | GenerateSymbolCallbackType, + + create: CreateChessCallbackType, + + scope?: object, + + moveTo?: MoveTo.IConfig, + + tileZ?: number | string, + }, + + swapAction?: SwapActionType, + + undoSwapAction?: SwapActionType, + + eliminatingAction?: EliminatingActionType, + + fallingAction?: FallingActionType, + + input?: boolean, + + mask?: boolean, + + debug?: boolean, + + } + + namespace Events { + type Select1CallbackType = (board: Board, bejeweled: Bejeweled) => void; + + type Select2CallbackType = (board: Board, bejeweled: Bejeweled) => void; + + type SwapCallbackType = ( + selectedChess1: Phaser.GameObjects.GameObject, + selectedChess2: Phaser.GameObjects.GameObject, + board: Board, bejeweled: Bejeweled + ) => void; + + type MatchStartCallbackType = (board: Board, bejeweled: Bejeweled) => void; + + type MatchCallbackType = ( + lines: Phaser.Structs.Set[], + board: Board, bejeweled: Bejeweled + ) => void; + + type EliminateCallbackType = ( + chessArray: Phaser.GameObjects.GameObject[], + board: Board, bejeweled: Bejeweled + ) => void; + + type FallCallbackType = (board: Board, bejeweled: Bejeweled) => void; + + type FillCallbackType = (board: Board, bejeweled: Bejeweled) => void; + + type MatchEndCallbackType = (board: Board, bejeweled: Bejeweled) => void; + + type UndoSwapCallbackType = ( + selectedChess1: Phaser.GameObjects.GameObject, + selectedChess2: Phaser.GameObjects.GameObject, + board: Board, bejeweled: Bejeweled + ) => void; + + type SetDataCallback = ( + bejeweled: Bejeweled, + key: string, value: any + ) => void; + + type ChangeetAnyDataCallback = ( + bejeweled: Bejeweled, + key: string, value: any, previousValue: any + ) => void; + + type ChangeetDataCallback = ( + bejeweled: Bejeweled, + value: any, previousValue: any + ) => void; + } +} + +declare class Bejeweled extends ComponentBase { + constructor( + scene: Phaser.Scene, + config?: Bejeweled.IConfig + ); + + start(): this; + + setInputEnable(enable?: boolean): this; + + worldXYToChess( + worldX: number, + worldY: number + ): Phaser.GameObjects.GameObject; + + tileXYToChess( + tileX: number, + tileY: number + ): Phaser.GameObjects.GameObject; + + getNeighborChessAtAngle( + chess: Phaser.GameObjects.GameObject | TileXYType, + angle: number + ): Phaser.GameObjects.GameObject; + + getNeighborChessAtDirection( + chess: Phaser.GameObjects.GameObject | TileXYType, + direction: number + ): Phaser.GameObjects.GameObject; + + selectChess1( + chess: Phaser.GameObjects.GameObject + ): this; + getSelectedChess1(): Phaser.GameObjects.GameObject; + + selectChess2( + chess: Phaser.GameObjects.GameObject + ): this; + getSelectedChess2(): Phaser.GameObjects.GameObject; + + getChessMoveTo( + chess: Phaser.GameObjects.GameObject + ): MoveTo | undefined; + + getChessTileZ(): number | string; + + getBoard(): Board; + getMatch(): Match; + + // Custom eliminateChess, falling action + waitEvent( + eventEmitter: Phaser.Events.EventEmitter, + eventName?: string + ): this; + isWaitingEvent(): boolean; + + // Data manager + setDataEnabled(): this; + setData(key: string, value: any): this; + incData(key: string, value: number): this; + toggleData(key: string): this; + getData(key: string): any; + data: Phaser.Data.DataManager; + + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/Bejeweled.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/Bejeweled.js new file mode 100644 index 000000000..84758f7f7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/Bejeweled.js @@ -0,0 +1,82 @@ +import ComponentBase from '../../plugins/utils/componentbase/ComponentBase.js'; +import MainState from './states/MainState.js'; +import Board from './board/Board.js'; +import Input from './input/Input.js'; +import WaitEvents from '../../plugins/waitevents.js'; +import InputMethods from './methods/InputMethods.js'; +import BoardMethods from './methods/BoardMethods.js'; +import WaitEventMethods from './methods/WaitEventMethods.js'; +import DataManagerMethods from '../../plugins/utils/data/DataManagerMethods.js'; + + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Bejeweled extends ComponentBase { + constructor(scene, config) { + super(scene, config); + // this.scene + + var rexBoardKey = GetValue(config, 'rexBoard', 'rexBoard'); + this.rexBoard = scene[rexBoardKey]; + + this.board = new Board(this, config); + + var defaultInput = GetValue(config, 'input', true); + if (defaultInput) { + this.input = new Input(this, config); + } else { + this.input = undefined; + } + + this.waitEvents = new WaitEvents(); + + this.mainState = new MainState(this, config); + + this.boot(); + } + + boot() { + this.scene.events.once('shutdown', this.destroy, this); + } + + shutdown(fromScene) { + super.shutdown(fromScene); + + if (this.input) { + this.input.destroy(); + } + this.board.destroy(); + this.mainState.destroy(); + this.waitEvents.destroy(); + + this.destroyDataManager(); + + this.board = undefined; + this.mainState = undefined; + this.input = undefined; + this.waitEvents = undefined; + + return this; + } + + destroy(fromScene) { + this.emit('destroy'); + super.destroy(fromScene); + return this; + } + + start() { + this.mainState.goto('START'); + return this; + } +} + +Object.assign( + Bejeweled.prototype, + InputMethods, + BoardMethods, + WaitEventMethods, + DataManagerMethods +); + +export default Bejeweled; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/actions/EliminateChess.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/actions/EliminateChess.js new file mode 100644 index 000000000..46e7140c2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/actions/EliminateChess.js @@ -0,0 +1,15 @@ +/* +1. Fade-out-destroy chess +*/ + +import FadeOutDestroy from '../../../plugins/fade-out-destroy.js'; + +var EliminateChess = function (chessArray, board, bejeweled) { + const duration = 500; //ms + for (var i = 0, cnt = chessArray.length; i < cnt; i++) { + var fade = FadeOutDestroy(chessArray[i], duration); + bejeweled.waitEvent(fade, 'complete'); + } +} + +export default EliminateChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/actions/FallingAllChess.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/actions/FallingAllChess.js new file mode 100644 index 000000000..097d9d93c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/actions/FallingAllChess.js @@ -0,0 +1,26 @@ +/* +1. Falling down all chess +*/ + +var FallingAllChess = function (board, bejeweled) { + var tileZ = bejeweled.getChessTileZ(), + chess, moveTo; + + for (var tileY = (board.height - 1); tileY >= 0; tileY--) { // bottom to top + for (var tileX = 0, cnt = board.width; tileX < cnt; tileX++) { // left to right + chess = board.tileXYZToChess(tileX, tileY, tileZ); + if (chess === null) { + continue; + } + moveTo = bejeweled.getChessMoveTo(chess); + do { + moveTo.moveToward(1); + } while (moveTo.lastMoveResult) + if (moveTo.isRunning) { + bejeweled.waitEvent(moveTo, 'complete'); + } + } + } +} + +export default FallingAllChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/actions/SelectChess.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/actions/SelectChess.js new file mode 100644 index 000000000..7e7ea454d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/actions/SelectChess.js @@ -0,0 +1,9 @@ +/* +Do nothing +*/ + +var SelectChess = function (chess, board, bejeweled) { + // Do nothing +} + +export default SelectChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/actions/SwapChess.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/actions/SwapChess.js new file mode 100644 index 000000000..0bfb6c65f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/actions/SwapChess.js @@ -0,0 +1,30 @@ +var SwapChess = function (chess1, chess2, board, bejeweled) { + var tileXYZ1 = board.chessToTileXYZ(chess1); + var tileXYZ2 = board.chessToTileXYZ(chess2); + var tileX1 = tileXYZ1.x, + tileY1 = tileXYZ1.y, + tileX2 = tileXYZ2.x, + tileY2 = tileXYZ2.y, + tileZ = tileXYZ1.z; + + // TileZ of chess1 and chess2 are the same, change tileZ of chess2 to a different value + board.setChessTileZ(chess2, `#${tileZ}`); + + // Move chess1 to tileXYZ2, chess2 to tileXYZ1 + var moveTo1 = bejeweled.getChessMoveTo(chess1); + var moveTo2 = bejeweled.getChessMoveTo(chess2); + moveTo1.moveTo(tileX2, tileY2); + moveTo2.moveTo(tileX1, tileY1); + + // Change tileZ of chess2 back + board.setChessTileZ(chess2, tileZ); + + if (moveTo1.isRunning) { + bejeweled.waitEvent(moveTo1, 'complete'); + } + if (moveTo2.isRunning) { + bejeweled.waitEvent(moveTo2, 'complete'); + } +}; + +export default SwapChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/board/Board.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/Board.js new file mode 100644 index 000000000..5a4d37f20 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/Board.js @@ -0,0 +1,152 @@ +// methods +import Init from './Init.js' +import Reset from './Reset.js'; +import CreateChess from './chess/CreateChess.js'; +import Fill from './Fill.js'; +import BreakMatch3 from './BreakMatch3.js'; +import PreTest from './PreTest.js'; +import GetAllMatch from './match/GetAllMatch.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Board { + constructor(bejeweled, config) { + var scene = bejeweled.scene; + this.scene = scene; + this.rexBoard = bejeweled.rexBoard; + + this.board = this.rexBoard.add.board(GetValue(config, 'board', undefined)); + this.match = this.rexBoard.add.match(GetValue(config, 'match', undefined)); + this.match.setBoard(this.board); + + this.initSymbolsMap = GetValue(config, 'initMap', undefined); // 2d array + // configuration of chess + this.chessTileZ = GetValue(config, 'chess.tileZ', 1); + this.candidateSymbols = GetValue(config, 'chess.symbols', undefined); + this.chessCallbackScope = GetValue(config, 'chess.scope', undefined); + this.chessCreateCallback = GetValue(config, 'chess.create', undefined); + this.chessMoveTo = GetValue(config, 'chess.moveTo', {}); + this.chessMoveTo.occupiedTest = true; + + // Mask & layer + this.rowMaskGameObject = undefined; + this.rowMask = undefined; + this.layer = undefined; + + if (GetValue(config, 'mask', false)) { + this.resetBoardMask(); + } + + if (GetValue(config, 'layer', false)) { + this.enableBoardLayer(); + } + } + + shutdown() { + this.match.destroy(); + this.board.destroy(); + + if (this.rowMaskGameObject) { + this.layer.setMask(); + this.rowMaskGameObject.destroy(); + this.rowMask.destroy(); + } + if (this.layer) { + this.layer.destroy(); + } + + this.board = undefined; + this.match = undefined; + + this.initSymbolsMap = undefined; + this.candidateSymbols = undefined; + this.chessCallbackScope = undefined; + this.chessCreateCallback = undefined; + this.chessMoveTo = undefined; + + return this; + } + + destroy() { + this.shutdown(); + return this; + } + + setBoardWidth(width) { + this.board.setBoardWidth(width); + return this; + } + + setBoardHeight(height) { + this.board.setBoardHeight(height); + return this; + } + + setInitSymbolsMap(map) { + this.initSymbolsMap = map; // 2d array + return this; + } + + enableBoardLayer() { + if (!this.layer) { + this.layer = this.scene.add.layer(); + } + return this; + } + + resetBoardMask() { + if (!this.rowMaskGameObject) { + this.rowMaskGameObject = this.scene.make.graphics().setVisible(false); + this.rowMask = this.rowMaskGameObject.createGeometryMask().setInvertAlpha(); + this.enableBoardLayer(); + this.layer.setMask(this.rowMask); + } + + // Rectangle of upper rows + var board = this.board; + var grid = board.grid; + var x = grid.x - (grid.width / 2); + var y = grid.y - (grid.height / 2); + var width = board.width * grid.width; + var height = (board.height / 2) * grid.height; + this.rowMaskGameObject.fillRect(x, y, width, height); + + return this; + } + + worldXYToChess(worldX, worldY) { + return this.board.worldXYToChess(worldX, worldY, this.chessTileZ); + } + + tileXYToChess(tileX, tileY) { + return this.board.tileXYZToChess(tileX, tileY, this.chessTileZ); + } + + getNeighborChessAtAngle(chess, angle) { + var direction = this.board.angleSnapToDirection(chess, angle); + return this.getNeighborChessAtDirection(chess, direction); + } + + getNeighborChessAtDirection(chess, direction) { + var neighborTileXY = this.board.getNeighborTileXY(chess, direction); + var neighborChess = (neighborTileXY) ? + this.board.tileXYZToChess(neighborTileXY.x, neighborTileXY.y, this.chessTileZ) : + null; + return neighborChess; + } +} + +var methods = { + init: Init, + reset: Reset, + createChess: CreateChess, + fill: Fill, + breakMatch3: BreakMatch3, + preTest: PreTest, + getAllMatch: GetAllMatch, +} +Object.assign( + Board.prototype, + methods +); +export default Board; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/board/BreakMatch3.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/BreakMatch3.js new file mode 100644 index 000000000..f96ba7a09 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/BreakMatch3.js @@ -0,0 +1,38 @@ +/* +1. Pick each match3 line +2. Pick a random chess in this match3 line +3. Change symbol to a different value of all neighbors +*/ + +import RefreshSymbolCache from './match/RefreshSymbolCache.js'; +import GetMatchN from './match/GetMatchN.js'; +import RandomSymbol from './chess/RandomSymobl.js'; + +const GetRandom = Phaser.Utils.Array.GetRandom; + +var BreakMatch3 = function () { + var tileZ = this.chessTileZ, + scope = this.chessCallbackScope, + symbols = this.candidateSymbols; + + RefreshSymbolCache.call(this); // only refresh symbol cache once + GetMatchN.call(this, 3, function (result, board) { + // Pick a random chess in this match3 line + var tileXY = GetRandom(result.tileXY); + var chess = board.tileXYZToChess(tileXY.x, tileXY.y, tileZ); + var neighborChess = board.getNeighborChess(chess, null); + // collect symbols of all neighbors + var excluded = []; + for (var i = 0, cnt = neighborChess.length; i < cnt; i++) { + excluded.push(neighborChess[i].getData('symbol')); + } + var newSymbol = RandomSymbol(board, tileXY.x, tileXY.y, symbols, scope, excluded); + if (newSymbol != null) { + // Change symbol to a different value of all neighbors. + // It also fires 'changedata_symbol' event. + chess.setData('symbol', newSymbol); + } + }); +} + +export default BreakMatch3; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/board/Fill.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/Fill.js new file mode 100644 index 000000000..c9cce6193 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/Fill.js @@ -0,0 +1,36 @@ +/* +1. Fill empty grids +*/ + +var Fill = function (map) { + var upperBoard = false; + if (typeof (map) === 'boolean') { + upperBoard = map; + map = undefined; + } + + var symbol; + var board = this.board, + symbols = this.candidateSymbols; + + var height = this.board.height; + if (upperBoard) { + height /= 2; + } + for (var tileY = 0; tileY < height; tileY++) { + for (var tileX = 0, width = this.board.width; tileX < width; tileX++) { + if (board.contains(tileX, tileY, this.chessTileZ)) { // not empty + continue; + } + + if (map !== undefined) { + symbol = map[tileX][tileY]; + if (symbol !== '?') { + symbols = symbol; + } + } + this.createChess(tileX, tileY, symbols); + } + } +} +export default Fill; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/board/Init.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/Init.js new file mode 100644 index 000000000..65e5491bb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/Init.js @@ -0,0 +1,9 @@ +/* +1. Fill background tiles +*/ +var Init = function () { + // TODO: assign symobls of board via callback + return this; +} + +export default Init; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/board/PreTest.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/PreTest.js new file mode 100644 index 000000000..e2b90040e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/PreTest.js @@ -0,0 +1,47 @@ +/* +1. Test if there has any matched line after chess swapping +*/ + +import RefreshSymbolCache from './match/RefreshSymbolCache.js'; +import AnyMatch from './match/AnyMatch.js'; + +var PreTest = function () { + var match = this.match; + var directions = this.board.grid.halfDirections; + var tileB; + RefreshSymbolCache.call(this); // only refresh symbol cache once + for (var tileY = (this.board.height / 2), rowCnt = this.board.height; tileY < rowCnt; tileY++) { + for (var tileX = 0, colCnt = this.board.width; tileX < colCnt; tileX++) { + tileA.x = tileX; + tileA.y = tileY; + for (var dir = 0, dirCnt = directions.length; dir < dirCnt; dir++) { + tileB = this.board.getNeighborTileXY(tileA, dir); + // swap symbol + swapSymbols(match, tileA, tileB); + // any match? + this.preTestResult = AnyMatch.call(this, 3); + // swap symbol back + swapSymbols(match, tileA, tileB); + + if (this.preTestResult) { + return true; + } + } + } + } + return false; +} + +var swapSymbols = function (match, tileA, tileB) { + var symbolA = match.getSymbol(tileA.x, tileA.y); + var symbolB = match.getSymbol(tileB.x, tileB.y); + match.setSymbol(tileA.x, tileA.y, symbolB); + match.setSymbol(tileB.x, tileB.y, symbolA); +}; + +var tileA = { + x: 0, + y: 0 +}; + +export default PreTest; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/board/Reset.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/Reset.js new file mode 100644 index 000000000..0c6167865 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/Reset.js @@ -0,0 +1,16 @@ +/* +1. Destroy all chess +2. Fill chess +3. Break match3 +*/ + +var Reset = function() { + // Destroy all chess + this.board.removeAllChess(); + // Fill chess (with initial symbol map) + this.fill(this.initSymbolsMap); + // Break match3 + this.breakMatch3(); +} + +export default Reset; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/board/chess/CreateChess.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/chess/CreateChess.js new file mode 100644 index 000000000..f5b974794 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/chess/CreateChess.js @@ -0,0 +1,30 @@ +import RandomSymbol from './RandomSymobl.js'; + +var CreateChess = function (tileX, tileY, symbols) { + var scene = this.scene, + board = this.board, + scope = this.chessCallbackScope; + + // Get symbol + var symbol = RandomSymbol(board, tileX, tileY, symbols, scope); + // Create game object + var gameObject; + if (scope) { + gameObject = this.chessCreateCallback.call(scope, board); + } else { + gameObject = this.chessCreateCallback(board); + } + // Set symbol, it also fires 'changedata_symbol' event + gameObject.setData('symbol', symbol); + // Add to board + board.addChess(gameObject, tileX, tileY, this.chessTileZ, true); + // behaviors + gameObject.rexMoveTo = this.rexBoard.add.moveTo(gameObject, this.chessMoveTo); + + if (this.layer) { + // Move chess gameObject from scene to layer + this.layer.add(gameObject); + } +} + +export default CreateChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/board/chess/RandomSymobl.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/chess/RandomSymobl.js new file mode 100644 index 000000000..cc8acc0db --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/chess/RandomSymobl.js @@ -0,0 +1,38 @@ +const GetRandom = Phaser.Utils.Array.GetRandom; + +var RandomSymbol = function (board, tileX, tileY, callback, scope, excluded) { + var symbol; + if (Array.isArray(callback)) { + // pick random symbol from symbol array + var symbols = callback; + // excluded: undefined or a symbol array + if (excluded !== undefined) { + for (var i = 0, cnt = symbols.length; i < cnt; i++) { + symbol = symbols[i]; + if (excluded.indexOf(symbol) !== -1) { + continue; + } + tmpSymbolArray.push(symbol); + } + symbol = GetRandom(tmpSymbolArray); + tmpSymbolArray.length = 0; + } else { + symbol = GetRandom(symbols); + } + + } else if (typeof (obj) === 'function') { + // symbols from return of callback + if (scope) { + symbol = callback.call(scope, board, tileX, tileY, excluded); + } else { + symbol = callback(board, tileX, tileY, excluded); + } + } else { + // symbol value + symbol = callback; + } + return symbol; +} + +var tmpSymbolArray = []; +export default RandomSymbol; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/AnyMatch.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/AnyMatch.js new file mode 100644 index 000000000..d329e65ab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/AnyMatch.js @@ -0,0 +1,5 @@ +var AnyMatch = function (n) { + return this.match.anyMatch(n); +} + +export default AnyMatch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/GetAllMatch.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/GetAllMatch.js new file mode 100644 index 000000000..14d32fba5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/GetAllMatch.js @@ -0,0 +1,35 @@ +import RefreshSymbolCache from './RefreshSymbolCache.js'; +import GetMatchN from './GetMatchN.js'; + +const SetStruct = Phaser.Structs.Set; +var GetAllMatch = function () { + RefreshSymbolCache.call(this) // only refresh symbol cache once + // Get match5, match4, match3 + var self = this; + var matchLines = []; + for (var n = 5; n >= 3; n--) { + GetMatchN.call(this, n, function (result, board) { + var newSet = new SetStruct(board.tileXYArrayToChessArray(result.tileXY, self.chessTileZ)); + for (var i = 0, cnt = matchLines.length; i < cnt; i++) { + if (subSetTest(matchLines[i], newSet)) { + return; // not a new set + } + } + matchLines.push(newSet); + }); + } + return matchLines; +} + +var subSetTest = function (setA, setB) { + // Return true if setB is a subset of setA + var itemsA = setA.entries; + for (var i = 0, cnt = itemsA.length; i < cnt; i++) { + if (!setB.contains(itemsA[i])) { + return false; + } + } + return true; +}; + +export default GetAllMatch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/GetMatchN.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/GetMatchN.js new file mode 100644 index 000000000..8d608ba8c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/GetMatchN.js @@ -0,0 +1,6 @@ +var GetMatchN = function (n, callback, scope) { + this.match.match(n, callback, scope); + return this; +} + +export default GetMatchN; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/RefreshSymbolCache.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/RefreshSymbolCache.js new file mode 100644 index 000000000..074a2fc4f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/RefreshSymbolCache.js @@ -0,0 +1,15 @@ +var RefreshSymbolCache = function () { + this.match.refreshSymbols(function (tileXY, board) { + // Return null in upper board + if (tileXY.y < (board.height / 2)) { + return null; + } + var chess = board.tileXYZToChess(tileXY.x, tileXY.y, this.chessTileZ); + if (chess == null) { + return null; + } + return chess.getData('symbol'); + }, this); +}; + +export default RefreshSymbolCache; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/input/Input.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/input/Input.js new file mode 100644 index 000000000..ce2f9a8cc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/input/Input.js @@ -0,0 +1,64 @@ +const GetValue = Phaser.Utils.Objects.GetValue; +class Input { + constructor(bejeweled, config) { + this.bejeweled = bejeweled; // Bejeweled + this.scene = bejeweled.scene; // Bejeweled.scene + + this.setEnable(GetValue(config, 'input.enable', true)); + this.boot(); + } + + boot() { + // Touch control + this.scene.input + .on('pointerdown', this.selectChess1, this) + .on('pointermove', this.selectChess2, this); + } + + shutdown() { + this.scene.input + .off('pointerdown', this.selectChess1, this) + .off('pointermove', this.selectChess2, this); + this.bejeweled = undefined; + this.scene = undefined; + } + + destroy() { + this.shutdown(); + return this; + } + + setEnable(enabled) { + if (enabled === undefined) { + enabled = true; + } + this.enable = enabled; + return this; + } + + selectChess1(pointer) { + if (!this.enable) { + return this; + } + var chess = this.bejeweled.worldXYToChess(pointer.worldX, pointer.worldY); + if (chess) { + this.bejeweled.selectChess1(chess); + } + } + + selectChess2(pointer) { + if (!this.enable) { + return this; + } + + if (!pointer.isDown) { + return; + } + var chess = this.bejeweled.worldXYToChess(pointer.worldX, pointer.worldY); + if (chess && (chess !== this.bejeweled.getSelectedChess1())) { + this.bejeweled.selectChess2(chess); + } + } +} + +export default Input; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/methods/BoardMethods.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/methods/BoardMethods.js new file mode 100644 index 000000000..a4d91ec17 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/methods/BoardMethods.js @@ -0,0 +1,41 @@ +export default { + setBoardSize(width, height) { + this.board.setBoardWidth(width).setBoardHeight(height); + return this; + }, + + // Chess properties + getChessMoveTo(chess) { + return (chess) ? chess.rexMoveTo : undefined; + }, + + getChessTileZ() { + return this.board.chessTileZ; + }, + + worldXYToChess(worldX, worldY) { + return this.board.worldXYToChess(worldX, worldY); + }, + + tileXYToChess(tileX, tileY) { + return this.board.tileXYToChess(tileX, tileY); + }, + + getNeighborChessAtAngle(chess, angle) { + return this.board.getNeighborChessAtAngle(chess, angle); + }, + + getNeighborChessAtDirection(chess, direction) { + return this.board.getNeighborChessAtDirection(chess, direction); + }, + + // Expose board instance + getBoard() { + return this.board.board; + }, + + // Expose match instance + getMatch() { + return this.board.match; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/methods/InputMethods.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/methods/InputMethods.js new file mode 100644 index 000000000..498db1d4d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/methods/InputMethods.js @@ -0,0 +1,26 @@ +export default { + getSelectedChess1() { + return this.mainState.selectedChess1; + }, + + getSelectedChess2() { + return this.mainState.selectedChess2; + }, + + selectChess1(chess) { + this.mainState.selectChess1(chess); + return this; + }, + + selectChess2(chess) { + this.mainState.selectChess2(chess); + return this; + }, + + setInputEnable(enable) { + if (this.input) { + this.input.setEnable(enable); + } + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/methods/WaitEventMethods.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/methods/WaitEventMethods.js new file mode 100644 index 000000000..58d5610c8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/methods/WaitEventMethods.js @@ -0,0 +1,13 @@ +export default { + waitEvent(eventEmitter, eventName) { + if (eventName === undefined) { + eventName = 'complete'; + } + this.waitEvents.waitEvent(eventEmitter, eventName); + return this; + }, + + isWaitingEvent() { + return !this.waitEvents.noWaitEvent; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/states/BaseState.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/states/BaseState.js new file mode 100644 index 000000000..80a126293 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/states/BaseState.js @@ -0,0 +1,36 @@ +import FSM from '../../../plugins/fsm.js'; + +class BaseState extends FSM { + constructor(bejeweled, config) { + super(config); + + this.bejeweled = bejeweled; // Bejeweled + this.board = bejeweled.board; // Bejeweled.board + this.waitEvents = bejeweled.waitEvents; // Bejeweled.waitEvents + } + + shutdown() { + super.shutdown(); + this.bejeweled = undefined; + this.board = undefined; + this.waitEvents = undefined; + } + + destroy() { + this.shutdown(); + return this; + } + + next() { + // Wait until all events are completed + if (this.waitEvents.noWaitEvent) { + // Go to next state + super.next(); + } else { + // Try again later + this.waitEvents.setCompleteCallback(this.next, this); + } + } +} + +export default BaseState \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/states/MainState.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/states/MainState.js new file mode 100644 index 000000000..9d094e9ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/states/MainState.js @@ -0,0 +1,222 @@ +import BaseState from './BaseState.js'; +import MatchState from './MatchState.js'; +// Actions +import SelectChess from '../actions/SelectChess.js'; +import SwapChess from '../actions/SwapChess.js' + +const GetValue = Phaser.Utils.Objects.GetValue; + +class State extends BaseState { + constructor(bejeweled, config) { + super(bejeweled, config); + // this.bejeweled = bejeweled; // Bejeweled + // this.board = bejeweled.board; // Bejeweled.board + + this.selectedChess1; + this.selectedChess2; + this.matchState = new MatchState(bejeweled, config); // sub-state + + // Actions + // select1 action + this.select1Action = GetValue(config, 'select1Action', SelectChess); + // select2 action + this.select2Action = GetValue(config, 'select2Action', this.select1Action); + // Swap action + this.swapAction = GetValue(config, 'swapAction', SwapChess); + // UndoSwap action + this.undoSwapAction = GetValue(config, 'undoSwapAction', this.swapAction); + + var debug = GetValue(config, 'debug', false); + if (debug) { + this.on('statechange', this.printState, this); + } + } + + shutdown() { + super.shutdown(); + + this.matchState.shutdown(); + + this.matchState = undefined; + this.selectedChess1 = undefined; + this.selectedChess2 = undefined; + return this; + } + + // START + enter_START() { + this.board.init(); // Fill background tiles + this.next(); + } + next_START() { + return 'RESET'; + } + // START + + // RESET + enter_RESET() { + this.board.reset(); // Refill chess + this.next(); + } + next_RESET() { + return 'PRETEST'; + } + // RESET + + + // PRETEST + enter_PRETEST() { + this.next(); + } + next_PRETEST() { + var nextState; + if (this.board.preTest()) { + nextState = 'SELECT1START'; + } else { + nextState = 'RESET'; + } + return nextState; + } + // PRETEST + + // SELECT1START + enter_SELECT1() { + this.selectedChess1 = undefined; + this.selectedChess2 = undefined; + + this.bejeweled.emit('select1-start', this.board.board, this.bejeweled); + } + selectChess1(chess) { + if (this.state === 'SELECT1START') { + this.selectedChess1 = chess; + this.next(); + } + return this; + } + next_SELECT1START() { + var nextState; + if (this.selectedChess1) { + nextState = 'SELECT1'; + } + return nextState; + } + // SELECT1START + + // SELECT1 + enter_SELECT1() { + var board = this.board.board, + chess = this.selectedChess1; + + this.bejeweled.emit('select1', chess, board, this.bejeweled); + + this.select1Action(chess, board, this.bejeweled); + + // To next state when all completed + this.next(); + } + next_SELECT1() { + return 'SELECT2START'; + } + // SELECT1 + + // SELECT2START + enter_SELECT2START() { + this.bejeweled.emit('select2-start', this.board.board, this.bejeweled); + } + selectChess2(chess) { + if (this.state === 'SELECT2START') { + this.selectedChess2 = chess; + this.next(); + } + return this; + } + next_SELECT2START() { + var nextState; + if (this.selectedChess2 && + this.board.board.areNeighbors(this.selectedChess1, this.selectedChess2)) { + nextState = 'SELECT2'; + } else { + nextState = 'SELECT1START'; + } + return nextState; + } + // SELECT2START + + // SELECT2 + enter_SELECT2() { + var board = this.board.board, + chess = this.selectedChess2; + + this.bejeweled.emit('select2', chess, board, this.bejeweled); + + this.select2Action(chess, board, this.bejeweled); + + // To next state when all completed + this.next(); + } + next_SELECT2() { + return 'SWAP'; + } + // SELECT2 + + // SWAP + enter_SWAP() { + var board = this.board.board, + chess1 = this.selectedChess1, + chess2 = this.selectedChess2; + + this.bejeweled.emit('swap', chess1, chess2, board, this.bejeweled); + + this.swapAction(chess1, chess2, board, this.bejeweled); + + // To next state when all completed + this.next(); + } + next_SWAP() { + return 'MATCH3'; + } + // SWAP + + // MATCH3 + enter_MATCH3() { + this.matchState + .once('complete', this.next, this) + .goto('START'); + } + next_MATCH3() { + var nextState; + if (this.matchState.totalMatchedLinesCount === 0) { + nextState = 'UNDOSWAP'; + } else { + nextState = 'PRETEST'; + } + return nextState; + } + // MATCH3 + + // UNDO_SWAP + enter_UNDOSWAP() { + var board = this.board.board, + chess1 = this.selectedChess1, + chess2 = this.selectedChess2; + + this.bejeweled.emit('undo-swap', chess1, chess2, board, this.bejeweled); + + this.undoSwapAction(chess1, chess2, board, this.bejeweled); + + // To next state when all completed + this.next(); + } + next_UNDOSWAP() { + return 'SELECT1START'; + } + // UNDO_SWAP + + // debug + printState() { + console.log('Main state: ' + this.prevState + ' -> ' + this.state); + } + +} + +export default State; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/bejeweled/states/MatchState.js b/ui/src/phaser3-rex-plugins/templates/bejeweled/states/MatchState.js new file mode 100644 index 000000000..9ebd32a31 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/bejeweled/states/MatchState.js @@ -0,0 +1,160 @@ +import BaseState from './BaseState.js'; +import EliminateChess from '../actions/EliminateChess.js'; +import FallingAllChess from '../actions/FallingAllChess.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const SetStruct = Phaser.Structs.Set; + +class State extends BaseState { + constructor(bejeweled, config) { + super(bejeweled, config); + // this.bejeweled = bejeweled; // Bejeweled + // this.board = bejeweled.board; // Bejeweled.board + + this.totalMatchedLinesCount = 0; + this.eliminatedChessArray; + + // Actions + // Eliminating action + this.eliminatingAction = GetValue(config, 'eliminatingAction', EliminateChess); + // on falling chess + this.fallingAction = GetValue(config, 'fallingAction', FallingAllChess); + + var debug = GetValue(config, 'debug', false); + if (debug) { + this.on('statechange', this.printState, this); + } + } + + shutdown() { + super.shutdown(); + + this.eliminatedChessArray = undefined; + // Actions + this.eliminatingAction = undefined; + this.fallingAction = undefined; + return this; + } + + destroy() { + this.shutdown(); + return this; + } + + // START + enter_START() { + this.totalMatchedLinesCount = 0; + + this.bejeweled.emit('match-start', this.board.board, this.bejeweled); + + this.next(); + } + next_START() { + return 'MATCH3'; + } + // START + + // MATCH3 + enter_MATCH3() { + var matchedLines = this.board.getAllMatch(); + + this.bejeweled.emit('match', matchedLines, this.board.board, this.bejeweled); + + var matchedLinesCount = matchedLines.length; + this.totalMatchedLinesCount += matchedLinesCount; + switch (matchedLinesCount) { + case 0: + this.eliminatedChessArray = []; + break; + case 1: + this.eliminatedChessArray = matchedLines[0].entries; + break; + default: + // Put all chess to a set + var newSet = new SetStruct(); + for (var i = 0; i < matchedLinesCount; i++) { + matchedLines[i].entries.forEach(function (value) { + newSet.set(value); + }); + } + this.eliminatedChessArray = newSet.entries; + break; + } + this.next(); + } + next_MATCH3() { + var nextState; + if (this.eliminatedChessArray.length === 0) { + nextState = 'END' + } else { + nextState = 'ELIMINATING'; + } + return nextState; + } + // MATCH3 + + // ELIMINATING + enter_ELIMINATING() { + var board = this.board.board, + chessArray = this.eliminatedChessArray; + + this.bejeweled.emit('eliminate', chessArray, board, this.bejeweled); + + this.eliminatingAction(chessArray, board, this.bejeweled); + + // Remove eliminated chess + chessArray.forEach(board.removeChess, board); + + // To next state when all completed + this.next(); + } + next_ELIMINATING() { + return 'FALLING'; + } + exit_ELIMINATING() { + this.eliminatedChessArray = undefined; + } + // ELIMINATING + + // FALLING + enter_FALLING() { + var board = this.board.board; + + this.bejeweled.emit('fall', board, this.bejeweled); + + this.fallingAction(board, this.bejeweled); + + // To next state when all completed + this.next(); + } + next_FALLING() { + return 'FILL'; + } + // FALLING + + // FILL + enter_FILL() { + this.board.fill(true); // Fill upper board only + + this.bejeweled.emit('fill', this.board.board, this.bejeweled); + + this.next(); + } + next_FILL() { + return 'MATCH3'; + } + // FILL + + // END + enter_END() { + this.bejeweled.emit('match-end', this.board.board, this.bejeweled); + + this.emit('complete'); + } + // END + + printState() { + console.log('Match state: ' + this.prevState + ' -> ' + this.state); + } +} +export default State; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/dialog-quest/DataMethods.js b/ui/src/phaser3-rex-plugins/templates/dialog-quest/DataMethods.js new file mode 100644 index 000000000..48e81fbd7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/dialog-quest/DataMethods.js @@ -0,0 +1,25 @@ +export default { + getData(key, defaultValue) { + return this.questionManager.getData(key, defaultValue); + }, + + setData(key, value) { + this.questionManager.setData(key, value); + return this; + }, + + incData(key, inc, defaultValue) { + this.questionManager.incData(key, inc, defaultValue); + return this; + }, + + mulData(key, mul, defaultValue) { + this.questionManager.mulData(key, mul, defaultValue); + return this; + }, + + clearData() { + this.questionManager.clearData(); + return this; + }, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/dialog-quest/DialogQuest.d.ts b/ui/src/phaser3-rex-plugins/templates/dialog-quest/DialogQuest.d.ts new file mode 100644 index 000000000..be65378dd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/dialog-quest/DialogQuest.d.ts @@ -0,0 +1,74 @@ +import { Dialog } from '../ui/ui-components'; +import QuestManager from '../../plugins/quest' + +export default DialogQuest; + +declare namespace DialogQuest { + interface IConfig extends QuestManager.IConfig { + dialog: Dialog, + } + + namespace Events { + type UpdateChoiceCallbackType = ( + choice: Phaser.GameObjects.GameObject, + option: QuestManager.QuestionType, + quest: QuestManager.Quest + ) => void; + + type UpdateDialogCallbackType = ( + dialog: Dialog, + question: QuestManager.QuestionType, + quest: QuestManager.Quest + ) => void; + + type ClickChoiceCallbackType = ( + choice: Phaser.GameObjects.GameObject, + dialog: Dialog, + quest: QuestManager.Quest + ) => void; + + type ClickActionCallbackType = ( + action: Phaser.GameObjects.GameObject, + dialog: Dialog, + quest: QuestManager.Quest + ) => void; + } +} + +declare class DialogQuest extends Phaser.Events.EventEmitter { + constructor( + config?: DialogQuest.IConfig + ); + + start(): this; + + next(key?: string): this; + + isLast(): boolean; + + getData( + key: string, + defaultValue?: any + ): any; + + getData(): any[]; + + setData( + key: string, + value: any + ): this; + + incData( + key: string, + inc: number, + defaultValue?: number + ): this; + + mulData( + key: string, + mul: number, + defaultValue?: number + ): this; + + clearData(): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/dialog-quest/DialogQuest.js b/ui/src/phaser3-rex-plugins/templates/dialog-quest/DialogQuest.js new file mode 100644 index 000000000..9baab5b49 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/dialog-quest/DialogQuest.js @@ -0,0 +1,54 @@ +import QuestionManager from '../../plugins/logic/quest/questions/QuestionManager.js'; +import QuestMethods from './QuestMethods.js'; +import DataMethods from './DataMethods.js'; + +const EE = Phaser.Events.EventEmitter; +const GetValue = Phaser.Utils.Objects.GetValue; + +class DialogQuest extends EE { + constructor(config) { + super(); + + if (config === undefined) { + config = {}; + } + if (!config.quest) { + config.quest = true; + } + + this.dialog = GetValue(config, 'dialog', undefined); + this.questionManager = new QuestionManager(config); + + // Attach events + this.questionManager + .on('quest', function (question) { + var choices = this.dialog.getElement('choices'); + var options = question.options, option; + for (var i = 0, cnt = choices.length; i < cnt; i++) { + option = options[i]; + if (option) { + this.dialog.showChoice(i); + this.emit('update-choice', choices[i], option, this); + } else { + this.dialog.hideChoice(i); + } + } + this.emit('update-dialog', this.dialog, question, this); + }, this); + + this.dialog + .on('button.click', function (button, groupName, index) { + var eventName = 'click-' + ((groupName === 'choices') ? 'choice' : 'action'); + this.emit(eventName, button, this.dialog, this); + }, this) + } +} + +Object.assign( + DialogQuest.prototype, + QuestMethods, + DataMethods +); + + +export default DialogQuest; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/dialog-quest/QuestMethods.js b/ui/src/phaser3-rex-plugins/templates/dialog-quest/QuestMethods.js new file mode 100644 index 000000000..90feacdc5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/dialog-quest/QuestMethods.js @@ -0,0 +1,18 @@ +export default { + start(key) { + this.questionManager + .restartQuest() + .getNextQuestion(key); + return this; + }, + + next(key) { + this.questionManager + .getNextQuestion(key); + return this; + }, + + isLast() { + return this.questionManager.isLastQuestion(); + }, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/ObjectFactory.js b/ui/src/phaser3-rex-plugins/templates/spinner/ObjectFactory.js new file mode 100644 index 000000000..56a37cdc6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/ObjectFactory.js @@ -0,0 +1,20 @@ +class ObjectFactory { + constructor(scene) { + this.scene = scene; + this.displayList = scene.sys.displayList; + this.updateList = scene.sys.updateList; + + scene.events.once('destroy', this.destroy, this); + } + + destroy() { + this.scene = null; + this.displayList = null; + this.updateList = null; + } + + static register(type, callback) { + ObjectFactory.prototype[type] = callback; + } +}; +export default ObjectFactory; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/audio/Audio.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/audio/Audio.d.ts new file mode 100644 index 000000000..a487b578d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/audio/Audio.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Audio extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/audio/Audio.js b/ui/src/phaser3-rex-plugins/templates/spinner/audio/Audio.js new file mode 100644 index 000000000..1567f4059 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/audio/Audio.js @@ -0,0 +1,61 @@ +import Base from '../base/Base.js'; +import { Line } from '../utils/Geoms.js'; + +const Linear = Phaser.Math.Linear; + +class Audio extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerAudio'; + } + + buildShapes() { + for (var i = 0; i < 4; i++) { + this.addShape(new Line()); + } + this.prevValue = undefined; + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var leftBound = centerX - radius; + var bottomBound = centerY + radius; + var maxLineHeight = radius * 2; + + var shapes = this.getShapes(), + cnt = shapes.length; + var cellWidth = (radius * 2) / cnt; + var lineWidth = cellWidth * 0.7; + + // Reset range of value + if ((this.prevValue === undefined) || (this.prevValue > this.value)) { + for (var i = 0; i < cnt; i++) { + var line = shapes[i]; + var from = (this.prevValue === undefined) ? Math.random() : line.getData('to'); + line + .setData('from', from) + .setData('to', Math.random()) + } + } + this.prevValue = this.value; + + for (var i = 0; i < cnt; i++) { + var line = shapes[i]; + var from = line.getData('from'), + to = line.getData('to'), + current = Linear(from, to, this.value); + var lineHeight = current * maxLineHeight; + var x = leftBound + (cellWidth * (i + 0.5)); + + line + .lineStyle(lineWidth, this.color, 1) + .setP0(x, bottomBound) + .setP1(x, (bottomBound - lineHeight)); + + } + } +} + +export default Audio; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/audio/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/audio/Factory.d.ts new file mode 100644 index 000000000..2d960987c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/audio/Factory.d.ts @@ -0,0 +1,6 @@ +import Audio from './Audio'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Audio; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/audio/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/audio/Factory.js new file mode 100644 index 000000000..1d68b7eda --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/audio/Factory.js @@ -0,0 +1,13 @@ +import Audio from './Audio.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('audio', function (config) { + var gameObject = new Audio(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Audio', Audio); + +export default Audio; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/ball/Ball.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/ball/Ball.d.ts new file mode 100644 index 000000000..2e1d6076c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/ball/Ball.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Ball extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/ball/Ball.js b/ui/src/phaser3-rex-plugins/templates/spinner/ball/Ball.js new file mode 100644 index 000000000..1029e82aa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/ball/Ball.js @@ -0,0 +1,45 @@ +import Base from '../base/Base.js'; +import { Circle } from '../utils/Geoms.js' +import Yoyo from '../utils/Yoyo.js'; + +const Linear = Phaser.Math.Linear; + +class Ball extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerBall'; + } + + buildShapes() { + for (var i = 0; i < 3; i++) { + this.addShape(new Circle()); + } + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var ballRadius = radius * 0.1; + var lineWidth = Math.ceil(ballRadius * 0.25); + + var t = 1 - Yoyo(this.value); + var trackRadius = Linear(0.3, 0.9, t) * radius; + + var shapes = this.getShapes(); + for (var i = 0, cnt = shapes.length; i < cnt; i++) { + var ball = shapes[i]; + var t = (this.value + (i / cnt)) % 1; + var angle = Math.PI * 2 * t; + ball + .lineStyle(lineWidth, this.color) + .setRadius(ballRadius) + .setCenterPosition( + centerX + Math.cos(angle) * trackRadius, + centerY + Math.sin(angle) * trackRadius + ); + } + } +} + +export default Ball; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/ball/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/ball/Factory.d.ts new file mode 100644 index 000000000..bea8880b9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/ball/Factory.d.ts @@ -0,0 +1,6 @@ +import Ball from './Ball'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Ball; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/ball/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/ball/Factory.js new file mode 100644 index 000000000..7f85ebb12 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/ball/Factory.js @@ -0,0 +1,13 @@ +import Ball from './Ball.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('ball', function (config) { + var gameObject = new Ball(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Ball', Ball); + +export default Ball; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/bars/Bars.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/bars/Bars.d.ts new file mode 100644 index 000000000..7e276c5ce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/bars/Bars.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Bars extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/bars/Bars.js b/ui/src/phaser3-rex-plugins/templates/spinner/bars/Bars.js new file mode 100644 index 000000000..871654a3e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/bars/Bars.js @@ -0,0 +1,54 @@ +import Base from '../base/Base.js'; +import { Line } from '../utils/Geoms.js'; +import Yoyo from '../utils/Yoyo.js'; + +const Linear = Phaser.Math.Linear; +const ExpoIn = Phaser.Math.Easing.Expo.In; + +class Bars extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerBars'; + } + + buildShapes() { + var cnt = 5; + for (var i = 0; i < cnt; i++) { + var line = new Line(); + this.addShape(line); + var offset = Yoyo(i / (cnt - 1)) / 2; + line.setData('offset', offset); + } + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var leftBound = centerX - radius; + var maxLineHeight = radius * 2; + + var shapes = this.getShapes(), + cnt = shapes.length; + var cellWidth = (radius * 2) / cnt; + var lineWidth = cellWidth * 0.7; + + + for (var i = 0; i < cnt; i++) { + var line = shapes[i]; + var t = (this.value + line.getData('offset')) % 1; + t = ExpoIn(Yoyo(t)); + + var lineHeight = Linear(0.4, 1, t) * maxLineHeight; + var x = leftBound + (cellWidth * (i + 0.5)) + + line + .lineStyle(lineWidth, this.color, 1) + .setP0(x, (centerY - (lineHeight / 2))) + .setP1(x, (centerY + (lineHeight / 2))); + + } + } +} + +export default Bars; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/bars/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/bars/Factory.d.ts new file mode 100644 index 000000000..46db880bc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/bars/Factory.d.ts @@ -0,0 +1,6 @@ +import Bars from './Bars'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Bars; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/bars/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/bars/Factory.js new file mode 100644 index 000000000..3e78250a7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/bars/Factory.js @@ -0,0 +1,13 @@ +import Bars from './Bars.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('bars', function (config) { + var gameObject = new Bars(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Bars', Bars); + +export default Bars; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/base/Base.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/base/Base.d.ts new file mode 100644 index 000000000..816e8c28c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/base/Base.d.ts @@ -0,0 +1,48 @@ +// import * as Phaser from 'phaser'; +import BaseShape from '../../../plugins/gameobjects/shape/shapes/BaseShapes'; + +export default Base; + +declare namespace Base { + + interface IConfig { + x?: number, y?: number, + width?: number, height?: number, + color?: number, + + duration?: number, + start?: boolean, + + ease?: string, + } + +} + +declare class Base extends BaseShape { + constructor( + scene: Phaser.Scene, + config?: Base.IConfig + ) + + start(duration?: number): this; + pause(): this; + resume(): this; + stop(): this; + readonly isRunning: boolean; + + setValue(t: number): this; + value: number; + + setColor(color: number): this; + color: number; + + setDuration(duration: number): this; + duration: this; + + setEase(ease: string): this; + ease: string; + + readonly centerX: number; + readonly centerY: number; + readonly radius: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/base/Base.js b/ui/src/phaser3-rex-plugins/templates/spinner/base/Base.js new file mode 100644 index 000000000..793181367 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/base/Base.js @@ -0,0 +1,112 @@ +import BaseShapes from '../../../plugins/gameobjects/shape/shapes/BaseShapes.js'; +import EaseValueMethods from './EaseValueMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Base extends BaseShapes { + constructor(scene, config) { + var x = GetValue(config, 'x', 0); + var y = GetValue(config, 'y', 0); + var width = GetValue(config, 'width', 64); + var height = GetValue(config, 'height', 64); + + super(scene, x, y, width, height); + + this.setDuration(GetValue(config, 'duration', 1000)); + this.setEase(GetValue(config, 'ease', 'Linear')); + this.setDelay(GetValue(config, 'delay', 0)); + this.setRepeatDelay(GetValue(config, 'repeatDelay', 0)); + var color = GetValue(config, 'color', 0xffffff); + var start = GetValue(config, 'start', true); + + this.buildShapes(config); + this.setColor(color); + this.setValue(0); + + if (start) { + this.start(); + } + } + + buildShapes() { + + } + + get centerX() { + return this.width / 2;; + } + + get centerY() { + return this.height / 2; + } + + get radius() { + return Math.min(this.centerX, this.centerY); + } + + get color() { + return this._color; + } + + set color(value) { + this.isColorChanged = this.isColorChanged || (this._color !== value); + this.dirty = this.dirty || this.isColorChanged; + this._color = value; + this.setShapesColor(value); + } + + setColor(color) { + this.color = color; + return this; + } + + setShapesColor(color) { + + } + + get value() { + return this._value; + } + + set value(value) { + value = Phaser.Math.Clamp(value, 0, 1); + this.dirty = this.dirty || (this._value != value); + this._value = value; + } + + setValue(value) { + this.value = value; + return this; + } + + setDuration(duration) { + this.duration = duration; + return this; + } + + setDelay(delay) { + this.delay = delay; + return this; + } + + setRepeatDelay(repeatDelay) { + this.repeatDelay = repeatDelay; + return this; + } + + setEase(ease) { + this.ease = ease; + return this; + } + + get isRunning() { + return (this.tweenTask) ? this.tweenTask.isRunning : false; + } +} + +Object.assign( + Base.prototype, + EaseValueMethods +); + +export default Base; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/base/EaseValueMethods.js b/ui/src/phaser3-rex-plugins/templates/spinner/base/EaseValueMethods.js new file mode 100644 index 000000000..b6ce2bd3e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/base/EaseValueMethods.js @@ -0,0 +1,67 @@ +import EaseValueTask from '../../../plugins/utils/ease/EaseValueTask.js'; + +var Start = function (duration) { + if (!this.easeValueTask) { + this.easeValueTask = new EaseValueTask(this, { eventEmitter: null }); + } + + if (duration !== undefined) { + this.duration = duration; + this.easeValueTask.stop(); // Will restart with new duration + } + + // Won't restart if easeValueTask is running + if (this.easeValueTask.isRunning) { + return this; + } + + // Start easeValueTask + this.easeValueTask.restart({ + key: 'value', + from: 0, to: 1, + duration: this.duration, + ease: this.ease, + repeat: -1, // -1: infinity + + delay: this.delay, + repeatDelay: this.repeatDelay + }); + + this.setDirty(); + + return this; +} + +var Stop = function () { + if (!this.easeValueTask) { + return this; + } + this.easeValueTask.stop(); + this.setDirty(); + return this; +} + +var Pause = function () { + if (!this.easeValueTask) { + return this; + } + this.easeValueTask.pause(); + this.setDirty(); + return this; +} + +var Resume = function () { + if (!this.easeValueTask) { + return this; + } + this.easeValueTask.pause(); + this.setDirty(); + return this; +} + +export default { + start: Start, + stop: Stop, + pause: Pause, + resume: Resume +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/box/Box.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/box/Box.d.ts new file mode 100644 index 000000000..878fd1b45 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/box/Box.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Box extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/box/Box.js b/ui/src/phaser3-rex-plugins/templates/spinner/box/Box.js new file mode 100644 index 000000000..12bd96f61 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/box/Box.js @@ -0,0 +1,50 @@ +import Base from '../base/Base.js'; +import { Lines } from '../utils/Geoms.js'; + +class Box extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerCube'; + } + + buildShapes() { + this.addShape((new Lines()).setName('border')); + this.addShape((new Lines()).setName('fill')); + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + + var halfWidth = radius * 0.7; + var left = centerX - halfWidth, + top = centerY - halfWidth, + width = halfWidth * 2; + + this.getShape('border') + .lineStyle(2, this.color, 1) + .startAt(left, top).lineTo(width, 0, true) + .lineTo(0, width, true).lineTo(-width, 0, true) + .lineTo(0, -width, true).close(); + + if (this.value < 0.5) { + var t = (0.5 - this.value) * 2; + var height = width * t; + this.getShape('fill') + .fillStyle(this.color, 1) + .startAt(left, top).lineTo(width, 0, true) + .lineTo(0, height, true).lineTo(-width, 0, true) + .lineTo(0, -height, true).close(); + + } else { // Rotate + var t = (this.value - 0.5) * 2; + var angle = 180 * t; + + this.getShape('border').rotateAround(centerX, centerY, angle); + this.getShape('fill').fillStyle().lineStyle(); + } + } +} + +export default Box; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/box/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/box/Factory.d.ts new file mode 100644 index 000000000..6282ec6e9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/box/Factory.d.ts @@ -0,0 +1,6 @@ +import Box from './Box'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Box; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/box/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/box/Factory.js new file mode 100644 index 000000000..6f8b5c576 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/box/Factory.js @@ -0,0 +1,13 @@ +import Box from './Box.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('box', function (config) { + var gameObject = new Box(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Box', Box); + +export default Box; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/clock/Clock.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/clock/Clock.d.ts new file mode 100644 index 000000000..d85c7816a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/clock/Clock.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Clock extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/clock/Clock.js b/ui/src/phaser3-rex-plugins/templates/spinner/clock/Clock.js new file mode 100644 index 000000000..5905d6567 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/clock/Clock.js @@ -0,0 +1,67 @@ +import Base from '../base/Base.js'; +import { Circle, Line } from '../utils/Geoms.js' + +const RadToDeg = Phaser.Math.RadToDeg; +const WrapDegrees = Phaser.Math.Angle.WrapDegrees; +const WrapRad = Phaser.Math.Angle.Wrap; +const ShortestBetween = Phaser.Math.Angle.ShortestBetween; +const DegToRad = Phaser.Math.DegToRad; +const Rad270 = Phaser.Math.DegToRad(270); + +class Clock extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerClock'; + + this.minuteHandAngle = 0; + this.hourHandAngle = 0; + } + + buildShapes() { + this.addShape((new Circle()).setName('border')); + this.addShape((new Line()).setName('minuteHand')); + this.addShape((new Line()).setName('hourHand')); + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var lineWidth = Math.ceil(radius / 25); + var borderRadius = radius - (lineWidth / 2); + var minuteHandLength = radius * 0.8; + var hourHandLength = radius * 0.5; + + var prevMinuteHandAngle = this.minuteHandAngle; + this.minuteHandAngle = Math.PI * 2 * this.value; + var angle0 = WrapDegrees(RadToDeg(prevMinuteHandAngle)); + var angle1 = WrapDegrees(RadToDeg(this.minuteHandAngle)); + var deltaAngle = ShortestBetween(angle0, angle1); + this.hourHandAngle = WrapRad(this.hourHandAngle + (DegToRad(deltaAngle) / 12)) + + this.getShape('border') + .lineStyle(lineWidth, this.color) + .setRadius(borderRadius) + .setCenterPosition(centerX, centerY); + + var angle = this.minuteHandAngle + Rad270; + this.getShape('minuteHand') + .lineStyle(lineWidth, this.color) + .setP0(centerX, centerY) + .setP1( + centerX + (Math.cos(angle) * minuteHandLength), + centerY + (Math.sin(angle) * minuteHandLength) + ) + + var angle = this.hourHandAngle + Rad270; + this.getShape('hourHand') + .lineStyle(lineWidth, this.color) + .setP0(centerX, centerY) + .setP1( + centerX + (Math.cos(angle) * hourHandLength), + centerY + (Math.sin(angle) * hourHandLength) + ) + } +} + +export default Clock; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/clock/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/clock/Factory.d.ts new file mode 100644 index 000000000..8748cf08e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/clock/Factory.d.ts @@ -0,0 +1,6 @@ +import Clock from './Clock'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Clock; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/clock/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/clock/Factory.js new file mode 100644 index 000000000..fb5e0791b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/clock/Factory.js @@ -0,0 +1,13 @@ +import Clock from './Clock.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('clock', function (config) { + var gameObject = new Clock(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Clock', Clock); + +export default Clock; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/cube/Cube.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/cube/Cube.d.ts new file mode 100644 index 000000000..4f0ad920b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/cube/Cube.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Cube extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/cube/Cube.js b/ui/src/phaser3-rex-plugins/templates/spinner/cube/Cube.js new file mode 100644 index 000000000..5354e3aaa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/cube/Cube.js @@ -0,0 +1,57 @@ +import Base from '../base/Base.js'; +import { Line } from '../utils/Geoms.js'; +import Yoyo from '../utils/Yoyo.js'; + +const Linear = Phaser.Math.Linear; +const ExpoIn = Phaser.Math.Easing.Expo.In; +const RowNum = 2; +const ColNum = 2; + +class Cube extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerCube'; + } + + buildShapes() { + var cnt = RowNum * ColNum; + for (var i = 0; i < cnt; i++) { + var line = new Line(); + this.addShape(line); + } + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var leftBound = centerX - radius; + var topBound = centerY - radius; + var cellWidth = (radius * 2) / ColNum; + var cellHeight = (radius * 2) / RowNum; + + var shapes = this.getShapes(), + cnt = shapes.length; + for (var i = 0; i < cnt; i++) { + var colIdx = (i % ColNum); + var rowIdx = Math.floor(i / RowNum); + var x = leftBound + (cellWidth * (colIdx + 0.5)); + var y = topBound + (cellHeight * (rowIdx + 0.5)); + + var line = shapes[i]; + var t = (this.value + ((cnt - i) * 0.1)) % 1; + t = ExpoIn(Yoyo(t)); + + var lineAlpha = (cnt - i) / cnt; + var lineHeight = Linear(0.7, 1, t) * cellHeight; + var lineWidth = Linear(0.7, 1, t) * cellWidth; + + line + .lineStyle(lineWidth, this.color, lineAlpha) + .setP0(x - (lineHeight / 2), y) + .setP1(x + (lineHeight / 2), y); + } + } +} + +export default Cube; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/cube/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/cube/Factory.d.ts new file mode 100644 index 000000000..906d09335 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/cube/Factory.d.ts @@ -0,0 +1,6 @@ +import Cube from './Cube'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Cube; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/cube/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/cube/Factory.js new file mode 100644 index 000000000..c3450fc3b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/cube/Factory.js @@ -0,0 +1,13 @@ +import Cube from './Cube.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('cube', function (config) { + var gameObject = new Cube(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Cube', Cube); + +export default Cube; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/custom/Custom.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/custom/Custom.d.ts new file mode 100644 index 000000000..ff04aebdf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/custom/Custom.d.ts @@ -0,0 +1,48 @@ +import Base from '../base/Base'; +import * as Geoms from '../../../plugins/gameobjects/shape/shapes/geoms'; + +export default Custom; + +declare namespace Custom { + + type NameTypes = string | string[] | number; + + type Arc = Geoms.Arc; + type Circle = Geoms.Circle; + type Curve = Geoms.Curve; + type Ellipse = Geoms.Ellipse; + type Line = Geoms.Line; + type Lines = Geoms.Lines; + type Rectangle = Geoms.Rectangle; + type RoundRectangle = Geoms.RoundRectangle; + type Triangle = Geoms.Triangle; + type ShapeTypes = Arc | Circle | Curve | Ellipse | + Line | Lines | Rectangle | RoundRectangle | Triangle; + + interface IConfig extends Base.IConfig { + create?: { + arc?: NameTypes, + circle?: NameTypes, + ellipse?: NameTypes, + line?: NameTypes, + lines?: NameTypes, + rectangle?: NameTypes, + triangle?: NameTypes, + } | ((this: Custom) => void); + + update?: (this: Custom) => void; + + type?: string, + } + +} + +declare class Custom extends Base { + constructor( + scene: Phaser.Scene, + config?: Custom.IConfig + ) + + getShape(name: string): Custom.ShapeTypes; + getShapes(): Custom.ShapeTypes[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/custom/Custom.js b/ui/src/phaser3-rex-plugins/templates/spinner/custom/Custom.js new file mode 100644 index 000000000..c872b9330 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/custom/Custom.js @@ -0,0 +1,18 @@ +import Base from '../base/Base.js'; +import ShapesUpdateMethods from '../../../plugins/gameobjects/shape/customshapes/ShapesUpdateMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Custom extends Base { + constructor(scene, config) { + super(scene, config); + this.type = GetValue(config, 'type', 'rexSpinnerCustom'); + } +} + +Object.assign( + Custom.prototype, + ShapesUpdateMethods +); + +export default Custom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/custom/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/custom/Factory.d.ts new file mode 100644 index 000000000..28a06b015 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/custom/Factory.d.ts @@ -0,0 +1,5 @@ +import Custom from './Custom'; + +export default function Factory( + config?: Custom.IConfig +): Custom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/custom/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/custom/Factory.js new file mode 100644 index 000000000..70cf6df87 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/custom/Factory.js @@ -0,0 +1,13 @@ +import Custom from './Custom.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('custom', function (config) { + var gameObject = new Custom(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Custom', Custom); + +export default Custom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/dots/Dots.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/dots/Dots.d.ts new file mode 100644 index 000000000..ce28f829f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/dots/Dots.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Dots extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/dots/Dots.js b/ui/src/phaser3-rex-plugins/templates/spinner/dots/Dots.js new file mode 100644 index 000000000..ee48586a6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/dots/Dots.js @@ -0,0 +1,54 @@ +import Base from '../base/Base.js'; +import { Circle } from '../utils/Geoms.js'; +import Yoyo from '../utils/Yoyo.js'; + + +const Linear = Phaser.Math.Linear; + +class Dots extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerDots'; + } + + buildShapes() { + var cnt = 3; + for (var i = 0; i < cnt; i++) { + var dot = new Circle(); + this.addShape(dot); + + var offset = Yoyo(i / (cnt - 1)) / 2; + dot.setData('offset', offset); + } + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var leftBound = centerX - radius; + + var shapes = this.getShapes(), + cnt = shapes.length; + var cellWidth = (radius * 2) / cnt; + var maxDotRadius = cellWidth / 2; + + for (var i = 0; i < cnt; i++) { + var dot = shapes[i]; + var t = (this.value + dot.getData('offset')) % 1; + t = Yoyo(t); + + var dotAlpha = Linear(0.25, 1, t); + var dotRadius = Linear(0.5, 1, t) * maxDotRadius; + dot + .fillStyle(this.color, dotAlpha) + .setRadius(dotRadius) + .setCenterPosition( + leftBound + (cellWidth * (i + 0.5)), + centerY + ) + } + } +} + +export default Dots; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/dots/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/dots/Factory.d.ts new file mode 100644 index 000000000..b7cd9933e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/dots/Factory.d.ts @@ -0,0 +1,6 @@ +import Dots from './Dots'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Dots; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/dots/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/dots/Factory.js new file mode 100644 index 000000000..ac9a10ec2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/dots/Factory.js @@ -0,0 +1,13 @@ +import Dots from './Dots.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('dots', function (config) { + var gameObject = new Dots(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Dots', Dots); + +export default Dots; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/facebook/Facebook.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/facebook/Facebook.d.ts new file mode 100644 index 000000000..13b7106bc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/facebook/Facebook.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Facebook extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/facebook/Facebook.js b/ui/src/phaser3-rex-plugins/templates/spinner/facebook/Facebook.js new file mode 100644 index 000000000..263408124 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/facebook/Facebook.js @@ -0,0 +1,50 @@ +import Base from '../base/Base.js'; +import { Line } from '../utils/Geoms.js'; +import Yoyo from '../utils/Yoyo.js'; + +const Linear = Phaser.Math.Linear; +const ExpoIn = Phaser.Math.Easing.Expo.In; + +class Facebook extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerFacebook'; + } + + buildShapes() { + for (var i = 0; i < 3; i++) { + var shape = new Line(); + this.addShape(shape); + } + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var leftBound = centerX - radius; + + var shapes = this.getShapes(), + cnt = shapes.length; + var cellWidth = (radius * 2) / cnt; + var cellHeight = radius * 2; + + for (var i = 0; i < cnt; i++) { + var line = shapes[i]; + var t = (this.value + ((cnt - i) * 0.1)) % 1; + t = ExpoIn(Yoyo(t)); + + var lineAlpha = (i + 1) / cnt; + var lineHeight = Linear(0.7, 1, t) * cellHeight; + var lineWidth = Linear(0.7, 1, t) * cellWidth; + var x = leftBound + (cellWidth * (i + 0.5)); + + line + .lineStyle(lineWidth, this.color, lineAlpha) + .setP0(x, centerY - (lineHeight / 2)) + .setP1(x, centerY + (lineHeight / 2)); + } + } +} + +export default Facebook; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/facebook/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/facebook/Factory.d.ts new file mode 100644 index 000000000..f51d5e0eb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/facebook/Factory.d.ts @@ -0,0 +1,6 @@ +import Facebook from './Facebook'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Facebook; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/facebook/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/facebook/Factory.js new file mode 100644 index 000000000..83564c35a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/facebook/Factory.js @@ -0,0 +1,13 @@ +import Facebook from './Facebook.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('facebook', function (config) { + var gameObject = new Facebook(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Facebook', Facebook); + +export default Facebook; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/grid/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/grid/Factory.d.ts new file mode 100644 index 000000000..003176bf2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/grid/Factory.d.ts @@ -0,0 +1,6 @@ +import Grid from './Grid'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Grid; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/grid/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/grid/Factory.js new file mode 100644 index 000000000..37c342597 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/grid/Factory.js @@ -0,0 +1,13 @@ +import Grid from './Grid.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('grid', function (config) { + var gameObject = new Grid(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Grid', Grid); + +export default Grid; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/grid/Grid.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/grid/Grid.d.ts new file mode 100644 index 000000000..e3ed12a39 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/grid/Grid.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Grid extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/grid/Grid.js b/ui/src/phaser3-rex-plugins/templates/spinner/grid/Grid.js new file mode 100644 index 000000000..9690f1146 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/grid/Grid.js @@ -0,0 +1,60 @@ +import Base from '../base/Base.js'; +import { Circle } from '../utils/Geoms.js'; +import Yoyo from '../utils/Yoyo.js'; + + +const Linear = Phaser.Math.Linear; +const RowNum = 3; +const ColNum = 3; + +class Grid extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerGrid'; + } + + buildShapes() { + var cnt = RowNum * ColNum; + for (var i = 0; i < cnt; i++) { + var dot = new Circle(); + this.addShape(dot); + + dot.setData('offset', Math.random()); + } + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var isSizeChanged = this.isSizeChanged; + + var leftBound = centerX - radius; + var topBound = centerY - radius; + var cellWidth = (radius * 2) / ColNum; + var cellHeight = (radius * 2) / RowNum; + var maxDotRadius = (Math.min(cellWidth, cellHeight) / 2) * 0.8; + + + var shapes = this.getShapes(); + for (var i = 0, cnt = shapes.length; i < cnt; i++) { + var colIdx = (i % ColNum); + var rowIdx = Math.floor(i / RowNum); + var x = leftBound + cellWidth * (colIdx + 0.5); + var y = topBound + cellHeight * (rowIdx + 0.5); + + var dot = shapes[i]; + var t = (this.value + dot.getData('offset')) % 1; + t = Yoyo(t); + dot.fillStyle(this.color, Linear(0.25, 1, t)); + + if (isSizeChanged) { + dot + .setRadius(maxDotRadius) + .setCenterPosition(x, y) + } + } + } +} + +export default Grid; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/los/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/los/Factory.d.ts new file mode 100644 index 000000000..7c8335e71 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/los/Factory.d.ts @@ -0,0 +1,6 @@ +import Los from './Los'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Los; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/los/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/los/Factory.js new file mode 100644 index 000000000..92ec48bc7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/los/Factory.js @@ -0,0 +1,13 @@ +import Los from './Los.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('los', function (config) { + var gameObject = new Los(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Los', Los); + +export default Los; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/los/Los.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/los/Los.d.ts new file mode 100644 index 000000000..7cd197f7f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/los/Los.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Los extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/los/Los.js b/ui/src/phaser3-rex-plugins/templates/spinner/los/Los.js new file mode 100644 index 000000000..d498cc6c7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/los/Los.js @@ -0,0 +1,49 @@ +import Base from '../base/Base.js'; +import { Line } from '../utils/Geoms.js' + +const Linear = Phaser.Math.Linear; + +class Los extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerLos'; + } + + buildShapes() { + for (var i = 0; i < 12; i++) { + this.addShape(new Line()); + } + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var isSizeChanged = this.isSizeChanged; + + var radius = this.radius; + var startRadius = radius / 2; + var lineWidth = Math.ceil(radius / 20); + var shapes = this.getShapes(); + for (var i = 0, cnt = shapes.length; i < cnt; i++) { + var line = shapes[i]; + var t = i / cnt; + var angle = Math.PI * 2 * t; + var alpha = Linear(0.25, 1, (1 - this.value + t) % 1); + line.lineStyle(lineWidth, this.color, alpha); + + if (isSizeChanged) { + line + .setP0( + centerX + Math.cos(angle) * startRadius, + centerY + Math.sin(angle) * startRadius + ) + .setP1( + centerX + Math.cos(angle) * radius, + centerY + Math.sin(angle) * radius + ) + } + } + } +} + +export default Los; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/orbit/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/orbit/Factory.d.ts new file mode 100644 index 000000000..aab7ee583 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/orbit/Factory.d.ts @@ -0,0 +1,6 @@ +import Orbit from './Orbit'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Orbit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/orbit/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/orbit/Factory.js new file mode 100644 index 000000000..f182c5c5b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/orbit/Factory.js @@ -0,0 +1,13 @@ +import Orbit from './Orbit.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('orbit', function (config) { + var gameObject = new Orbit(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Orbit', Orbit); + +export default Orbit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/orbit/Orbit.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/orbit/Orbit.d.ts new file mode 100644 index 000000000..32c61a155 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/orbit/Orbit.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Orbit extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/orbit/Orbit.js b/ui/src/phaser3-rex-plugins/templates/spinner/orbit/Orbit.js new file mode 100644 index 000000000..eb02a4333 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/orbit/Orbit.js @@ -0,0 +1,39 @@ +import Base from '../base/Base.js'; +import { Circle } from '../utils/Geoms.js' + +class Orbit extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerOrbit'; + } + + buildShapes() { + this.addShape((new Circle()).setName('track')); + this.addShape((new Circle()).setName('thumb')); + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var trackRadius = radius * 0.9; + var trackThickness = Math.ceil(trackRadius/25); + var thumbRadius = radius * 0.1; + var thumbAngle = Math.PI * 2 * this.value; + + this.getShape('track') + .lineStyle(trackThickness, this.color, 0.7) + .setRadius(trackRadius) + .setCenterPosition(centerX, centerY); + + this.getShape('thumb') + .fillStyle(this.color) + .setRadius(thumbRadius) + .setCenterPosition( + centerX + Math.cos(thumbAngle) * trackRadius, + centerY + Math.sin(thumbAngle) * trackRadius + ); + } +} + +export default Orbit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/oval/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/oval/Factory.d.ts new file mode 100644 index 000000000..50fdb3162 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/oval/Factory.d.ts @@ -0,0 +1,6 @@ +import Oval from './Oval'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Oval; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/oval/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/oval/Factory.js new file mode 100644 index 000000000..9d68b3357 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/oval/Factory.js @@ -0,0 +1,13 @@ +import Oval from './Oval.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('oval', function (config) { + var gameObject = new Oval(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Oval', Oval); + +export default Oval; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/oval/Oval.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/oval/Oval.d.ts new file mode 100644 index 000000000..4ddc61085 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/oval/Oval.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Oval extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/oval/Oval.js b/ui/src/phaser3-rex-plugins/templates/spinner/oval/Oval.js new file mode 100644 index 000000000..81870e200 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/oval/Oval.js @@ -0,0 +1,39 @@ +import Base from '../base/Base.js'; +import { Arc, Circle } from '../utils/Geoms.js' + + +class Oval extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerOval'; + } + + buildShapes() { + this.addShape((new Circle()).setName('track')); + this.addShape((new Arc()).setName('arc')); + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var lineWidth = Math.ceil(radius / 25); + var maxRadius = radius - (lineWidth / 2); + + this.getShape('track') + .lineStyle(lineWidth, this.color, 0.5) + .setRadius(maxRadius) + .setCenterPosition(centerX, centerY); + + var startAngle = this.value * 360; + var endAngle = startAngle + 60; + this.getShape('arc') + .lineStyle(lineWidth, this.color, 1) + .setRadius(maxRadius) + .setCenterPosition(centerX, centerY) + .setAngle(startAngle, endAngle); + + } +} + +export default Oval; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/pie/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/pie/Factory.d.ts new file mode 100644 index 000000000..f7adcad88 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/pie/Factory.d.ts @@ -0,0 +1,6 @@ +import Pie from './Pie'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Pie; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/pie/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/pie/Factory.js new file mode 100644 index 000000000..a5861a1d6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/pie/Factory.js @@ -0,0 +1,13 @@ +import Pie from './Pie.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('pie', function (config) { + var gameObject = new Pie(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Pie', Pie); + +export default Pie; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/pie/Pie.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/pie/Pie.d.ts new file mode 100644 index 000000000..3cc4591ea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/pie/Pie.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Pie extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/pie/Pie.js b/ui/src/phaser3-rex-plugins/templates/spinner/pie/Pie.js new file mode 100644 index 000000000..9a7fc283c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/pie/Pie.js @@ -0,0 +1,68 @@ +import Base from '../base/Base.js'; +import { Arc } from '../utils/Geoms.js'; + +const Linear = Phaser.Math.Linear; + +class Pie extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerPie'; + } + + buildShapes() { + for (var i = 0; i < 4; i++) { + var pie = (new Arc()).setPie(); + this.addShape(pie); + + pie.setData('speed', Linear(180, 360, Math.random())); + } + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + + var deltaValue; + if (this.prevValue !== undefined) { + deltaValue = this.value - this.prevValue; + if (this.prevValue > this.value) { + deltaValue += 1; + } + } + + var shapes = this.getShapes(); + for (var i = 0, cnt = shapes.length; i < cnt; i++) { + var pie = shapes[i]; + var pieAlpha = (i + 1) / cnt; + + if (this.prevValue === undefined) { + var startAngle = (i / cnt) * 360; + var endAngle = startAngle + 90; + pie + .fillStyle(this.color, pieAlpha) + .setRadius(radius) + .setCenterPosition(centerX, centerY) + .setAngle(startAngle, endAngle) + .setData('angle', startAngle); + } else { + var startAngle = pie.getData('angle') + pie.getData('speed') * deltaValue; + startAngle = startAngle % 360; + var endAngle = startAngle + 90; + pie + .fillStyle(this.color, pieAlpha) + .setRadius(radius) + .setCenterPosition(centerX, centerY) + .setAngle(startAngle, endAngle) + .setData('angle', startAngle); + + } + + } + + this.prevValue = this.value; + + } +} + +export default Pie; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/puff/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/puff/Factory.d.ts new file mode 100644 index 000000000..4fb5fe2cd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/puff/Factory.d.ts @@ -0,0 +1,6 @@ +import Puff from './Puff'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Puff; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/puff/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/puff/Factory.js new file mode 100644 index 000000000..fc6c647ed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/puff/Factory.js @@ -0,0 +1,13 @@ +import Puff from './Puff.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('puff', function (config) { + var gameObject = new Puff(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Puff', Puff); + +export default Puff; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/puff/Puff.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/puff/Puff.d.ts new file mode 100644 index 000000000..dfe78f846 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/puff/Puff.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Puff extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/puff/Puff.js b/ui/src/phaser3-rex-plugins/templates/spinner/puff/Puff.js new file mode 100644 index 000000000..268eb9697 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/puff/Puff.js @@ -0,0 +1,31 @@ +import Base from '../base/Base.js'; +import { Circle } from '../utils/Geoms.js'; +import Yoyo from '../utils/Yoyo.js'; + + +class Puff extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerPuff'; + } + + buildShapes() { + this.addShape(new Circle()); + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var puffRadius = radius * this.value; + var lineWidth = Math.ceil(radius / 25); + var alpha = Yoyo(this.value); + + this.getShapes()[0] + .lineStyle(lineWidth, this.color, alpha) + .setRadius(puffRadius) + .setCenterPosition(centerX, centerY) + } +} + +export default Puff; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/radio/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/radio/Factory.d.ts new file mode 100644 index 000000000..37ead6459 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/radio/Factory.d.ts @@ -0,0 +1,6 @@ +import Radio from './Radio'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Radio; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/radio/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/radio/Factory.js new file mode 100644 index 000000000..a6f52cb02 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/radio/Factory.js @@ -0,0 +1,13 @@ +import Radio from './Radio.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('radio', function (config) { + var gameObject = new Radio(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Radio', Radio); + +export default Radio; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/radio/Radio.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/radio/Radio.d.ts new file mode 100644 index 000000000..68294654f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/radio/Radio.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Radio extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/radio/Radio.js b/ui/src/phaser3-rex-plugins/templates/spinner/radio/Radio.js new file mode 100644 index 000000000..af01a3584 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/radio/Radio.js @@ -0,0 +1,82 @@ +import Base from '../base/Base.js'; +import { Circle, Lines } from '../utils/Geoms.js'; +import Yoyo from '../utils/Yoyo.js'; + +const Linear = Phaser.Math.Linear; +const ExpoIn = Phaser.Math.Easing.Expo.In; + +class Radio extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerRadio'; + } + + buildShapes() { + this.addShape((new Circle()).setName('center')); + this.addShape((new Lines()).setName('arc0')); + this.addShape((new Lines()).setName('arc1')); + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var isSizeChanged = this.isSizeChanged; + + var centerRadius = (radius * 2) / 6; + var x = centerX - radius + centerRadius; + var y = centerY + radius - centerRadius; + + var shapes = this.getShapes(); + for (var i = 0, cnt = shapes.length; i < cnt; i++) { + var shape = shapes[i]; + + var t = (this.value + ((cnt - i) * 0.1)) % 1; + t = ExpoIn(Yoyo(t)); + + switch (shape.name) { + case 'center': + shape.fillStyle(this.color, Linear(0.25, 1, t)) + + if (isSizeChanged) { + shape + .setRadius(centerRadius) + .setCenterPosition(x, y); + } + break; + case 'arc0': + shape.fillStyle(this.color, Linear(0.25, 1, t)); + + if (isSizeChanged) { + var radius0 = centerRadius * 2, + radius1 = centerRadius * 3; + shape + .startAt(x, y - radius0) + .lineTo(x, y - radius1) + .setIterations(8).arc(x, y, radius1, 270, 360) + .lineTo(x + radius0, y) + .setIterations(6).arc(x, y, radius0, 360, 270, true) + .close(); + } + break; + case 'arc1': + shape.fillStyle(this.color, Linear(0.25, 1, t)); + + if (isSizeChanged) { + var radius0 = centerRadius * 4, + radius1 = centerRadius * 5; + shape + .startAt(x, y - radius0) + .lineTo(x, y - radius1) + .setIterations(8).arc(x, y, radius1, 270, 360) + .lineTo(x + radius0, y) + .setIterations(6).arc(x, y, radius0, 360, 270, true) + .close(); + } + break; + } + } + } +} + +export default Radio; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/rings/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/rings/Factory.d.ts new file mode 100644 index 000000000..0c2572b63 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/rings/Factory.d.ts @@ -0,0 +1,6 @@ +import Rings from './Rings'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Rings; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/rings/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/rings/Factory.js new file mode 100644 index 000000000..93ffb4d3e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/rings/Factory.js @@ -0,0 +1,13 @@ +import Rings from './Rings.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('rings', function (config) { + var gameObject = new Rings(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Rings', Rings); + +export default Rings; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/rings/Rings.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/rings/Rings.d.ts new file mode 100644 index 000000000..07d49107c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/rings/Rings.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Rings extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/rings/Rings.js b/ui/src/phaser3-rex-plugins/templates/spinner/rings/Rings.js new file mode 100644 index 000000000..5a0707cf2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/rings/Rings.js @@ -0,0 +1,38 @@ +import Base from '../base/Base.js'; +import { Circle } from '../utils/Geoms.js' +import Yoyo from '../utils/Yoyo.js'; + + +class Rings extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerRings'; + } + + buildShapes() { + for (var i = 0; i < 2; i++) { + this.addShape(new Circle()); + } + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var lineWidth = Math.ceil(radius / 25); + var maxRingRadius = radius - lineWidth; + + var shapes = this.getShapes(); + for (var i = 0, cnt = shapes.length; i < cnt; i++) { + var ring = shapes[i]; + var t = (this.value + (i / cnt)) % 1; + var alpha = Yoyo(t); + ring + .lineStyle(lineWidth, this.color, alpha) + .setRadius(t * maxRingRadius) + .setCenterPosition(centerX, centerY) + } + } +} + +export default Rings; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/spinner-components.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/spinner-components.d.ts new file mode 100644 index 000000000..3ca2d056b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/spinner-components.d.ts @@ -0,0 +1,39 @@ +import Audio from './audio/Audio'; +import Ball from './ball/Ball'; +import Bars from './bars/Bars'; +import Box from './box/Box'; +import Clock from './clock/Clock'; +import Cube from './cube/Cube'; +import Custom from './custom/Custom'; +import Dots from './dots/Dots'; +import Facebook from './facebook/Facebook'; +import Grid from './grid/Grid'; +import Los from './los/Los'; +import Orbit from './orbit/Orbit'; +import Oval from './oval/Oval'; +import Pie from './pie/Pie'; +import Puff from './puff/Puff'; +import Radio from './radio/Radio'; +import Rings from './rings/Rings'; +import Spinner from './spinner/Spinner'; + +export { + Audio, + Ball, + Bars, + Box, + Clock, + Cube, + Custom, + Dots, + Facebook, + Grid, + Los, + Orbit, + Oval, + Pie, + Puff, + Radio, + Rings, + Spinner +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/spinner-components.js b/ui/src/phaser3-rex-plugins/templates/spinner/spinner-components.js new file mode 100644 index 000000000..f059f3eb5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/spinner-components.js @@ -0,0 +1,39 @@ +import Audio from './audio/Audio.js'; +import Ball from './ball/Ball.js'; +import Bars from './bars/Bars.js'; +import Box from './box/Box.js'; +import Clock from './clock/Clock.js'; +import Cube from './cube/Cube.js'; +import Custom from './custom/Custom.js'; +import Dots from './dots/Dots.js'; +import Facebook from './facebook/Facebook.js'; +import Grid from './grid/Grid.js'; +import Los from './los/Los.js'; +import Orbit from './orbit/Orbit.js'; +import Oval from './oval/Oval.js'; +import Pie from './pie/Pie.js'; +import Puff from './puff/Puff.js'; +import Radio from './radio/Radio.js'; +import Rings from './rings/Rings.js'; +import Spinner from './spinner/Spinner.js'; + +export { + Audio, + Ball, + Bars, + Box, + Clock, + Cube, + Custom, + Dots, + Facebook, + Grid, + Los, + Orbit, + Oval, + Pie, + Puff, + Radio, + Rings, + Spinner +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/spinner-plugin.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/spinner-plugin.d.ts new file mode 100644 index 000000000..c8a8f3bc1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/spinner-plugin.d.ts @@ -0,0 +1,87 @@ +import AudioFactory from './audio/Factory'; +import BallFactory from './ball/Factory'; +import BarsFactory from './bars/Factory'; +import BoxFactory from './box/Factory'; +import ClockFactory from './clock/Factory'; +import CubeFactory from './cube/Factory'; +import CustomFactory from './custom/Factory'; +import DotsFactory from './dots/Factory'; +import FacebookFactory from './facebook/Factory'; +import GridFactory from './grid/Factory'; +import LosFactory from './los/Factory'; +import OrbitFactory from './orbit/Factory'; +import OvalFactory from './oval/Factory'; +import PieFactory from './pie/Factory'; +import PuffFactory from './puff/Factory'; +import RadioFactory from './radio/Factory'; +import RingsFactory from './rings/Factory'; +import SpinnerFactory from './spinner/Factory'; + +export default SpinnerPlugins; + +declare class Factories { + audio: typeof AudioFactory; + ball: typeof BallFactory; + bars: typeof BarsFactory; + box: typeof BoxFactory; + clock: typeof ClockFactory; + cube: typeof CubeFactory; + custom: typeof CustomFactory; + dots: typeof DotsFactory; + facebook: typeof FacebookFactory; + grid: typeof GridFactory; + los: typeof LosFactory; + orbit: typeof OrbitFactory; + oval: typeof OvalFactory; + pie: typeof PieFactory; + puff: typeof PuffFactory; + radio: typeof RadioFactory; + rings: typeof RingsFactory; + spinner: typeof SpinnerFactory; +} + +declare class SpinnerPlugins { + constructor(scene: Phaser.Scene); + + add: Factories; +} + +import AudioClass from './audio/Audio'; +import BallClass from './ball/Ball'; +import BarsClass from './bars/Bars'; +import BoxClass from './box/Box'; +import ClockClass from './clock/Clock'; +import CubeClass from './cube/Cube'; +import CustomClass from './custom/Custom'; +import DotsClass from './dots/Dots'; +import FacebookClass from './facebook/Facebook'; +import GridClass from './grid/Grid'; +import LosClass from './los/Los'; +import OrbitClass from './orbit/Orbit'; +import OvalClass from './oval/Oval'; +import PieClass from './pie/Pie'; +import PuffClass from './puff/Puff'; +import RadioClass from './radio/Radio'; +import RingsClass from './rings/Rings'; +import SpinnerClass from './spinner/Spinner'; + +declare namespace SpinnerPlugins { + type Audio = AudioClass; + type Ball = BallClass; + type Bars = BarsClass + type Box = BoxClass; + type Clock = ClockClass; + type Cube = CubeClass; + type Custom = CustomClass; + type Dots = DotsClass; + type Facebook = FacebookClass; + type Grid = GridClass; + type Los = LosClass; + type Orbit = OrbitClass; + type Oval = OvalClass; + type Pie = PieClass; + type Puff = PuffClass; + type Radio = RadioClass; + type Rings = RingsClass; + type Spinner = SpinnerClass; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/spinner-plugin.js b/ui/src/phaser3-rex-plugins/templates/spinner/spinner-plugin.js new file mode 100644 index 000000000..df5d2cc99 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/spinner-plugin.js @@ -0,0 +1,35 @@ +import ObjectFactory from './ObjectFactory.js'; + +import AudioFactory from './audio/Factory.js'; +import BallFactory from './ball/Factory.js'; +import BarsFactory from './bars/Factory.js'; +import BoxFactory from './box/Factory.js'; +import ClockFactory from './clock/Factory.js'; +import CubeFactory from './cube/Factory.js'; +import CustomFactory from './custom/Factory.js'; +import DotsFactory from './dots/Factory.js'; +import FacebookFactory from './facebook/Factory.js'; +import GridFactory from './grid/Factory.js'; +import LosFactory from './los/Factory.js'; +import OrbitFactory from './orbit/Factory.js'; +import OvalFactory from './oval/Factory.js'; +import PieFactory from './pie/Factory.js'; +import PuffFactory from './puff/Factory.js'; +import RadioFactory from './radio/Factory.js'; +import RingsFactory from './rings/Factory.js'; +import SpinnerFactory from './spinner/Factory.js'; + + +class SpinnerPlugin extends Phaser.Plugins.ScenePlugin { + constructor(scene, pluginManager) { + super(scene, pluginManager); + + this.add = new ObjectFactory(scene); + } + + start() { + var eventEmitter = this.scene.events; + eventEmitter.on('destroy', this.destroy, this); + } +} +export default SpinnerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/spinner/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/spinner/Factory.d.ts new file mode 100644 index 000000000..73eac30cf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/spinner/Factory.d.ts @@ -0,0 +1,6 @@ +import Spinner from './Spinner'; +import Base from '../base/Base'; + +export default function Factory( + config?: Base.IConfig +): Spinner; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/spinner/Factory.js b/ui/src/phaser3-rex-plugins/templates/spinner/spinner/Factory.js new file mode 100644 index 000000000..88b0621cd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/spinner/Factory.js @@ -0,0 +1,13 @@ +import Spinner from './Spinner.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('spinner', function (config) { + var gameObject = new Spinner(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Spinner.Spinner', Spinner); + +export default Spinner; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/spinner/Spinner.d.ts b/ui/src/phaser3-rex-plugins/templates/spinner/spinner/Spinner.d.ts new file mode 100644 index 000000000..22dfb7c55 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/spinner/Spinner.d.ts @@ -0,0 +1,2 @@ +import Base from '../base/Base'; +export default class Spinner extends Base { } \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/spinner/Spinner.js b/ui/src/phaser3-rex-plugins/templates/spinner/spinner/Spinner.js new file mode 100644 index 000000000..5227a4bd7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/spinner/Spinner.js @@ -0,0 +1,34 @@ +import Base from '../base/Base.js'; +import { Arc } from '../utils/Geoms.js' +import Yoyo from '../utils/Yoyo.js'; + +class Spinner extends Base { + constructor(scene, config) { + super(scene, config); + this.type = 'rexSpinnerSpinner'; + } + + buildShapes() { + this.addShape((new Arc()).setName('arc')); + } + + updateShapes() { + var centerX = this.centerX; + var centerY = this.centerY; + var radius = this.radius; + var lineWidth = Math.ceil(radius / 10); + var maxRadius = radius - lineWidth; + + var endAngle = this.value * 720; + var arcAngle = Yoyo(this.value) * 180; + var startAngle = endAngle - arcAngle; + this.getShape('arc') + .lineStyle(lineWidth, this.color, 1) + .setRadius(maxRadius) + .setCenterPosition(centerX, centerY) + .setAngle(startAngle + 315, endAngle + 315); + + } +} + +export default Spinner; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/utils/Geoms.js b/ui/src/phaser3-rex-plugins/templates/spinner/utils/Geoms.js new file mode 100644 index 000000000..7bb21ff0a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/utils/Geoms.js @@ -0,0 +1,23 @@ +import { + Arc, + Circle, + Curve, + Ellipse, + Line, + Lines, + Rectangle, + RoundRectangle, + Triangle +} from '../../../plugins/gameobjects/shape/shapes/geoms'; + +export { + Arc, + Circle, + Curve, + Ellipse, + Line, + Lines, + Rectangle, + RoundRectangle, + Triangle +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/spinner/utils/Yoyo.js b/ui/src/phaser3-rex-plugins/templates/spinner/utils/Yoyo.js new file mode 100644 index 000000000..7d1430b87 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/spinner/utils/Yoyo.js @@ -0,0 +1,2 @@ +import Yoyo from '../../../plugins/utils/math/Yoyo.js' +export default Yoyo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/ObjectFactory.js b/ui/src/phaser3-rex-plugins/templates/ui/ObjectFactory.js new file mode 100644 index 000000000..56a37cdc6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/ObjectFactory.js @@ -0,0 +1,20 @@ +class ObjectFactory { + constructor(scene) { + this.scene = scene; + this.displayList = scene.sys.displayList; + this.updateList = scene.sys.updateList; + + scene.events.once('destroy', this.destroy, this); + } + + destroy() { + this.scene = null; + this.displayList = null; + this.updateList = null; + } + + static register(type, callback) { + ObjectFactory.prototype[type] = callback; + } +}; +export default ObjectFactory; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/AlphaMaskImage.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/AlphaMaskImage.d.ts new file mode 100644 index 000000000..892d49c9e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/AlphaMaskImage.d.ts @@ -0,0 +1,2 @@ +import AlphaMaskImage from '../../../plugins/alphamaskimage'; +export default AlphaMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/AlphaMaskImage.js b/ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/AlphaMaskImage.js new file mode 100644 index 000000000..7bfad1377 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/AlphaMaskImage.js @@ -0,0 +1,2 @@ +import AlphaMaskImage from '../../../plugins/alphamaskimage.js'; +export default AlphaMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/Factory.d.ts new file mode 100644 index 000000000..7c79ae188 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/Factory.d.ts @@ -0,0 +1,7 @@ +import AlphaMaskImage from './AlphaMaskImage'; + +export default function ( + x?: number, y?: number, + key?: string, frame?: string, + config?: AlphaMaskImage.IConfig +): AlphaMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/Factory.js new file mode 100644 index 000000000..a0df36403 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/Factory.js @@ -0,0 +1,13 @@ +import AlphaMaskImage from './AlphaMaskImage.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('alphaMaskImage', function (x, y, key, frame, config) { + var gameObject = new AlphaMaskImage(this.scene, x, y, key, frame, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.AlphaMaskImage', AlphaMaskImage); + +export default AlphaMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/anchor/Anchor.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/anchor/Anchor.d.ts new file mode 100644 index 000000000..cffd99e19 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/anchor/Anchor.d.ts @@ -0,0 +1,2 @@ +import Anchor from '../../../plugins/behaviors/anchor/Anchor'; +export default Anchor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/anchor/Anchor.js b/ui/src/phaser3-rex-plugins/templates/ui/anchor/Anchor.js new file mode 100644 index 000000000..664b937ad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/anchor/Anchor.js @@ -0,0 +1,2 @@ +import Anchor from '../../../plugins/behaviors/anchor/Anchor.js'; +export default Anchor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/anchor/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/anchor/Factory.d.ts new file mode 100644 index 000000000..1d0955e69 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/anchor/Factory.d.ts @@ -0,0 +1,7 @@ +// import * as Phaser from 'phaser'; +import Anchor from "./Anchor"; + +export default function ( + gameObject: Phaser.GameObjects.GameObject, + config?: Anchor.IConfig +): Anchor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/anchor/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/anchor/Factory.js new file mode 100644 index 000000000..87311e1d1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/anchor/Factory.js @@ -0,0 +1,11 @@ +import Anchor from "./Anchor.js"; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('anchor', function (gameObject, config) { + return new Anchor(gameObject, config); +}); + +SetValue(window, 'RexPlugins.UI.Anchor', Anchor); + +export default Anchor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/badgelabel/BadgeLabel.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/badgelabel/BadgeLabel.d.ts new file mode 100644 index 000000000..a63bf75bc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/badgelabel/BadgeLabel.d.ts @@ -0,0 +1,30 @@ +// import * as Phaser from 'phaser'; +import OverlapSizer from '../overlapsizer/OverlapSizer'; + +export default BadgeLabel; + +declare namespace BadgeLabel { + + interface IConfig extends OverlapSizer.IConfig { + background?: Phaser.GameObjects.GameObject, + main?: Phaser.GameObjects.GameObject, + + leftTop?: Phaser.GameObjects.GameObject, + centerTop?: Phaser.GameObjects.GameObject, + rightTop?: Phaser.GameObjects.GameObject, + leftCenter?: Phaser.GameObjects.GameObject, + center?: Phaser.GameObjects.GameObject, + rightCenter?: Phaser.GameObjects.GameObject, + leftBottom?: Phaser.GameObjects.GameObject, + centerBottom?: Phaser.GameObjects.GameObject, + rightBottom?: Phaser.GameObjects.GameObject, + } +} + +declare class BadgeLabel extends OverlapSizer { + + constructor( + scene: Phaser.Scene, + config?: BadgeLabel.IConfig + ); +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/badgelabel/BadgeLabel.js b/ui/src/phaser3-rex-plugins/templates/ui/badgelabel/BadgeLabel.js new file mode 100644 index 000000000..fdbb2bbdd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/badgelabel/BadgeLabel.js @@ -0,0 +1,49 @@ +import OverlapSizer from '../overlapsizer/OverlapSizer.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const BadgeKeys = { + leftTop: 'left-top', centerTop: 'center-top', rightTop: 'right-top', + leftCenter: 'left-center', center: 'center', rightCenter: 'right-center', + leftBottom: 'left-bottom', centerBottom: 'center-bottom', rightBottom: 'right-bottom' +} + +class Badge extends OverlapSizer { + constructor(scene, config) { + // Create sizer + super(scene, config); + this.type = 'rexBadge'; + + // Add elements + var background = GetValue(config, 'background', undefined); + if (background) { + this.addBackground(background); + } + this.addChildrenMap('background', background); + + // Base item + var main = GetValue(config, 'main', undefined); + if (main) { + this.add(main, { + key: 'main', + align: 'center', + expand: false, + }) + } + this.addChildrenMap('main', main); + + // Badges + for (var key in BadgeKeys) { + var badge = GetValue(config, key, undefined); + if (badge) { + this.add(badge, { + key: key, + align: BadgeKeys[key], + expand: false, + }) + this.addChildrenMap(key, badge); + } + } + } +} + +export default Badge; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/badgelabel/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/badgelabel/Factory.d.ts new file mode 100644 index 000000000..05a39fbaa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/badgelabel/Factory.d.ts @@ -0,0 +1,5 @@ +import BadgeLabel from './BadgeLabel'; + +export default function ( + config?: BadgeLabel.IConfig +): BadgeLabel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/badgelabel/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/badgelabel/Factory.js new file mode 100644 index 000000000..1e182ab94 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/badgelabel/Factory.js @@ -0,0 +1,13 @@ +import BadgeLabel from './BadgeLabel.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('badgeLabel', function (config) { + var gameObject = new BadgeLabel(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.BadgeLabel', BadgeLabel); + +export default BadgeLabel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/AddChildMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/AddChildMethods.js new file mode 100644 index 000000000..c487e69bf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/AddChildMethods.js @@ -0,0 +1,37 @@ +import GetBoundsConfig from '../utils/GetBoundsConfig.js'; +import AddChild from './utils/AddChild.js'; + +export default { + addBackground(gameObject, paddingConfig, childKey) { + if (this.backgroundChildren === undefined) { + this.backgroundChildren = []; + } + + if (typeof (paddingConfig) === 'string') { + childKey = paddingConfig; + paddingConfig = undefined; + } + + if (paddingConfig === undefined) { + paddingConfig = 0; + } + + AddChild.call(this, gameObject); + this.backgroundChildren.push(gameObject); + + var config = this.getSizerConfig(gameObject); + config.padding = GetBoundsConfig(paddingConfig); + + if (childKey !== undefined) { + this.addChildrenMap(childKey, gameObject) + } + return this; + }, + + isBackground(gameObject) { + if (this.backgroundChildren === undefined) { + return false; + } + return (this.backgroundChildren.indexOf(gameObject) !== -1); + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/AddChildrenMap.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/AddChildrenMap.js new file mode 100644 index 000000000..2a234643e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/AddChildrenMap.js @@ -0,0 +1,6 @@ +var AddChildrenMap = function (key, gameObject) { + this.childrenMap[key] = gameObject; + return this; +} + +export default AddChildrenMap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/BaseSizer.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/BaseSizer.d.ts new file mode 100644 index 000000000..b773fbe4b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/BaseSizer.d.ts @@ -0,0 +1,739 @@ +// import * as Phaser from 'phaser'; +import ContainerLite from '../../../plugins/containerlite.js'; +import Anchor from '../anchor/Anchor'; +import Click from '../click/Click'; +import ClickOutside from '../clickoutside/ClickOutside'; +import InTouching from '../intouching/InTouching'; +import SetChildrenInteractive from '../utils/setchildreninteractive/SetChildrenInteractive'; +import { ModalBehavoir } from '../modal/Modal'; + +export default BaseSizer; + +declare namespace BaseSizer { + type AlignTypes = number | 'center' | 'left' | 'right' | 'top' | 'bottom' | + 'left-top' | 'left-center' | 'left-bottom' | + 'center-top' | 'center-center' | 'center-bottom' | + 'right-top' | 'right-center' | 'right-bottom'; + + type PaddingTypes = number | + { + left?: number, + right?: number, + top?: number, + bottom?: number + }; + + interface IConfig { + space?: { + left?: number, right?: number, top?: number, bottom?: number, + }, + + anchor?: Anchor.IConfig, + + name?: string, + + enableLayer?: boolean, + + draggable?: boolean | string | Phaser.GameObjects.GameObject, + + sizerEvents?: boolean, + } + + type PrevState = { + x: number, + y: number, + width: number, height: number, + displayWidth: number, displayHeight: number, + scaleX: number, scaleY: number + } + + type OnModalCloseCallbackType = ( + data: Object + ) => void; + +} + +declare class BaseSizer extends ContainerLite { + isRexSizer: true; + + space: { [name: string]: number }; + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + minWidth?: number, minHeight?: number, + config?: BaseSizer.IConfig + ); + + setMinSize(minWidth: number, minHeight: number): this; + + setMinWidth(minWidth: number): this; + + setMinHeight(minHeight: number): this; + + setDirty(dirty?: boolean): this; + + setSizerEventsEnable(enable?: boolean): this; + sizerEventsEnable: boolean; + + left: number; + + alignLeft(value: number): this; + + right: number; + + alignRight(value: number): this; + + centerX: number; + + alignCenterX(value: number): this; + + top: number; + + alignTop(value: number): this; + + bottom: number; + + alignBottom(value: number): this; + + centerY: number; + + alignCenterY(value: number): this; + + pushIntoBounds( + bounds?: Phaser.Geom.Rectangle | { left?: number, right?: number, top?: number, bottom?: number } + ): this; + + readonly innerLeft: number; + + readonly innerRight: number; + + readonly innerTop: number; + + readonly innerBottom: number; + + readonly innerWidth: number; + + readonly innerHeight: number; + + readonly minInnerWidth: number; + + readonly minInnerHeight: number; + + addBackground( + gameObject: Phaser.GameObjects.GameObject, + padding?: BaseSizer.PaddingTypes, + childKey?: string + ): this; + + isBackground( + gameObject: Phaser.GameObjects.GameObject + ): boolean; + + layout(): this; + + drawBounds( + graphics: Phaser.GameObjects.Graphics, + color?: number + ): this; + + drawBounds( + graphics: Phaser.GameObjects.Graphics, + config?: { + color?: number, + lineWidth?: number, + name?: boolean | + { + createTextCallback: (scene: Phaser.Scene) => Phaser.GameObjects.GameObject, + createTextCallbackScope?: object, + align?: BaseSizer.AlignTypes + } + } + ): this; + + childrenMap: { + [key: string]: + Phaser.GameObjects.GameObject + }; + addChildrenMap( + key: string, + gameObject: Phaser.GameObjects.GameObject + ): this; + + removeChildrenMap(key: string): this; + removeChildrenMap(gameObject: Phaser.GameObjects.GameObject): this; + + getElement( + name: string, + recursive?: boolean + ): Phaser.GameObjects.GameObject | + Phaser.GameObjects.GameObject[] | + { [name: string]: Phaser.GameObjects.GameObject } | + null; + + getParentSizer( + name?: string + ): BaseSizer | null; + + getParentSizer( + gameObject?: Phaser.GameObjects.GameObject, + name?: string + ): BaseSizer | null; + + getTopmostSizer( + gameObject?: Phaser.GameObjects.GameObject + ): BaseSizer | null; + + getSizerConfig( + gameObject?: Phaser.GameObjects.GameObject + ): { [name: string]: any }; + + getChildPrevState( + gameObject: Phaser.GameObjects.GameObject + ): BaseSizer.PrevState; + + isInTouching(): boolean; + + isInTouching( + pointer: Phaser.Input.Pointer, + gameObject?: Phaser.GameObjects.GameObject | string + ): boolean; + + isInTouching( + gameObject?: Phaser.GameObjects.GameObject | string + ): boolean; + + + moveFrom( + duration: number, + x: number, + y: number, + ease?: string + ): this; + + moveFrom( + config: { + x: number, + y: number, + speed?: number, + duration?: number, + ease?: string, + } + ): this; + + moveFromPromise( + duration: number, + x: number, + y: number, + ease?: string + ): Promise; + + moveFromPromise( + config: { + x: number, + y: number, + speed?: number, + duration?: number, + ease?: string, + } + ): Promise; + + moveFromDestroy( + duration: number, + x: number, + y: number, + ease?: string + ): this; + + moveFromDestroy( + config: { + x: number, + y: number, + speed?: number, + duration?: number, + ease?: string, + } + ): this; + + moveFromDestroyPromise( + duration: number, + x: number, + y: number, + ease?: string + ): Promise; + + moveFromDestroyPromise( + config: { + x: number, + y: number, + speed?: number, + duration?: number, + ease?: string, + } + ): Promise; + + moveTo( + duration: number, + x: number, + y: number, + ease?: string + ): this; + + moveTo( + config: { + x: number, + y: number, + speed?: number, + duration?: number, + ease?: string, + } + ): this; + + moveToPromise( + duration: number, + x: number, + y: number, + ease?: string + ): Promise; + + moveToPromise( + config: { + x: number, + y: number, + speed?: number, + duration?: number, + ease?: string, + } + ): Promise; + + moveToDestroy( + duration: number, + x: number, + y: number, + ease?: string + ): this; + + moveToDestroy( + config: { + x: number, + y: number, + speed?: number, + duration?: number, + ease?: string, + } + ): this; + + moveToDestroyPromise( + duration: number, + x: number, + y: number, + ease?: string + ): Promise; + + moveToDestroyPromise( + config: { + x: number, + y: number, + speed?: number, + duration?: number, + ease?: string, + } + ): Promise; + + moveStop(toEnd?: boolean): this; + + fadeIn( + duration: number, + alpha?: number + ): this; + + fadeInPromise( + duration: number, + alpha?: number + ): Promise; + + fadeOutDestroy( + duration: number + ): this; + + fadeOutDestroyPromise( + duration: number + ): Promise; + + fadeOut( + duration: number + ): this; + + fadeOutPromise( + duration: number + ): Promise; + + popUp( + duration: number, + orientation?: 0 | 1 | 'x' | 'y', + ease?: string + ): this; + + popUpPromise( + duration: number, + orientation?: 0 | 1 | 'x' | 'y', + ease?: string + ): Promise; + + scaleDownDestroy( + duration: number, + orientation?: 0 | 1 | 'x' | 'y', + ease?: string + ): this; + + scaleDownDestroyPromise( + duration: number, + orientation?: 0 | 1 | 'x' | 'y', + ease?: string + ): Promise; + + scaleDown( + duration: number, + orientation?: 0 | 1 | 'x' | 'y', + ease?: string + ): this; + + scaleDownPromise( + duration: number, + orientation?: 0 | 1 | 'x' | 'y', + ease?: string + ): Promise; + + scaleYoyo( + duration: number, + peakValue?: number, + repeat?: number, + orientation?: 0 | 1 | 'x' | 'y', + ease?: string + ): this; + + scaleYoyoPromise( + duration: number, + peakValue?: number, + repeat?: number, + orientation?: 0 | 1 | 'x' | 'y', + ease?: string + ): Promise; + + shake( + duration?: number, + magnitude?: number, + magnitudeMode?: 0 | 1 | 'constant' | 'decay' + ): this; + + shakePromise( + duration?: number, + magnitude?: number, + magnitudeMode?: 0 | 1 | 'constant' | 'decay' + ): Promise; + + easeDataTo( + key: string, + value: number, + duration?: number, + ease?: string + ): this; + + easeDataTo( + config: { + key: string, + value: number, + duration?: number, + ease?: string, + speed?: number + } + ): this; + + easeDataToPromise( + key: string, + value: number, + duration?: number, + ease?: string + ): Promise; + + easeDataToPromise( + config: { + key: string, + value: number, + duration?: number, + ease?: string, + speed?: number + } + ): Promise; + + stopEaseData( + key: string, + toEnd?: boolean + ): this; + + stopAllEaseData( + toEnd?: boolean + ): this; + + setAnchor(config: { + left?: string, right?: string, centerX?: string, x?: string, + top?: string, bottom?: string, centerY?: string, y?: string + }): this; + + setDraggable( + senser: boolean | string | Phaser.GameObjects.GameObject, + draggable?: boolean + ): this; + + onClick( + callback: ( + click: Click, + gameObject: Phaser.GameObjects.GameObject, + pointer: Phaser.Input.Pointer, + event: Phaser.Types.Input.EventData + ) => void, + scope?: object, + config?: Click.IConfig + ): this; + + + onClick( + gameObject: Phaser.GameObjects.GameObject, + callback: ( + click: Click, + gameObject: Phaser.GameObjects.GameObject, + pointer: Phaser.Input.Pointer, + event: Phaser.Types.Input.EventData + ) => void, + scope?: object, + config?: Click.IConfig + ): this; + + offClick( + callback: Function, + scope?: object + ): this; + + offClick( + gameObject: Phaser.GameObjects.GameObject, + callback: Function, + scope?: object + ): this; + + enableClick(enabled?: boolean): this; + + enableClick( + gameObject: Phaser.GameObjects.GameObject, + enabled?: boolean + ): this; + + disableClick(): this; + + disableClick(gameObject: Phaser.GameObjects.GameObject): this; + + onClickOutside( + callback: ( + clickOutside: ClickOutside, + gameObject: Phaser.GameObjects.GameObject, + pointer: Phaser.Input.Pointer + ) => void, + scope?: object, + config?: ClickOutside.IConfig + ): this; + + onClickOutside( + gameObject: Phaser.GameObjects.GameObject, + callback: ( + clickOutside: ClickOutside, + gameObject: Phaser.GameObjects.GameObject, + pointer: Phaser.Input.Pointer + ) => void, + scope?: object, + config?: ClickOutside.IConfig + ): this; + + offClickOutside( + callback: Function, + scope?: object + ): this; + + offClickOutside( + gameObject: Phaser.GameObjects.GameObject, + callback: Function, + scope?: object + ): this; + + + enableClickOutside(enabled?: boolean): this; + + enableClickOutside( + gameObject: Phaser.GameObjects.GameObject, + enabled?: boolean + ): this; + + disableClickOutside(): this; + + disableClickOutside(gameObject: Phaser.GameObjects.GameObject): this; + + isPointerInBounds(): boolean; + isPointerInBounds(gameObject: Phaser.GameObjects.GameObject): boolean; + isPointerInBounds(name: string): boolean; + + onTouching( + callback: ( + inTouch: InTouching, + gameObject: Phaser.GameObjects.GameObject, + pointer: Phaser.Input.Pointer, + ) => void, + scope?: object, + config?: InTouching.IConfig + ): this; + + onTouching( + gameObject: Phaser.GameObjects.GameObject, + callback: ( + inTouch: InTouching, + gameObject: Phaser.GameObjects.GameObject, + pointer: Phaser.Input.Pointer, + ) => void, + scope?: object, + config?: InTouching.IConfig + ): this; + + offTouching( + callback: Function, + scope?: object + ): this; + + offTouching( + gameObject: Phaser.GameObjects.GameObject, + callback: Function, + scope?: object + ): this; + + onTouchingEnd( + callback: ( + inTouch: InTouching, + gameObject: Phaser.GameObjects.GameObject, + pointer: Phaser.Input.Pointer, + ) => void, + scope?: object, + config?: InTouching.IConfig + ): this; + + onTouchingEnd( + gameObject: Phaser.GameObjects.GameObject, + callback: ( + inTouch: InTouching, + gameObject: Phaser.GameObjects.GameObject, + pointer: Phaser.Input.Pointer, + ) => void, + scope?: object, + config?: InTouching.IConfig + ): this; + + offTouchingEnd( + callback: Function, + scope?: object + ): this; + + offTouchingEnd( + gameObject: Phaser.GameObjects.GameObject, + callback: Function, + scope?: object + ): this; + + enableTouching(enable?: boolean): this; + + enableTouching( + gameObject: Phaser.GameObjects.GameObject, + enable?: boolean + ): this; + + disableTouching(): this; + + disableTouching(gameObject: Phaser.GameObjects.GameObject): this; + + setChildrenInteractive( + config: SetChildrenInteractive.IConfig + ): this; + + show( + gameObject?: Phaser.GameObjects.GameObject + ): this; + + hide( + gameObject?: Phaser.GameObjects.GameObject + ): this; + + isShow( + gameObject: Phaser.GameObjects.GameObject + ): boolean; + + onCreateModalBehavior: (self: this) => void; + + modal( + config?: ModalBehavoir.IConfig, + onClose?: BaseSizer.OnModalCloseCallbackType + ): this; + + modal( + onClose?: BaseSizer.OnModalCloseCallbackType + ): this; + + modalPromise( + config?: ModalBehavoir.IConfig + ): Promise; + + modalClose(closeEventData?: Object): this; + + broadcastEvent( + event: string, + ...args: any[] + ): this; + + getShownChildren( + out?: Phaser.GameObjects.GameObject[] + ): Phaser.GameObjects.GameObject[]; + + getAllShownChildren( + out?: Phaser.GameObjects.GameObject[] + ): Phaser.GameObjects.GameObject[]; + + getInnerPadding( + key?: string + ): number | { left: number, right: number, top: number, bottom: number }; + + setInnerPadding( + key: string | number | { left?: number, right?: number, top?: number, bottom?: number }, + value?: number + ): this; + + getOutterPadding( + key?: string + ): number | { left: number, right: number, top: number, bottom: number }; + + setOuterPadding( + key: string | number | { left?: number, right?: number, top?: number, bottom?: number }, + value?: number + ): this; + + getChildOutterPadding( + child: string | Phaser.GameObjects.GameObject, + key?: string + ): number | { left: number, right: number, top: number, bottom: number }; + + setChildOuterPadding( + child: string | Phaser.GameObjects.GameObject, + key: string | number | { left?: number, right?: number, top?: number, bottom?: number }, + value?: number + ): this; + + pointToChild( + x: number, + y: number, + preTest?: (gameObject: Phaser.GameObjects.GameObject, x: number, y: number) => boolean, + postTest?: (gameObject: Phaser.GameObjects.GameObject, x: number, y: number) => boolean, + children?: Phaser.GameObjects.GameObject[] + ): Phaser.GameObjects.GameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/BaseSizer.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/BaseSizer.js new file mode 100644 index 000000000..7b422f11d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/BaseSizer.js @@ -0,0 +1,241 @@ +import Container from '../container/Container.js'; +import Methods from './Methods.js'; +import { GetDisplayWidth, GetDisplayHeight } from '../../../plugins/utils/size/GetDisplaySize.js'; +import Clear from '../../../plugins/utils/object/Clear.js' + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Base extends Container { + constructor(scene, x, y, minWidth, minHeight, config) { + super(scene, x, y, 1, 1); + + this.isRexSizer = true; + this.setMinSize(minWidth, minHeight); + this.setName(GetValue(config, 'name', '')); + this.rexSizer = {}; + this.space = {}; + this.backgroundChildren = undefined; + this.sizerChildren = undefined; // [] or {} + this.childrenMap = {}; + this.layoutedChildren = undefined; + + var anchorConfig = GetValue(config, 'anchor', undefined); + if (anchorConfig) { + this.setAnchor(anchorConfig); + } + + this.setInnerPadding(GetValue(config, 'space', 0)); + + var draggable = GetValue(config, 'draggable', false); + if (draggable) { + this.setDraggable(draggable); + } + + this.setSizerEventsEnable(GetValue(config, 'sizerEvents', false)); + this.setDirty(true); + + if (GetValue(config, 'enableLayer', false)) { + this.enableLayer(); + } + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + if (fromScene) { + // In this case, children will be cleared and destroy in scene level + var sizers = this.getAllChildrenSizers([this]); + for (var i = 0, cnt = sizers.length; i < cnt; i++) { + sizers[i].sizerEventsEnable = false; + } + } + + super.destroy(fromScene); + + Clear(this.backgroundChildren); + Clear(this.sizerChildren); + this.childrenMap = undefined; + this.space = undefined; + this.rexSizer = undefined; + this.layoutedChildren = undefined; + } + + setMinSize(minWidth, minHeight) { + this.setMinWidth(minWidth).setMinHeight(minHeight); + return this; + } + + setMinWidth(minWidth) { + if (minWidth == null) { + minWidth = 0; + } + this.minWidth = minWidth; + return this; + } + + setMinHeight(minHeight) { + if (minHeight == null) { + minHeight = 0; + } + this.minHeight = minHeight; + return this; + } + + setDirty(dirty) { + if (dirty === undefined) { + dirty = true; + } + this.dirty = dirty; + return this; + } + + setSizerEventsEnable(enable) { + if (enable === undefined) { + enable = true; + } + + this.sizerEventsEnable = enable; + return this; + } + + get ignoreLayout() { + // Skip hidden or !dirty sizer + return this.rexSizer.hidden || (!this.dirty); + } + + get childrenWidth() { + if (this._childrenWidth === undefined) { + this._childrenWidth = this.getChildrenWidth(); + } + return this._childrenWidth; + } + + get childrenHeight() { + if (this._childrenHeight === undefined) { + this._childrenHeight = this.getChildrenHeight(); + } + return this._childrenHeight; + } + + get left() { + return this.x - (GetDisplayWidth(this) * this.originX); + } + + set left(value) { + this.x += (value - this.left); + } + + alignLeft(value) { + this.left = value; + return this; + } + + get right() { + return this.left + GetDisplayWidth(this); + } + + set right(value) { + this.x += (value - this.right); + } + + alignRight(value) { + this.right = value; + return this; + } + + get centerX() { + return this.left + (GetDisplayWidth(this) / 2); + } + + set centerX(value) { + this.x += (value - this.centerX); + } + + alignCenterX(value) { + this.centerX = value; + return this; + } + + get top() { + return this.y - (GetDisplayHeight(this) * this.originY); + } + + set top(value) { + this.y += (value - this.top); + } + + alignTop(value) { + this.top = value; + return this; + } + + get bottom() { + return this.top + GetDisplayHeight(this); + } + + set bottom(value) { + this.y += (value - this.bottom); + } + + alignBottom(value) { + this.bottom = value; + return this; + } + + get centerY() { + return this.top + (GetDisplayHeight(this) / 2); + } + + set centerY(value) { + this.y += (value - this.centerY); + } + + alignCenterY(value) { + this.centerY = value; + return this; + } + + get innerLeft() { + return this.left + this.space.left; + } + + get innerRight() { + return this.right - this.space.right; + } + + get innerTop() { + return this.top + this.space.top; + } + + get innerBottom() { + return this.bottom - this.space.bottom; + } + + get innerWidth() { + return this.width - this.space.left - this.space.right; + } + + get innerHeight() { + return this.height - this.space.top - this.space.bottom; + } + + get minInnerWidth() { + var result = this.minWidth - this.space.left - this.space.right; + return Math.max(result, 0); + } + + get minInnerHeight() { + var result = this.minHeight - this.space.top - this.space.bottom; + return Math.max(result, 0); + } +} + +Object.assign( + Base.prototype, + Methods +); + +export default Base; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/BroadcastEvent.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/BroadcastEvent.js new file mode 100644 index 000000000..1d9008e99 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/BroadcastEvent.js @@ -0,0 +1,10 @@ +var BroadcastEvent = function () { + var gameObjects = this.getAllChildren([this]); + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + var gameObject = gameObjects[i]; + gameObject.emit.apply(gameObject, arguments); + } + return this; +} + +export default BroadcastEvent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ClickMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ClickMethods.js new file mode 100644 index 000000000..7691bbebb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ClickMethods.js @@ -0,0 +1,65 @@ +import Click from '../click/Click.js'; + +export default { + onClick(gameObject, callback, scope, config) { + if (!gameObject) { + return this; + } + + if (typeof (gameObject) === 'function') { + config = scope; + scope = callback; + callback = gameObject; + gameObject = this; + } + + if (gameObject._click === undefined) { + gameObject._click = new Click(gameObject, config); + } + gameObject._click.on('click', callback, scope); + + return this; + }, + + offClick(gameObject, callback, scope) { + if (typeof (gameObject) === 'function') { + scope = callback; + callback = gameObject; + gameObject = this; + } + + if (gameObject._click === undefined) { + return this; + } + gameObject._click.off('click', callback, scope); + + return this; + }, + + enableClick(gameObject, enabled) { + if (gameObject && typeof (gameObject) !== 'object') { + enabled = gameObject; + gameObject = this; + } + + if (gameObject._click === undefined) { + return this; + } + + gameObject._click.setEnable(enabled); + return this; + }, + + disableClick(gameObject) { + if (gameObject && typeof (gameObject) !== 'object') { + gameObject = this; + } + + if (gameObject._click === undefined) { + return this; + } + gameObject._click.setEnable(false); + + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ClickOutsideMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ClickOutsideMethods.js new file mode 100644 index 000000000..ef5d6d4cd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ClickOutsideMethods.js @@ -0,0 +1,65 @@ +import ClickOutside from '../clickoutside/ClickOutside.js'; + +export default { + onClickOutside(gameObject, callback, scope, config) { + if (!gameObject) { + return this; + } + + if (typeof (gameObject) === 'function') { + config = scope; + scope = callback; + callback = gameObject; + gameObject = this; + } + + if (gameObject._clickOutside === undefined) { + gameObject._clickOutside = new ClickOutside(gameObject, config); + } + gameObject._clickOutside.on('clickoutside', callback, scope); + + return this; + }, + + offClickOutside(gameObject, callback, scope) { + if (typeof (gameObject) === 'function') { + scope = callback; + callback = gameObject; + gameObject = this; + } + + if (gameObject._clickOutside === undefined) { + return this; + } + gameObject._clickOutside.off('clickoutside', callback, scope); + + return this; + }, + + enableClickOutside(gameObject, enabled) { + if (gameObject && typeof (gameObject) !== 'object') { + enabled = gameObject; + gameObject = this; + } + + if (gameObject._clickOutside === undefined) { + return this; + } + gameObject._clickOutside.setEnable(enabled); + + return this; + }, + + disableClickOutside(gameObject) { + if (gameObject && typeof (gameObject) !== 'object') { + gameObject = this; + } + + if (gameObject._clickOutside === undefined) { + return this; + } + gameObject._clickOutside.setEnable(false); + + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/DrawBounds.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/DrawBounds.js new file mode 100644 index 000000000..159a8d12d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/DrawBounds.js @@ -0,0 +1,90 @@ +import ALIGNMODE from '../utils/AlignConst.js'; +import AlignIn from '../../../plugins/utils/actions/AlignIn.js'; +import { GetBounds } from '../../../plugins/utils/bounds/GetBounds.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Group = Phaser.GameObjects.Group; + +var DrawBounds = function (graphics, config) { + var scene = graphics.scene; + + var color, lineWidth; + var createTextCallback, createTextCallbackScope, textAlign; + if (typeof (config) === 'number') { + color = config; + } else { + color = GetValue(config, 'color'); + lineWidth = GetValue(config, 'lineWidth'); + var nameTextConfig = GetValue(config, 'name', false); + if (nameTextConfig) { + createTextCallback = GetValue(nameTextConfig, 'createTextCallback', DefaultCreateTextCallback); + createTextCallbackScope = GetValue(nameTextConfig, 'createTextCallbackScope', undefined); + textAlign = GetValue(nameTextConfig, 'align', 'left-top'); + if (typeof (textAlign) === 'string') { + textAlign = ALIGNMODE[textAlign]; + } + } + } + + if (color === undefined) { + color = 0xffffff; + } + if (lineWidth === undefined) { + lineWidth = 1; + } + + if (createTextCallback && !graphics.children) { + graphics.children = new Group(scene); + graphics.once('destroy', function (graphics, fromScene) { + graphics.children.destroy(!fromScene); + graphics.children = undefined; + }) + var graphicsClear = graphics.clear.bind(graphics); + graphics.clear = function () { + graphicsClear(); + graphics.children.clear(false, true); + } + } + + var children = this.getAllShownChildren([this]), child; + var nameText; + for (var i = 0, cnt = children.length; i < cnt; i++) { + child = children[i]; + if (child.getBounds || + ((child.width !== undefined) && (child.height !== undefined)) + ) { + GlobRect = GetBounds(child, GlobRect); + } else { + continue; + } + + if (color != null) { + graphics + .lineStyle(lineWidth, color) + .strokeRectShape(GlobRect); + } + + if (child.name && createTextCallback) { + if (createTextCallbackScope) { + nameText = createTextCallback.call(createTextCallbackScope, scene); + } else { + nameText = createTextCallback(scene); + } + if (nameText) { + nameText.setText(child.name); + graphics.children.add(nameText); + + AlignIn(nameText, GlobRect.x, GlobRect.y, GlobRect.width, GlobRect.height, textAlign); + } + } + } + return this; +} + +var DefaultCreateTextCallback = function (scene, child, childBoundsRect) { + return scene.add.text(0, 0, ''); +} + +var GlobRect = undefined; + +export default DrawBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/EaseDataMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/EaseDataMethods.js new file mode 100644 index 000000000..b71567aba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/EaseDataMethods.js @@ -0,0 +1,44 @@ +import { EaseData } from '../../../plugins/easedata.js'; +import { WaitEvent } from '../utils/WaitEvent.js'; + +var OnInitEaseData = function (gameObject, easeData) { + // Route 'complete' of easeData to gameObject + easeData.on('complete', function (key) { + gameObject.emit(`easedata.${key}.complete`, gameObject); + gameObject.emit('easedata.complete', key, gameObject); + }) +} + +export default { + easeDataTo(key, value, duration, ease) { + if (!this._easeData) { + this._easeData = new EaseData(this); + OnInitEaseData(this, this._easeData); + } + this._easeData.easeTo(key, value, duration, ease); + return this; + }, + + easeDataToPromise(key, value, duration, ease) { + this.easeDataTo(key, value, duration, ease); + return WaitEvent(this._easeData, `complete-${key}`); + }, + + stopEaseData(key, toEnd) { + if (!this._easeData) { + return this; + } + + this._easeData.stopEase(key, toEnd); + return this; + }, + + stopAllEaseData(toEnd) { + if (!this._easeData) { + return this; + } + + this._easeData.stopAll(toEnd); + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/EaseMoveMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/EaseMoveMethods.js new file mode 100644 index 000000000..04cbc9211 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/EaseMoveMethods.js @@ -0,0 +1,120 @@ +import { EaseMoveTo, EaseMoveFrom } from '../easemove/EaseMove.js'; +import { WaitComplete } from '../utils/WaitEvent.js'; +import GetParentSizerMethods from './GetParentSizerMethods.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const DistanceBetween = Phaser.Math.Distance.Between; + +var OnInitEaseMove = function (gameObject, easeMove) { + // Route 'complete' of easeMove to gameObject + easeMove.completeEventName = undefined; + easeMove.on('complete', function () { + if (easeMove.completeEventName) { + gameObject.emit(easeMove.completeEventName, gameObject); + easeMove.completeEventName = undefined; + } + }) + + // Update local state + easeMove.on('update', function () { + var parent = GetParentSizerMethods.getParentSizer(gameObject); + if (parent) { + parent.resetChildPositionState(gameObject); + } + }) +} + +export default { + moveFrom(duration, x, y, ease, destroyMode) { + if (IsPlainObject(duration)) { + var config = duration; + x = config.x; + y = config.y; + if (config.hasOwnProperty('speed')) { + duration = (DistanceBetween(x, y, this.x, this.y) * 1000) / config.speed; + } else { + duration = config.duration; + } + + ease = config.ease; + } + + var isInit = (this._easeMove === undefined); + + this._easeMove = EaseMoveFrom(this, duration, x, y, ease, destroyMode, this._easeMove); + + if (isInit) { + OnInitEaseMove(this, this._easeMove); + } + + this._easeMove.completeEventName = 'movefrom.complete'; + + return this; + }, + + moveFromPromise(duration, x, y, ease, destroyMode) { + this.moveFrom(duration, x, y, ease, destroyMode); + return WaitComplete(this._easeMove); + }, + + moveFromDestroy(duration, x, y, ease) { + this.moveFrom(duration, x, y, ease, true); + return this; + }, + + moveFromDestroyPromise(duration, x, y, ease) { + this.moveFromDestroy(duration, x, y, ease); + return WaitComplete(this._easeMove); + }, + + moveTo(duration, x, y, ease, destroyMode) { + if (IsPlainObject(duration)) { + var config = duration; + x = config.x; + y = config.y; + if (config.hasOwnProperty('speed')) { + duration = (DistanceBetween(x, y, this.x, this.y) * 1000) / config.speed; + } else { + duration = config.duration; + } + + ease = config.ease; + } + + var isInit = (this._easeMove === undefined); + + this._easeMove = EaseMoveTo(this, duration, x, y, ease, destroyMode, this._easeMove); + + if (isInit) { + OnInitEaseMove(this, this._easeMove); + } + + this._easeMove.completeEventName = 'moveto.complete'; + + return this; + }, + + moveToPromise(duration, x, y, ease, destroyMode) { + this.moveTo(duration, x, y, ease, destroyMode); + return WaitComplete(this._easeMove); + }, + + moveToDestroy(duration, x, y, ease) { + this.moveTo(duration, x, y, ease, true) + return this; + }, + + moveToDestroyPromise(duration, x, y, ease) { + this.moveToDestroy(duration, x, y, ease, true); + return WaitComplete(this._easeMove); + }, + + moveStop(toEnd) { + if (!this._easeMove) { + return this; + } + + this._easeMove.stop(toEnd); + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/FadeMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/FadeMethods.js new file mode 100644 index 000000000..8db7ef932 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/FadeMethods.js @@ -0,0 +1,86 @@ +import { FadeIn, FadeOutDestroy } from '../fade/Fade.js'; +import { WaitComplete } from '../utils/WaitEvent.js'; +import GetParentSizerMethods from './GetParentSizerMethods.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +var OnInitFade = function (gameObject, fade) { + // Route 'complete' of fade to gameObject + fade.completeEventName = undefined; + fade.on('complete', function () { + if (fade.completeEventName) { + gameObject.emit(fade.completeEventName, gameObject); + fade.completeEventName = undefined; + } + }) + + // Update local state + fade.on('update', function () { + var parent = GetParentSizerMethods.getParentSizer(gameObject); + if (parent) { + parent.resetChildAlphaState(gameObject); + } + }) +} + +export default { + fadeIn(duration, alpha) { + if (IsPlainObject(duration)) { + var config = duration; + duration = config.duration; + alpha = config.alpha; + } + + var isInit = (this._fade === undefined); + + this._fade = FadeIn(this, duration, alpha, this._fade); + + if (isInit) { + OnInitFade(this, this._fade); + } + + this._fade.completeEventName = 'fadein.complete'; + + return this; + }, + + fadeInPromise(duration, alpha) { + this.fadeIn(duration, alpha); + return WaitComplete(this._fade); + }, + + fadeOutDestroy(duration, destroyMode) { + if (IsPlainObject(duration)) { + var config = duration; + duration = config.duration; + destroyMode = config.destroy; + } + + var isInit = (this._fade === undefined); + + this._fade = FadeOutDestroy(this, duration, destroyMode, this._fade); + + if (isInit) { + OnInitFade(this, this._fade); + } + + this._fade.completeEventName = 'fadeout.complete'; + + return this; + }, + + fadeOutDestroyPromise(duration, destroyMode) { + this.fadeOutDestroy(duration, destroyMode); + return WaitComplete(this._fade); + }, + + fadeOut(duration) { + this.fadeOutDestroy(duration, false); + return this; + }, + + fadeOutPromise(duration) { + this.fadeOut(duration); + return WaitComplete(this._fade); + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetAllChildrenSizers.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetAllChildrenSizers.js new file mode 100644 index 000000000..0a5b3ddca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetAllChildrenSizers.js @@ -0,0 +1,14 @@ +var GetAllChildrenSizers = function (out) { + if (out === undefined) { + out = []; + } + var startIdx = out.length; + var children = this.getChildrenSizers(out); + var endIdx = out.length; + for (var i = startIdx; i < endIdx; i++) { + children[i].getAllChildrenSizers(out); + } + + return out; +} +export default GetAllChildrenSizers; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildHeight.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildHeight.js new file mode 100644 index 000000000..39b21a747 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildHeight.js @@ -0,0 +1,17 @@ +import { GetDisplayHeight } from '../../../plugins/utils/size/GetDisplaySize.js'; + +var GetChildHeight = function (child) { + var childHeight; + if (child.isRexSizer) { // Sizer game object + childHeight = Math.max(child.minHeight, child.childrenHeight); + } else { // Normal game object + if (child.minHeight !== undefined) { // Force minHeight + childHeight = child.minHeight; + } else { + childHeight = GetDisplayHeight(child); + } + } + return childHeight; +} + +export default GetChildHeight; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildWidth.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildWidth.js new file mode 100644 index 000000000..0cc8c826c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildWidth.js @@ -0,0 +1,18 @@ +import { GetDisplayWidth } from '../../../plugins/utils/size/GetDisplaySize.js'; + +var GetChildWidth = function (child) { + var childWidth; + if (child.isRexSizer) { // Sizer game object + childWidth = Math.max(child.minWidth, child.childrenWidth); + } else { // Normal game object + if (child.minWidth !== undefined) { // Force minWidth + childWidth = child.minWidth; + } else { + childWidth = GetDisplayWidth(child); + } + } + + return childWidth; +} + +export default GetChildWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildrenHeight.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildrenHeight.js new file mode 100644 index 000000000..6ce92c6e5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildrenHeight.js @@ -0,0 +1,6 @@ +// Override +var GetChildrenHeight = function () { + return 0; +} + +export default GetChildrenHeight; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildrenSizers.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildrenSizers.js new file mode 100644 index 000000000..d46b3bb14 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildrenSizers.js @@ -0,0 +1,8 @@ +// Default method +var GetChildrenSizers = function(out) { + if (out === undefined) { + out = []; + } + return out; +} +export default GetChildrenSizers; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildrenWidth.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildrenWidth.js new file mode 100644 index 000000000..5284072d5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildrenWidth.js @@ -0,0 +1,6 @@ +// Override +var GetChildrenWidth = function () { + return 0; +} + +export default GetChildrenWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetElement.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetElement.js new file mode 100644 index 000000000..9e840eb4d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetElement.js @@ -0,0 +1,41 @@ +var GetElement = function (mapNameList, recursive) { + if (typeof (mapNameList) === 'string') { + mapNameList = mapNameList.split('.'); + } + if (mapNameList.length === 0) { + return undefined; + } + + var name = mapNameList.shift(), + element = null; + if (name.charAt(0) === '#') { // Get element by name + name = name.substring(1); + element = this.getByName(name, recursive); + } else if (name.indexOf('[') === (-1)) { // Get element by key + if (this.childrenMap) { + element = this.childrenMap[name]; + } + } else { // Get element by key[] + var innerMatch = name.match(RE_OBJ); + if (innerMatch != null) { + if (this.childrenMap) { + var elements = this.childrenMap[innerMatch[1]]; + if (elements) { + element = elements[innerMatch[2]]; + } + } + } + } + + if (mapNameList.length === 0) { + return element; + } else if (element && element.childrenMap) { + return element.getElement(mapNameList); + } else { + return null; + } +}; + +const RE_OBJ = /(\S+)\[(\d+)\]/i; + +export default GetElement; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetExpandedChildHeight.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetExpandedChildHeight.js new file mode 100644 index 000000000..889b74c9e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetExpandedChildHeight.js @@ -0,0 +1,6 @@ +// Override +var GetExpandedChildHeight = function (child, parentHeight) { + return parentHeight; +} + +export default GetExpandedChildHeight; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetExpandedChildWidth.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetExpandedChildWidth.js new file mode 100644 index 000000000..37be00767 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetExpandedChildWidth.js @@ -0,0 +1,6 @@ +// Override +var GetExpandedChildWidth = function (child, parentWidth) { + return parentWidth; +} + +export default GetExpandedChildWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetParentSizerMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetParentSizerMethods.js new file mode 100644 index 000000000..e047af02d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetParentSizerMethods.js @@ -0,0 +1,56 @@ +var GetParent = function (gameObject, name) { + var parent = null; + if (name === undefined) { + if (gameObject.hasOwnProperty('rexContainer')) { + parent = gameObject.rexContainer.parent; + if (parent) { + if (!parent.isRexSizer) { + // Try to get sizer parent + parent = GetParent(parent); + } + } else { + parent = null; + } + } + + } else { + parent = GetParent(gameObject); + while (parent) { + if (parent.name === name) { + break; + } + parent = GetParent(parent); + } + } + return parent; +} + +var GetTopmostParent = function (gameObject) { + var parent = GetParent(gameObject); + while (parent) { + gameObject = parent; + parent = GetParent(parent); + } + return gameObject; +} + + +export default { + getParentSizer(gameObject, name) { + if (typeof (gameObject) === 'string') { + name = gameObject; + gameObject = undefined; + } + if (gameObject === undefined) { + gameObject = this; + } + return GetParent(gameObject, name); + }, + + getTopmostSizer(gameObject) { + if (gameObject === undefined) { + gameObject = this; + } + return GetTopmostParent(gameObject); + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetShownChildrenMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetShownChildrenMethods.js new file mode 100644 index 000000000..81af7c160 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetShownChildrenMethods.js @@ -0,0 +1,43 @@ +export default { + getShownChildren(out) { + if (out === undefined) { + out = []; + } + var children = this.children, + child; + for (var i = 0, cnt = children.length; i < cnt; i++) { + child = children[i]; + if (child.rexSizer && child.rexSizer.hidden) { // Don't add hidden child + continue; + } + + out.push(child); + } + + return out; + }, + + getAllShownChildren(out) { + if (out === undefined) { + out = []; + } + + var queue = [this]; + while (queue.length > 0) { + var current = queue.shift(); + if (current.rexSizer && current.rexSizer.hidden) { + continue; + } + + if (current !== this) { + out.push(current); + } + + if (current.isRexContainerLite) { + queue.push(...current.children); + } + } + + return out; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetSizerConfig.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetSizerConfig.js new file mode 100644 index 000000000..9034ff619 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetSizerConfig.js @@ -0,0 +1,8 @@ +import GetSizerConfig from '../utils/GetSizerConfig.js'; + +export default function (gameObject) { + if (gameObject === undefined) { + gameObject = this; + } + return GetSizerConfig(gameObject); +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/HideMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/HideMethods.js new file mode 100644 index 000000000..456c4a4d1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/HideMethods.js @@ -0,0 +1,30 @@ +import { + Show, + Hide, + IsShown, +} from '../utils/Hide.js'; + +export default { + show(gameObject) { + if (gameObject === undefined) { + gameObject = this; + } + Show(gameObject, false); + return this; + }, + + hide(gameObject) { + if (gameObject === undefined) { + gameObject = this; + } + Hide(gameObject, true); + return this; + }, + + isShow(gameObject) { + if (gameObject === undefined) { + gameObject = this; + } + return IsShown(gameObject); + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/IsInTouching.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/IsInTouching.js new file mode 100644 index 000000000..53f39a744 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/IsInTouching.js @@ -0,0 +1,19 @@ +import IsPointerInBounds from '../../../plugins/utils/input/IsPointerInBounds.js'; +import IsGameObject from '../../../plugins/utils/system/IsGameObject.js'; + +var IsInTouching = function (pointer, gameObject) { + if (IsGameObject(pointer) || (typeof (pointer) === 'string')) { + gameObject = pointer; + pointer = undefined; + } + + if (gameObject === undefined) { + gameObject = this; + } else if (typeof (gameObject) === 'string') { + gameObject = this.getElement(gameObject); + } + + return IsPointerInBounds(gameObject, pointer); +} + +export default IsInTouching; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/Layout.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/Layout.js new file mode 100644 index 000000000..67bf7d7c7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/Layout.js @@ -0,0 +1,19 @@ +var Layout = function () { + // Save scale + var scaleXSave = this.scaleX; + var scaleYSave = this.scaleY; + var scale1 = (scaleXSave === 1) && (scaleYSave === 1); + if (!scale1) { + this.setScale(1); + } + + // Run layout with scale = 1 + this.runLayout(); + + // Restore scale + if (!scale1) { + this.setScale(scaleXSave, scaleYSave); + } + return this; +} +export default Layout; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/LayoutBackgrounds.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/LayoutBackgrounds.js new file mode 100644 index 000000000..3cfa39b55 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/LayoutBackgrounds.js @@ -0,0 +1,41 @@ +import ResizeGameObject from '../../../plugins/utils/size/ResizeGameObject.js'; +import PreLayoutChild from './utils/PreLayoutChild.js'; +import LayoutChild from './utils/LayoutChild.js'; + +const ALIGN_CENTER = Phaser.Display.Align.CENTER; + +var LayoutBackgrounds = function () { + if (this.backgroundChildren === undefined) { + return; + } + var backgrounds = this.backgroundChildren; + + var startX = this.left, + startY = this.top; + var parentWidth = this.width, + parentHeight = this.height; + var child, childConfig, padding, + x, y, width, height; + for (var i = 0, cnt = backgrounds.length; i < cnt; i++) { + child = backgrounds[i]; + childConfig = child.rexSizer; + if (childConfig.hidden) { + continue; + } + + padding = childConfig.padding; + + PreLayoutChild.call(this, child); + + x = startX + padding.left; + y = startY + padding.top; + width = parentWidth - padding.left - padding.right; + height = parentHeight - padding.top - padding.bottom; + + ResizeGameObject(child, width, height); + + LayoutChild.call(this, child, x, y, width, height, ALIGN_CENTER); + } +} + +export default LayoutBackgrounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/LayoutChildren.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/LayoutChildren.js new file mode 100644 index 000000000..979b7317b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/LayoutChildren.js @@ -0,0 +1,6 @@ +// Override +var LayoutChildren = function () { + +} + +export default LayoutChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/Methods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/Methods.js new file mode 100644 index 000000000..b8df93508 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/Methods.js @@ -0,0 +1,108 @@ +import GetSizerConfig from './GetSizerConfig.js'; +import GetChildPrevState from '../utils/GetChildPrevState.js'; +import PushIntoBounds from './PushIntoBounds.js'; +import DrawBounds from './DrawBounds.js'; +import AddChildMethods from './AddChildMethods.js'; +import RemoveChildMethods from './RemoveChildMethods.js'; +import AddChildrenMap from './AddChildrenMap.js'; +import RemoveChildrenMap from './RemoveChildrenMap.js'; +import GetElement from './GetElement.js'; +import PaddingMethods from './PaddingMethods.js'; +import ResolveWidth from './ResolveWidth.js'; +import ResolveChildrenWidth from './ResolveChildrenWidth.js'; +import ResolveHeight from './ResolveHeight.js'; +import PostResolveSize from './PostResolveSize.js'; +import GetChildWidth from './GetChildWidth.js'; +import GetChildHeight from './GetChildHeight.js'; +import GetExpandedChildWidth from './GetExpandedChildWidth.js'; +import GetExpandedChildHeight from './GetExpandedChildHeight.js'; +import GetChildrenWidth from './GetChildrenWidth.js'; +import GetChildrenHeight from './GetChildrenHeight.js'; +import GetAllChildrenSizers from './GetAllChildrenSizers.js'; +import GetChildrenSizers from './GetChildrenSizers.js'; +import GetShownChildrenMethods from './GetShownChildrenMethods.js'; +import PreLayout from './PreLayout.js'; +import Layout from './Layout.js'; +import RunLayout from './RunLayout.js'; +import LayoutChildren from './LayoutChildren.js'; +import PostLayout from './PostLayout.js'; +import RunWidthWrap from './RunWidthWrap.js'; + +import SetAnchor from './SetAnchor.js'; +import ScaleMethods from './ScaleMethods.js'; +import FadeMethods from './FadeMethods.js'; +import EaseMoveMethods from './EaseMoveMethods.js'; +import ShakeMethods from './ShakeMethods.js'; +import EaseDataMethods from './EaseDataMethods.js'; +import HideMethods from './HideMethods.js'; +import ModalMethods from './ModalMethods.js'; +import IsInTouching from './IsInTouching.js'; +import PointToChild from './PointToChild.js'; +import GetParentSizerMethods from './GetParentSizerMethods.js'; +import LayoutBackgrounds from './LayoutBackgrounds.js'; +import SetDraggable from './SetDraggable.js'; +import ClickMethods from './ClickMethods.js'; +import ClickOutsideMethods from './ClickOutsideMethods.js'; +import TouchingMethods from './TouchingMethods.js'; +import SetChildrenInteractive from './SetChildrenInteractive.js'; +import BroadcastEvent from './BroadcastEvent.js'; + +var methods = { + getSizerConfig: GetSizerConfig, + getChildPrevState: GetChildPrevState, + pushIntoBounds: PushIntoBounds, + drawBounds: DrawBounds, + resolveWidth: ResolveWidth, + resolveChildrenWidth: ResolveChildrenWidth, + resolveHeight: ResolveHeight, + postResolveSize: PostResolveSize, + getChildWidth: GetChildWidth, + getChildHeight: GetChildHeight, + getExpandedChildWidth: GetExpandedChildWidth, + getExpandedChildHeight: GetExpandedChildHeight, + + getChildrenWidth: GetChildrenWidth, + getChildrenHeight: GetChildrenHeight, + addChildrenMap: AddChildrenMap, + addElement: AddChildrenMap, + removeChildrenMap: RemoveChildrenMap, + getElement: GetElement, + getAllChildrenSizers: GetAllChildrenSizers, + getChildrenSizers: GetChildrenSizers, + preLayout: PreLayout, + layout: Layout, + runLayout: RunLayout, + layoutChildren: LayoutChildren, + runWidthWrap: RunWidthWrap, + layoutBackgrounds: LayoutBackgrounds, + postLayout: PostLayout, + + setAnchor: SetAnchor, + isInTouching: IsInTouching, + pointToChild: PointToChild, + setDraggable: SetDraggable, + setChildrenInteractive: SetChildrenInteractive, + broadcastEvent: BroadcastEvent, + +}; + +Object.assign( + methods, + PaddingMethods, + AddChildMethods, + RemoveChildMethods, + GetParentSizerMethods, + ScaleMethods, + FadeMethods, + EaseMoveMethods, + ShakeMethods, + EaseDataMethods, + ClickMethods, + ClickOutsideMethods, + TouchingMethods, + HideMethods, + ModalMethods, + GetShownChildrenMethods, +); + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ModalMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ModalMethods.js new file mode 100644 index 000000000..144c45c35 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ModalMethods.js @@ -0,0 +1,41 @@ +import { Modal, ModalClose } from '../modal/Modal.js'; +import IsFunction from '../../../plugins/utils/object/IsFunction.js'; + +export default { + // Override + // onCreateModalBehavior(self, config) { }, + + modal(config, onClose) { + if (IsFunction(config)) { + onClose = config; + config = undefined; + } + + if (this._modalBehavior === undefined) { + if (this.onCreateModalBehavior) { + this.onCreateModalBehavior(this, config); + } + this._modalBehavior = Modal(this, config); + } + + if (onClose) { + this._modalBehavior.once('close', onClose); + } + + this._modalBehavior.requestOpen(); + + return this; + }, + + modalPromise(config) { + var self = this; + return new Promise(function (resolve, reject) { + self.modal(config, resolve); + }); + }, + + modalClose(closeEventData) { + ModalClose(this, closeEventData); + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PaddingMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PaddingMethods.js new file mode 100644 index 000000000..038e6bd56 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PaddingMethods.js @@ -0,0 +1,36 @@ +import { GetPadding, SetPadding } from '../../../plugins/utils/padding/PaddingMethods.js'; + +export default { + getInnerPadding(key) { + return GetPadding(this.space, key); + }, + + setInnerPadding(key, value) { + SetPadding(this.space, key, value); + return this; + }, + + getOuterPadding(key) { + return GetPadding(this.getSizerConfig(this).padding, key); + }, + + setOuterPadding(key, value) { + SetPadding(this.getSizerConfig(this).padding, key, value); + return this; + }, + + getChildOuterPadding(child, key) { + if (typeof (child) === 'string') { + child = this.getElement(child); + } + return GetPadding(this.getSizerConfig(child).padding, key); + }, + + setChildOuterPadding(child, key, value) { + if (typeof (child) === 'string') { + child = this.getElement(child); + } + SetPadding(this.getSizerConfig(child).padding, key, value); + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PointToChild.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PointToChild.js new file mode 100644 index 000000000..02539fdb4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PointToChild.js @@ -0,0 +1,41 @@ +import IsFunction from '../../../plugins/utils/object/IsFunction.js'; +import IsArray from '../../../plugins/utils/object/IsArray.js'; +import ContainsPoint from '../utils/ContainsPoint.js'; + +var PointToChild = function (x, y, preTest, postTest, children) { + if (!IsFunction(preTest)) { + children = preTest; + preTest = undefined; + postTest = undefined; + } + + if (children === undefined) { + if (this.sizerChildren) { + children = this.sizerChildren; + } else { + children = this.children; + } + } + + if (IsArray(children)) { + var child; + for (var i = 0, cnt = children.length; i < cnt; i++) { + child = children[i]; + if (ContainsPoint(child, x, y, preTest, postTest)) { + return child; + } + } + } else { + var child; + for (var key in children) { + child = children[key]; + if (ContainsPoint(child, x, y, preTest, postTest)) { + return child; + } + } + } + + return null; +} + +export default PointToChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PostLayout.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PostLayout.js new file mode 100644 index 000000000..0c65af357 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PostLayout.js @@ -0,0 +1,7 @@ +var PostLayout = function (parent, newWidth, newHeight) { + if (this._anchor) { + this._anchor.updatePosition(); + } + return this; +} +export default PostLayout; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PostResolveSize.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PostResolveSize.js new file mode 100644 index 000000000..6aed56dd5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PostResolveSize.js @@ -0,0 +1,4 @@ +var PostResolveSize = function (width, height) { +} + +export default PostResolveSize; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PreLayout.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PreLayout.js new file mode 100644 index 000000000..686955872 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PreLayout.js @@ -0,0 +1,15 @@ +var PreLayout = function () { + this._childrenWidth = undefined; + this._childrenHeight = undefined; + + var children = this.getChildrenSizers(), + child; + for (var i = 0, cnt = children.length; i < cnt; i++) { + child = children[i]; + if (child.ignoreLayout) { + continue; + } + child.preLayout(); + } +} +export default PreLayout; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PushIntoBounds.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PushIntoBounds.js new file mode 100644 index 000000000..e9b7fdf52 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/PushIntoBounds.js @@ -0,0 +1,15 @@ +import GetViewport from '../../../plugins/utils/system/GetViewport.js'; + +var PushIntoBounds = function (bounds) { + if (bounds === undefined) { + bounds = GetViewport(this.scene); + } + + this.left = Math.max(this.left, bounds.left); + this.right = Math.min(this.right, bounds.right); + this.top = Math.max(this.top, bounds.top); + this.bottom = Math.min(this.bottom, bounds.bottom); + return this; +} + +export default PushIntoBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/RemoveChildMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/RemoveChildMethods.js new file mode 100644 index 000000000..8f1d88272 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/RemoveChildMethods.js @@ -0,0 +1,39 @@ +import RemoveChild from './utils/RemoveChild.js'; +import GetParentSizerMethods from './GetParentSizerMethods.js'; + +const RemoveItem = Phaser.Utils.Array.Remove; + +export default { + removeFromParentSizer() { + var parent = GetParentSizerMethods.getParentSizer(gameObject); + if (parent) { + parent.remove(this); + } + return this; + }, + + removeBackground(gameObject, destroyChild) { + if (this.backgroundChildren === undefined) { + return this; + } + + if (this.getParentSizer(gameObject) !== this) { + return this; + } + + RemoveItem(this.backgroundChildren, gameObject); + RemoveChild.call(this, gameObject, destroyChild); + return this; + }, + + removeAllBackgrounds(destroyChild) { + if (this.backgroundChildren === undefined) { + return this; + } + + for (var i = this.backgroundChildren.length - 1; i >= 0; i--) { + this.remove(this.backgroundChildren[i], destroyChild); + } + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/RemoveChildrenMap.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/RemoveChildrenMap.js new file mode 100644 index 000000000..e8087f593 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/RemoveChildrenMap.js @@ -0,0 +1,17 @@ + +var RemoveChildrenMap = function (key) { + if (typeof (key) === 'object') { + var gameObject = key; + for (var key in this.childrenMap) { + if (this.childrenMap[key] === gameObject) { + delete this.childrenMap[key]; + return this; + } + } + } + + delete this.childrenMap[key]; + return this; +} + +export default RemoveChildrenMap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ResolveChildrenWidth.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ResolveChildrenWidth.js new file mode 100644 index 000000000..de20be2fc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ResolveChildrenWidth.js @@ -0,0 +1,14 @@ +var ResolveChildrenWidth = function (parentWidth) { + // Resolve width of sizer children + var child, childWidth; + for (var i in this.sizerChildren) { + child = this.sizerChildren[i]; + if (child && child.isRexSizer && !child.ignoreLayout) { + childWidth = this.getExpandedChildWidth(child, parentWidth); + childWidth = child.resolveWidth(childWidth); + child.resolveChildrenWidth(childWidth); + } + } +} + +export default ResolveChildrenWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ResolveHeight.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ResolveHeight.js new file mode 100644 index 000000000..63cc461f4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ResolveHeight.js @@ -0,0 +1,14 @@ +var ResolveHeight = function (height) { + var minHeight = Math.max(this.childrenHeight, this.minHeight); + if (height === undefined) { + height = minHeight; + } else { + if (minHeight > height) { + // Warning + } + } + + return height; +} + +export default ResolveHeight; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ResolveWidth.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ResolveWidth.js new file mode 100644 index 000000000..b1e54851f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ResolveWidth.js @@ -0,0 +1,16 @@ +var ResolveWidth = function (width) { + if (width === undefined) { + width = Math.max(this.childrenWidth, this.minWidth); + } else { + /* + var minWidth = Math.max(this.childrenWidth, this.minWidth); + if (minWidth > width) { + // Warning + } + */ + } + + return width; +} + +export default ResolveWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/RunLayout.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/RunLayout.js new file mode 100644 index 000000000..ec91878f0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/RunLayout.js @@ -0,0 +1,47 @@ +// Override +var RunLayout = function (parent, newWidth, newHeight) { + // Skip hidden or !dirty sizer + if (this.ignoreLayout) { + return this; + } + + var isTopmostParent = !parent; + // Preprocessor, top parent only + if (isTopmostParent) { + this.preLayout(); + } + + // Calculate parent width + newWidth = this.resolveWidth(newWidth); + // Calculate all children width, run width wrap + if (isTopmostParent) { + this.resolveChildrenWidth(newWidth); + this.runWidthWrap(newWidth); + } + // Calculate parent height + newHeight = this.resolveHeight(newHeight); + // The last chance of resolving size + this.postResolveSize(newWidth, newHeight); + // Resize parent + this.resize(newWidth, newHeight); + + if (this.sizerEventsEnable) { + if (this.layoutedChildren === undefined) { + this.layoutedChildren = []; + } + } + + // Layout children + this.layoutChildren(); + + // Layout background children + this.layoutBackgrounds(); + + if (this.sizerEventsEnable) { + this.emit('postlayout', this.layoutedChildren, this); + this.layoutedChildren.length = 0; + } + + return this.postLayout(); +} +export default RunLayout; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/RunWidthWrap.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/RunWidthWrap.js new file mode 100644 index 000000000..1c489fdb6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/RunWidthWrap.js @@ -0,0 +1,23 @@ +// Default method +var RunWidthWrap = function (parentWidth) { + var child, childWidth; + for (var i in this.sizerChildren) { + child = this.sizerChildren[i]; + if ( + (!child) || + (child.isRexSizer && child.ignoreLayout) || + (!child.runWidthWrap) + ) { + continue; + } + + childWidth = this.getExpandedChildWidth(child, parentWidth); + if (child.isRexSizer) { + childWidth = child.resolveWidth(childWidth); + } + child.runWidthWrap(childWidth); + } + return this; +} + +export default RunWidthWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ScaleMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ScaleMethods.js new file mode 100644 index 000000000..3adcb9d0b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ScaleMethods.js @@ -0,0 +1,121 @@ +import PopUp from '../../../plugins/popup.js'; +import ScaleDownDestroy from '../../../plugins/scale-down-destroy.js'; +import Yoyo from '../../../plugins/behaviors/scale/Yoyo.js'; +import { WaitComplete } from '../utils/WaitEvent.js' +import GetParentSizerMethods from './GetParentSizerMethods.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +var OnInitScale = function (gameObject, scale) { + // Route 'complete' of scale to gameObject + scale.completeEventName = undefined; + scale.on('complete', function () { + if (scale.completeEventName) { + gameObject.emit(scale.completeEventName, gameObject); + scale.completeEventName = undefined; + } + }) + + // Update local state + scale.on('update', function () { + var parent = GetParentSizerMethods.getParentSizer(gameObject) + if (parent) { + parent.resetChildPositionState(gameObject); + } + }) +} + +export default { + popUp(duration, orientation, ease) { + if (IsPlainObject(duration)) { + var config = duration; + duration = config.duration; + orientation = config.orientation; + ease = config.ease; + } + + var isInit = (this._scaleBehavior === undefined); + + this._scaleBehavior = PopUp(this, duration, orientation, ease, this._scaleBehavior); + + if (isInit) { + OnInitScale(this, this._scaleBehavior); + } + + this._scaleBehavior.completeEventName = 'popup.complete'; + + return this; + }, + + popUpPromise(duration, orientation, ease) { + this.popUp(duration, orientation, ease); + return WaitComplete(this._scaleBehavior); + }, + + scaleDownDestroy(duration, orientation, ease, destroyMode) { + if (IsPlainObject(duration)) { + var config = duration; + duration = config.duration; + orientation = config.orientation; + ease = config.ease; + destroyMode = config.destroy; + } + + var isInit = (this._scaleBehavior === undefined); + + this._scaleBehavior = ScaleDownDestroy(this, duration, orientation, ease, destroyMode, this._scaleBehavior); + + if (isInit) { + OnInitScale(this, this._scaleBehavior); + } + + this._scaleBehavior.completeEventName = 'scaledown.complete'; + + return this; + }, + + scaleDownDestroyPromise(duration, orientation, ease, destroyMode) { + this.scaleDownDestroy(duration, orientation, ease, destroyMode); + return WaitComplete(this._scaleBehavior); + }, + + scaleDown(duration, orientation, ease) { + this.scaleDownDestroy(duration, orientation, ease, false); + return this; + }, + + scaleDownPromise(duration, orientation, ease) { + this.scaleDown(duration, orientation, ease); + return WaitComplete(this._scaleBehavior); + }, + + scaleYoyo(duration, peakValue, repeat, orientation, ease) { + if (IsPlainObject(duration)) { + var config = duration; + duration = config.duration; + peakValue = config.peakValue; + repeat = config.repeat; + orientation = config.orientation; + ease = config.ease; + } + + var isInit = (this._scaleBehavior === undefined); + + this._scaleBehavior = Yoyo(this, duration, peakValue, repeat, orientation, ease, this._scaleBehavior); + + if (isInit) { + OnInitScale(this, this._scaleBehavior); + } + + this._scaleBehavior.completeEventName = 'scaleyoyo.complete'; + + return this; + }, + + scaleYoyoPromise(duration, peakValue, repeat, orientation, ease) { + this.scaleYoyo(duration, peakValue, repeat, orientation, ease); + return WaitComplete(this._scaleBehavior); + }, + + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/SetAnchor.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/SetAnchor.js new file mode 100644 index 000000000..9733271a6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/SetAnchor.js @@ -0,0 +1,34 @@ +import Anchor from '../anchor/Anchor.js'; + +var SetAnchor = function (config) { + if (config === undefined) { + config = {}; + } + + // Assign default onResizeCallback if not given + var hasMinWidth = config.hasOwnProperty('width'); + var hasMinHeight = config.hasOwnProperty('height'); + var hasOnResizeCallback = config.hasOwnProperty('onResizeCallback'); + if ((hasMinWidth || hasMinHeight) && !hasOnResizeCallback) { + config.onResizeCallback = function (width, height, sizer) { + if (hasMinWidth) { + sizer.setMinWidth(width); + } + + if (hasMinHeight) { + sizer.setMinHeight(height); + } + + sizer.layout(); + } + } + + if (this._anchor === undefined) { + this._anchor = new Anchor(this, config); + } else { + this._anchor.resetFromJSON(config) + } + return this; +} + +export default SetAnchor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/SetChildrenInteractive.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/SetChildrenInteractive.js new file mode 100644 index 000000000..1a1eaf082 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/SetChildrenInteractive.js @@ -0,0 +1,8 @@ +import SetChildrenInteractive from '../utils/setchildreninteractive/SetChildrenInteractive.js'; + +var SetChildrenInteractiveWrap = function (config) { + SetChildrenInteractive(this, config); + return this; +} + +export default SetChildrenInteractiveWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/SetDraggable.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/SetDraggable.js new file mode 100644 index 000000000..61f89badb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/SetDraggable.js @@ -0,0 +1,47 @@ +var SetDraggable = function (senser, draggable) { + var senserType = typeof (senser); + if (senserType === 'string') { + var senserName = senser; + senser = this.getElement(senserName); + if (!senser) { + console.error(`Can get element '${senserName}'`); + return this; + } + } else if ((senser === undefined) || (senserType != 'object')) { + draggable = senser; + senser = this; + } + if (draggable === undefined) { + draggable = true; + } + + if (senser.input && senser.input._dragTopmostSizer) { + // Draggable is already registered + senser.input.draggable = draggable; + } else if (draggable) { + // Register draggable + senser.setInteractive(); + senser.scene.input.setDraggable(senser); + senser + .on('drag', function (pointer, dragX, dragY) { + var topmostParent = this.getTopmostSizer(); + topmostParent.x += (dragX - senser.x); + topmostParent.y += (dragY - senser.y); + topmostParent.emit('sizer.drag', pointer, dragX, dragY); + }, this) + .on('dragstart', function (pointer, dragX, dragY) { + var topmostParent = this.getTopmostSizer(); + topmostParent.emit('sizer.dragstart', pointer, dragX, dragY); + }, this) + .on('dragend', function (pointer, dragX, dragY, dropped) { + var topmostParent = this.getTopmostSizer(); + topmostParent.emit('sizer.dragend', pointer, dragX, dragY, dropped); + }, this) + senser.input._dragTopmostSizer = true; + } else { + // Not draggable and draggable is not registered yet, do nothing + } + return this; +} + +export default SetDraggable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ShakeMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ShakeMethods.js new file mode 100644 index 000000000..6a1bd46b9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/ShakeMethods.js @@ -0,0 +1,53 @@ +import Shake from '../shake/Shake.js'; +import { WaitComplete } from '../utils/WaitEvent.js' + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +var OnInitShake = function (gameObject, shake) { + // Route 'complete' of shake to gameObject + shake.on('complete', function () { + gameObject.emit('shake.complete', gameObject); + }) + + // Shake effect won't change position +} + +export default { + shake(duration, magnitude, magnitudeMode) { + if (IsPlainObject(duration)) { + var config = duration; + duration = config.duration; + magnitude = config.magnitude; + magnitudeMode = config.magnitudeMode; + } + + if (this._shake === undefined) { + this._shake = new Shake(this, { + mode: 0, + magnitudeMode: 1 + }); + OnInitShake(this, this._shake); + } + + if (duration !== undefined) { + this._shake.setDuration(duration); + } + + if (magnitude !== undefined) { + this._shake.setMagnitude(magnitude); + } + + if (magnitudeMode !== undefined) { + this._shake.setMagnitudeMode(magnitudeMode); + } + + this._shake.shake(); + + return this; + }, + + shakePromise(duration, alpha) { + this.shake(duration, alpha); + return WaitComplete(this._shake); + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/TouchingMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/TouchingMethods.js new file mode 100644 index 000000000..05d6c32fb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/TouchingMethods.js @@ -0,0 +1,118 @@ +import InTouching from '../intouching/InTouching.js'; +import IsPointerInBounds from '../../../plugins/utils/input/IsPointerInBounds.js'; + +export default { + isPointerInBounds(target) { + if (target === undefined) { + target = this; + } else if (typeof (target) === 'string') { + target = this.getElement(target); + } + + if (!target) { + return false; + } + + return IsPointerInBounds(target); + }, + + onTouching(gameObject, callback, scope, config) { + if (!gameObject) { + return this; + } + + if (typeof (gameObject) === 'function') { + config = scope; + scope = callback; + callback = gameObject; + gameObject = this; + } + + if (gameObject._inTouching === undefined) { + gameObject._inTouching = new InTouching(gameObject, config); + } + gameObject._inTouching.on('intouch', callback, scope); + + return this; + }, + + offTouching(gameObject, callback, scope) { + if (typeof (gameObject) === 'function') { + scope = callback; + callback = gameObject; + gameObject = this; + } + + if (gameObject._inTouching === undefined) { + return this; + } + gameObject._inTouching.off('intouch', callback, scope); + + return this; + }, + + onTouchingEnd(gameObject, callback, scope, config) { + if (!gameObject) { + return this; + } + + if (typeof (gameObject) === 'function') { + config = scope; + scope = callback; + callback = gameObject; + gameObject = this; + } + + if (gameObject._inTouching === undefined) { + gameObject._inTouching = new InTouching(gameObject, config); + } + gameObject._inTouching.on('touchend', callback, scope); + + return this; + }, + + offTouchingEnd(gameObject, callback, scope) { + if (typeof (gameObject) === 'function') { + scope = callback; + callback = gameObject; + gameObject = this; + } + + if (gameObject._inTouching === undefined) { + return this; + } + gameObject._inTouching.off('touchend', callback, scope); + + return this; + }, + + + enableTouching(gameObject, enabled) { + if (gameObject && typeof (gameObject) !== 'object') { + enabled = gameObject; + gameObject = this; + } + + if (gameObject._inTouching === undefined) { + return this; + } + gameObject._inTouching.setEnable(enabled); + + return this; + }, + + disableTouching(gameObject) { + if (gameObject && typeof (gameObject) !== 'object') { + gameObject = this; + } + + if (gameObject._inTouching === undefined) { + return this; + } + gameObject._inTouching.setEnable(false); + + return this; + }, + + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/AddChild.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/AddChild.js new file mode 100644 index 000000000..82d7c905b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/AddChild.js @@ -0,0 +1,16 @@ +import Container from '../../container/Container.js'; + +const ContainerAdd = Container.prototype.add; + +var AddChild = function (gameObject) { + ContainerAdd.call(this, gameObject); + + if (this.sizerEventsEnable) { + gameObject.emit('sizer.add', gameObject, this); + this.emit('add', gameObject, this); + } + + return this; +} + +export default AddChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/CheckSize.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/CheckSize.js new file mode 100644 index 000000000..cae321956 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/CheckSize.js @@ -0,0 +1,12 @@ +var CheckSize = function (child, parent) { + if (child.width < child.childrenWidth) { + // Warning + console.warn(`Layout width error: Parent=${parent.constructor.name}, Child=${child.constructor.name}`); + } + if (child.height < child.childrenHeight) { + // Warning + console.warn(`Layout height error: Parent=${parent.constructor.name}, Child=${child.constructor.name}`); + } +} + +export default CheckSize; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/ClearChildren.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/ClearChildren.js new file mode 100644 index 000000000..6a527b2be --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/ClearChildren.js @@ -0,0 +1,29 @@ +import Container from '../../container/Container.js'; + +const ContainerClear = Container.prototype.clear; + +var ClearChildren = function (destroyChild) { + if (this.backgroundChildren) { + this.backgroundChildren.length = 0; + } + + var fireRemoveEvent = !destroyChild && this.sizerEventsEnable; + var children; + if (fireRemoveEvent) { + children = this.getChildren([]); + } + + ContainerClear.call(this, destroyChild); + + if (fireRemoveEvent) { + var gameObject; + for (var i = 0, cnt = children.length; i < cnt; i++) { + gameObject = children[i]; + gameObject.emit('sizer.remove', gameObject, this); + this.emit('remove', gameObject, this); + } + } + return this; +} + +export default ClearChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/LayoutChild.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/LayoutChild.js new file mode 100644 index 000000000..f188a5edc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/LayoutChild.js @@ -0,0 +1,20 @@ +import AlignIn from '../../../../plugins/utils/actions/AlignIn.js'; + +var LayoutChild = function (child, x, y, width, height, align, offsetX, offsetY) { + AlignIn(child, x, y, width, height, align); + + if (offsetX !== undefined) { + child.x += offsetX; + } + if (offsetY !== undefined) { + child.y += offsetY; + } + + this.resetChildPositionState(child); + + if (this.sizerEventsEnable) { + child.emit('sizer.postlayout', child, this); + } +} + +export default LayoutChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/PreLayoutChild.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/PreLayoutChild.js new file mode 100644 index 000000000..05906a832 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/PreLayoutChild.js @@ -0,0 +1,10 @@ +import CopyState from '../../utils/CopyState'; + +var PreLayoutChild = function (child) { + if (this.sizerEventsEnable) { + CopyState(child, this.getChildPrevState(child)); + this.layoutedChildren.push(child); + } +} + +export default PreLayoutChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/RemoveChild.js b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/RemoveChild.js new file mode 100644 index 000000000..6b28d2a35 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/RemoveChild.js @@ -0,0 +1,19 @@ +import Container from '../../container/Container.js'; + +const RemoveItem = Phaser.Utils.Array.Remove; +const ContainerRemove = Container.prototype.remove; + +var RemoveChild = function (gameObject, destroyChild) { + if (this.isBackground(gameObject)) { + RemoveItem(this.backgroundChildren, gameObject); + } + ContainerRemove.call(this, gameObject, destroyChild); + + if (!destroyChild && this.sizerEventsEnable) { + gameObject.emit('sizer.remove', gameObject, this); + this.emit('remove', gameObject, this); + } + return this; +} + +export default RemoveChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/BBCodeText.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/BBCodeText.d.ts new file mode 100644 index 000000000..f4364757e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/BBCodeText.d.ts @@ -0,0 +1,2 @@ +import BBCodeText from '../../../plugins/bbcodetext'; +export default BBCodeText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/BBCodeText.js b/ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/BBCodeText.js new file mode 100644 index 000000000..dd09ba101 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/BBCodeText.js @@ -0,0 +1,2 @@ +import BBCodeText from '../../../plugins/bbcodetext.js'; +export default BBCodeText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/Factory.d.ts new file mode 100644 index 000000000..ef40eae87 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/Factory.d.ts @@ -0,0 +1,7 @@ +import BBCodeText from './BBCodeText'; + +export default function ( + x?: number, y?: number, + content?: string, + style?: BBCodeText.TextStyle +): BBCodeText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/Factory.js new file mode 100644 index 000000000..b737a8b65 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/Factory.js @@ -0,0 +1,13 @@ +import BBCodeText from './BBCodeText.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('BBCodeText', function (x, y, text, style) { + var gameObject = new BBCodeText(this.scene, x, y, text, style); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.BBCodeText', BBCodeText); + +export default BBCodeText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/buttons/AddChildMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/buttons/AddChildMethods.js new file mode 100644 index 000000000..da6ba4cea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/buttons/AddChildMethods.js @@ -0,0 +1,85 @@ +import Sizer from '../sizer/Sizer.js'; +import IsArray from '../../../plugins/utils/object/IsArray.js'; + +const SizerAdd = Sizer.prototype.add; +const SizerAddSpace = Sizer.prototype.addSpace; + +var Add = function (gameObject) { + var isNormalGameObject = !gameObject.isRexSpace; + var proportion = (!isNormalGameObject || this.buttonsExpand) ? 1 : 0; + + if (this.sizerChildren.length === 0) { // First element + if (isNormalGameObject) { + // Add space at head + var hasHeadSpace = (!this.buttonsExpand) && + ((this.buttonsAlign === 'right') || (this.buttonsAlign === 'center') || (this.buttonsAlign === 'bottom')); + if (hasHeadSpace) { + SizerAddSpace.call(this); + } + + SizerAdd.call(this, + gameObject, + { proportion: proportion, expand: true } + ); + + // Add space at tail + var hasTailSpace = (!this.buttonsExpand) && (this.buttonsAlign === 'center'); + if (hasTailSpace) { + SizerAddSpace.call(this); + } + this.hasTailSpace = hasTailSpace; + + } else { // A space + SizerAdd.call(this, + gameObject, + { proportion: proportion, expand: true } + ); + this.hasTailSpace = false; + + } + + } else { // Others + if (this.hasTailSpace) { + var lastIndex = this.sizerChildren.length - 1; + SizerAdd.call(this, + gameObject, + { index: lastIndex, proportion: proportion, expand: true } + ); + + } else { + SizerAdd.call(this, + gameObject, + { proportion: proportion, expand: true } + ); + } + + } + + // Space or other game object as button + if (isNormalGameObject) { + this.buttonGroup.add(gameObject); + } + + return this; +}; + +export default { + addButton(gameObject) { + if (IsArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + Add.call(this, gameObjects[i]); + } + } else { + Add.call(this, gameObject); + } + return this; + }, + + addButtons(gameObjects) { + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + Add.call(this, gameObjects[i]); + } + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/buttons/Buttons.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/buttons/Buttons.d.ts new file mode 100644 index 000000000..06bf44dc4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/buttons/Buttons.d.ts @@ -0,0 +1,95 @@ +// import * as Phaser from 'phaser'; +import Sizer from '../sizer/Sizer'; +import { IConfig as IConfigButtons } from '../utils/buttongroup/Buttons'; + +export default Buttons; + +declare namespace Buttons { + + type AlignTypes = 'left' | 'top' | 'right' | 'bottom' | 'center'; + + interface IConfig extends Sizer.IConfig, IConfigButtons { + background?: Phaser.GameObjects.GameObject, + + buttons?: Phaser.GameObjects.GameObject[], + + expand?: boolean, + + align?: AlignTypes, + } +} + +declare class Buttons extends Sizer { + constructor( + scene: Phaser.Scene, + config?: Buttons.IConfig + ); + + emitButtonClick( + index: number | Phaser.GameObjects.GameObject + ): this; + + setButtonEnable( + index?: number | Phaser.GameObjects.GameObject | boolean, + enable?: boolean + ): this; + + toggleButtonEnable( + index?: number | Phaser.GameObjects.GameObject + ): this; + + getButtonEnable( + index: number | Phaser.GameObjects.GameObject + ): boolean; + + getButton( + index: number + ): Phaser.GameObjects.GameObject | null; + + addButton( + gameObject: Phaser.GameObjects.GameObject + ): this; + + removeButton( + gameObject: Phaser.GameObjects.GameObject, + destroyChild?: boolean + ): this; + + clearButtons( + destroyChild?: boolean + ): this; + + showButton( + index: number | Phaser.GameObjects.GameObject + ): this; + + hideButton( + index: number | Phaser.GameObjects.GameObject + ): this; + + forEachButtton( + callback: (button: Phaser.GameObjects.GameObject, index: number, buttons: Phaser.GameObjects.GameObject[]) => void, + scop?: unknown + ): this; + + readonly buttons: Phaser.GameObjects.GameObject[]; + + value: unknown; + + setSelectedButtonName( + name: string + ): this; + + getSelectedButtonName(): string; + + setButtonState( + name: string, + state?: boolean + ): this; + + getButtonState( + name: string + ): boolean; + + getAllButtonsState(): { [name: string]: boolean }; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/buttons/Buttons.js b/ui/src/phaser3-rex-plugins/templates/ui/buttons/Buttons.js new file mode 100644 index 000000000..e032cd4b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/buttons/Buttons.js @@ -0,0 +1,88 @@ +import Sizer from '../sizer/Sizer.js'; +import AddChildMethods from './AddChildMethods.js'; +import RemoveChildMethods from './RemoveChildMethods.js'; +import ButtonGroup from '../utils/buttongroup/ButtonGroup.js'; +import ButtonMethods from '../utils/buttongroup/ButtonMethods.js'; +import ButtonStateMethods from '../utils/buttongroup/ButtonStateMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Buttons extends Sizer { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + var buttonSpace = config.space; + if (typeof (buttonSpace) === 'number') { + config.space = { item: buttonSpace }; + } + + // Create + super(scene, config); + this.type = 'rexButtons'; + this.buttonGroup = new ButtonGroup({ + parent: this, + eventEmitter: GetValue(config, 'eventEmitter', this), + groupName: GetValue(config, 'groupName', undefined), + clickConfig: GetValue(config, 'click', undefined) + }) + .setButtonsType(config) + + // Add elements + var background = GetValue(config, 'background', undefined); + var buttons = GetValue(config, 'buttons', undefined); + + // Buttons properties + this.buttonsExpand = GetValue(config, 'expand', false); + this.buttonsAlign = GetValue(config, 'align', undefined); // undefined/left/top: no space + + if (background) { + this.addBackground(background); + } + + if (buttons) { + this.addButtons(buttons); + } + + this.addChildrenMap('background', background); + this.addChildrenMap('buttons', this.buttonGroup.buttons); + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + super.destroy(fromScene); + this.buttonGroup.destroy(); + this.buttonGroup = undefined; + } + + get buttons() { + return this.buttonGroup.buttons; + } + + get groupName() { + return this.buttonGroup.groupName; + } + + set groupName(value) { + this.buttonGroup.groupName = value; + } + + get eventEmitter() { + return this.buttonGroup.eventEmitter; + } +} + +Object.assign( + Buttons.prototype, + AddChildMethods, + RemoveChildMethods, + ButtonMethods, + ButtonStateMethods +); + +export default Buttons; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/buttons/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/buttons/Factory.d.ts new file mode 100644 index 000000000..f23714204 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/buttons/Factory.d.ts @@ -0,0 +1,5 @@ +import Buttons from './Buttons'; + +export default function ButtonsFactory( + config?: Buttons.IConfig +): Buttons; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/buttons/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/buttons/Factory.js new file mode 100644 index 000000000..ec3147a0e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/buttons/Factory.js @@ -0,0 +1,13 @@ +import Buttons from './Buttons.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('buttons', function (config) { + var gameObject = new Buttons(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.Buttons', Buttons); + +export default Buttons; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/buttons/RemoveChildMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/buttons/RemoveChildMethods.js new file mode 100644 index 000000000..f07d12dc1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/buttons/RemoveChildMethods.js @@ -0,0 +1,55 @@ +import Sizer from '../sizer/Sizer.js'; +import IsArray from '../../../plugins/utils/object/IsArray.js'; + +const SizerRmove = Sizer.prototype.remove; +const SizerClear = Sizer.prototype.clear; + +var Remove = function (gameObject, destroyChild) { + if (this.getParentSizer(gameObject) !== this) { + return this; + } + + this.buttonGroup.remove(gameObject); + SizerRmove.call(this, gameObject, destroyChild); + return this; +}; + +export default { + remove(gameObject, destroyChild) { + // Remove gameObject no matter it is a button or not + if (IsArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + Remove.call(this, gameObjects[i], destroyChild); + } + } else { + Remove.call(this, gameObject, destroyChild); + } + return this; + }, + + clear(destroyChild) { + var buttons = this.buttonGroup.buttons; + buttons.length = 0; + SizerClear.call(this, destroyChild); + return this; + }, + + removeButton(gameObject, destroyChild) { + var gameObject = this.getButton(gameObject); + // Don't remove this gameObject, it is not a button + if (!gameObject) { + return this; + } + this.remove(gameObject, destroyChild); + return this; + }, + + clearButtons(destroyChild) { + var buttons = this.buttonGroup.buttons; + for (var i = buttons.length - 1; i >= 0; i--) { + Remove.call(this, buttons[i], destroyChild); + } + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/canvas/Canvas.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/canvas/Canvas.d.ts new file mode 100644 index 000000000..899032d7a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/canvas/Canvas.d.ts @@ -0,0 +1,2 @@ +import Canvas from '../../../plugins/canvas'; +export default Canvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/canvas/Canvas.js b/ui/src/phaser3-rex-plugins/templates/ui/canvas/Canvas.js new file mode 100644 index 000000000..2c979f6e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/canvas/Canvas.js @@ -0,0 +1,2 @@ +import Canvas from '../../../plugins/canvas.js'; +export default Canvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/canvas/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/canvas/Factory.d.ts new file mode 100644 index 000000000..b1aa15542 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/canvas/Factory.d.ts @@ -0,0 +1,6 @@ +import Canvas from './Canvas'; + +export default function ( + x?: number, y?: number, + width?: number, height?: number +): Canvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/canvas/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/canvas/Factory.js new file mode 100644 index 000000000..95444d0f0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/canvas/Factory.js @@ -0,0 +1,13 @@ +import Canvas from './Canvas.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('canvas', function (x, y, width, height) { + var gameObject = new Canvas(this.scene, x, y, width, height); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.Canvas', Canvas); + +export default Canvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/canvasinput/CanvasInput.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/canvasinput/CanvasInput.d.ts new file mode 100644 index 000000000..fb4e9bc59 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/canvasinput/CanvasInput.d.ts @@ -0,0 +1,2 @@ +import CanvasInput from '../../../plugins/canvasinput'; +export default CanvasInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/canvasinput/CanvasInput.js b/ui/src/phaser3-rex-plugins/templates/ui/canvasinput/CanvasInput.js new file mode 100644 index 000000000..318c7182f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/canvasinput/CanvasInput.js @@ -0,0 +1,2 @@ +import CanvasInput from '../../../plugins/canvasinput.js'; +export default CanvasInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/canvasinput/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/canvasinput/Factory.d.ts new file mode 100644 index 000000000..686d181d7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/canvasinput/Factory.d.ts @@ -0,0 +1,7 @@ +import CanvasInput from './CanvasInput'; + +export default function ( + x?: number, y?: number, + fixedWidth?: number, fixedHeight?: number, + config?: CanvasInput.IConfig +): CanvasInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/canvasinput/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/canvasinput/Factory.js new file mode 100644 index 000000000..33f0f109d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/canvasinput/Factory.js @@ -0,0 +1,13 @@ +import CanvasInput from './CanvasInput.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('canvasInput', function (x, y, fixedWidth, fixedHeight, config) { + var gameObject = new CanvasInput(this.scene, x, y, fixedWidth, fixedHeight, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.CanvasInput', CanvasInput); + +export default CanvasInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/chart/Chart.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/chart/Chart.d.ts new file mode 100644 index 000000000..9ed7bcd28 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/chart/Chart.d.ts @@ -0,0 +1,42 @@ +// import * as Phaser from 'phaser'; +import Canvas from '../canvas/Canvas'; + +export default Chart; + +declare namespace Chart { + type IndexType = number | string; + + interface IConfig { + + } +} + +declare class Chart extends Canvas { + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + config?: Chart.IConfig + ); + + setChart(config: Chart.IConfig): this; + + getChartDataset( + datasetIndex: Chart.IndexType + ): { [index: Chart.IndexType]: number }; + + getChartData( + datasetIndex: Chart.IndexType, + dataIndex: Chart.IndexType + ): number; + + setChartData( + datasetIndex: Chart.IndexType, + dataIndex: Chart.IndexType, + value: number + ): this; + + updateChart(): this; + + chart: any; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/chart/Chart.js b/ui/src/phaser3-rex-plugins/templates/ui/chart/Chart.js new file mode 100644 index 000000000..ff54125a8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/chart/Chart.js @@ -0,0 +1,65 @@ +import Canvas from '../canvas/Canvas.js'; +import SetChart from './SetChart.js'; +import GetChartDataset from './GetChartDataset.js'; +import GetChartData from './GetChartData.js'; +import SetChartData from './SetChartData.js'; +import UpdateChart from './UpdateChart.js'; + +// This plugin does not contain chart.js +// Load chart.js in preload stage - +// scene.load.script('chartjs', 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.0/Chart.min.js'); + +class Chart extends Canvas { + constructor(scene, x, y, width, height, config) { + super(scene, x, y, width, height); + this.type = 'rexChart'; + this.chart = undefined; + + if (config !== undefined) { + this.setChart(config); + } + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene) { + return; + } + if (this.chart) { + this.chart.destroy(); + this.chart = undefined; + } + super.destroy(fromScene); + } + + resize(width, height) { + if ((width === this.width) && (height === this.height)) { + return this; + } + + super.resize(width, height); + + if (this.chart) { + var chart = this.chart; + chart.height = this.canvas.height; + chart.width = this.canvas.width; + chart.aspectRatio = (chart.height) ? chart.width / chart.height : null; + chart.update(); + } + return this; + } +} + +var methods = { + setChart: SetChart, + getChartDataset: GetChartDataset, + getChartData: GetChartData, + setChartData: SetChartData, + updateChart: UpdateChart, +} +Object.assign( + Chart.prototype, + methods +); + +export default Chart; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/chart/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/chart/Factory.d.ts new file mode 100644 index 000000000..94240ec54 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/chart/Factory.d.ts @@ -0,0 +1,5 @@ +import Chart from './Chart'; + +export default function ChartFactory( + config?: Chart.IConfig +): Chart; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/chart/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/chart/Factory.js new file mode 100644 index 000000000..7b1ce6aef --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/chart/Factory.js @@ -0,0 +1,13 @@ +import Chart from './Chart.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('chart', function (x, y, width, height, config) { + var gameObject = new Chart(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.Chart', Chart); + +export default Chart; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/chart/GetChartData.js b/ui/src/phaser3-rex-plugins/templates/ui/chart/GetChartData.js new file mode 100644 index 000000000..220aeb86c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/chart/GetChartData.js @@ -0,0 +1,15 @@ +var GetChartData = function (datasetIndex, dataIndex) { + var dataset = this.getChartDataset(datasetIndex); + if (dataset === undefined) { + return undefined; + } + if (typeof (dataIndex) === 'string') { + var labels = this.chart.data.labels; + dataIndex = labels.indexOf(dataIndex); + if (dataIndex === -1) { + return undefined; + } + } + return dataset.data[dataIndex]; +} +export default GetChartData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/chart/GetChartDataset.js b/ui/src/phaser3-rex-plugins/templates/ui/chart/GetChartDataset.js new file mode 100644 index 000000000..1f8d3d532 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/chart/GetChartDataset.js @@ -0,0 +1,21 @@ +var GetChartDataset = function (datasetIndex) { + if (this.chart === undefined) { + return undefined; + } + + if (typeof (datasetIndex) === 'string') { + var datasets = this.chart.data.datasets, dataset; + for (var i = 0, cnt = datasets.length; i < cnt; i++) { + dataset = datasets[i]; + if (dataset.label === datasetIndex) { + return dataset; + } + } + } else { + return this.chart.data.datasets[datasetIndex]; + } + + return undefined; +} + +export default GetChartDataset; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/chart/SetChart.js b/ui/src/phaser3-rex-plugins/templates/ui/chart/SetChart.js new file mode 100644 index 000000000..7248679bd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/chart/SetChart.js @@ -0,0 +1,66 @@ +var SetChart = function (config) { + if (!window.Chart) { + var msg = `Can not find chartjs! Load chartjs in preload stage. +scene.load.script('chartjs', 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.0/Chart.min.js');` + console.error(msg); + return this; + } + + if (this.chart) { + this.chart.destroy(); + } + this.chart = new Chart(this.context, FillConfig(this, config)); + return this; +} + +var FillConfig = function (canvas, config) { + // Get options + if (config === undefined) { + config = {}; + } + if (config.options === undefined) { + config.options = {}; + } + var options = config.options; + + // Fill options + options.responsive = false; + options.maintainAspectRatio = false; + if (!options.hasOwnProperty('devicePixelRatio')) { + options.devicePixelRatio = 1; + } + + // Get animation config + var noAnimation = false; + if (options.animation === undefined) { + options.animation = {}; + } else if (options.animation === false) { + noAnimation = true; + options.animation = {}; + } + var animationConfig = options.animation; + + // Fill animation config + if (noAnimation) { + animationConfig.duration = 0; + } + + var onProgress = animationConfig.onProgress; + animationConfig.onProgress = function (animation) { + if (onProgress) { + onProgress(animation); + } + canvas.needRedraw(); + } + + var onComplete = animationConfig.onComplete; + animationConfig.onComplete = function (animation) { + if (onComplete) { + onComplete(animation); + } + canvas.needRedraw(); + } + return config; +} + +export default SetChart; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/chart/SetChartData.js b/ui/src/phaser3-rex-plugins/templates/ui/chart/SetChartData.js new file mode 100644 index 000000000..6acb2a8ae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/chart/SetChartData.js @@ -0,0 +1,17 @@ +var SetChartData = function (datasetIndex, dataIndex, value) { + if (this.chart === undefined) { + return this; + } + + var dataset = this.getChartDataset(datasetIndex); + if (typeof (dataIndex) === 'string') { + var labels = this.chart.data.labels; + dataIndex = labels.indexOf(dataIndex); + if (dataIndex === -1) { + return this; + } + } + dataset.data[dataIndex] = value; + return this; +}; +export default SetChartData; diff --git a/ui/src/phaser3-rex-plugins/templates/ui/chart/UpdateChart.js b/ui/src/phaser3-rex-plugins/templates/ui/chart/UpdateChart.js new file mode 100644 index 000000000..84d6d4932 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/chart/UpdateChart.js @@ -0,0 +1,8 @@ +var UpdateChart = function () { + if (this.chart === undefined) { + return this; + } + this.chart.update(); + return this; +} +export default UpdateChart; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/checkbox/Checkbox.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/checkbox/Checkbox.d.ts new file mode 100644 index 000000000..fb2e6098b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/checkbox/Checkbox.d.ts @@ -0,0 +1,2 @@ +import Checkbox from '../../../plugins/checkbox'; +export default Checkbox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/checkbox/Checkbox.js b/ui/src/phaser3-rex-plugins/templates/ui/checkbox/Checkbox.js new file mode 100644 index 000000000..c991ae0f4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/checkbox/Checkbox.js @@ -0,0 +1,2 @@ +import Checkbox from '../../../plugins/checkbox.js'; +export default Checkbox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/checkbox/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/checkbox/Factory.d.ts new file mode 100644 index 000000000..fc386f212 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/checkbox/Factory.d.ts @@ -0,0 +1,19 @@ +import Checkbox from './Checkbox'; + +export default function ( + x: number, y: number, + width: number, height: number, + color?: number, + config?: Checkbox.IConfig +): Checkbox; + +export default function ( + x: number, y: number, + width: number, height: number, + config?: Checkbox.IConfig +): Checkbox; + + +export default function ( + config?: Checkbox.IConfig +): Checkbox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/checkbox/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/checkbox/Factory.js new file mode 100644 index 000000000..f2e16442a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/checkbox/Factory.js @@ -0,0 +1,13 @@ +import Checkbox from './Checkbox.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('checkbox', function (x, y, width, height, color, config) { + var gameObject = new Checkbox(this.scene, x, y, width, height, color, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.Checkbox', Checkbox); + +export default Checkbox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/CircleMaskImage.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/CircleMaskImage.d.ts new file mode 100644 index 000000000..bb69ef4fe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/CircleMaskImage.d.ts @@ -0,0 +1,2 @@ +import CircleMaskImage from '../../../plugins/circlemaskimage'; +export default CircleMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/CircleMaskImage.js b/ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/CircleMaskImage.js new file mode 100644 index 000000000..dafd452a2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/CircleMaskImage.js @@ -0,0 +1,2 @@ +import CircleMaskImage from '../../../plugins/circlemaskimage.js'; +export default CircleMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/Factory.d.ts new file mode 100644 index 000000000..a82d8c2f4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/Factory.d.ts @@ -0,0 +1,9 @@ +import CircleMaskImage from './CircleMaskImage'; + +export default function ( + x?: number, y?: number, + key?: string, frame?: string, + config?: + null | 0 | 1 | 2 | 'circle' | 'ellipse' | 'roundRectangle' | + CircleMaskImage.IConfig +): CircleMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/Factory.js new file mode 100644 index 000000000..33a542395 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/Factory.js @@ -0,0 +1,13 @@ +import CircleMaskImage from './CircleMaskImage.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('circleMaskImage', function (x, y, key, frame, config) { + var gameObject = new CircleMaskImage(this.scene, x, y, key, frame, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.CircleMaskImage', CircleMaskImage); + +export default CircleMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/circularprogress/CircularProgress.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/circularprogress/CircularProgress.d.ts new file mode 100644 index 000000000..ae26052e9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/circularprogress/CircularProgress.d.ts @@ -0,0 +1,2 @@ +import CircularProgress from "../../../plugins/circularprogress"; +export default CircularProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/circularprogress/CircularProgress.js b/ui/src/phaser3-rex-plugins/templates/ui/circularprogress/CircularProgress.js new file mode 100644 index 000000000..bd8b0b1ac --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/circularprogress/CircularProgress.js @@ -0,0 +1,2 @@ +import CircularProgress from '../../../plugins/circularprogress.js'; +export default CircularProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/circularprogress/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/circularprogress/Factory.d.ts new file mode 100644 index 000000000..b938fd054 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/circularprogress/Factory.d.ts @@ -0,0 +1,13 @@ +import CircularProgress from './CircularProgress'; + +export default function ( + config?: CircularProgress.IConfig +): CircularProgress; + +export default function ( + x?: number, y?: number, + radius?: number, + barColor?: string | number, + value?: number, + config?: CircularProgress.IConfig +): CircularProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/circularprogress/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/circularprogress/Factory.js new file mode 100644 index 000000000..44250496e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/circularprogress/Factory.js @@ -0,0 +1,13 @@ +import CircularProgress from './CircularProgress.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('circularProgress', function (x, y, radius, barColor, value, config) { + var gameObject = new CircularProgress(this.scene, x, y, radius, barColor, value, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.CircularProgress', CircularProgress); + +export default CircularProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/CircularProgressCanvas.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/CircularProgressCanvas.d.ts new file mode 100644 index 000000000..93fff3e5b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/CircularProgressCanvas.d.ts @@ -0,0 +1,2 @@ +import CircularProgressCanvas from "../../../plugins/circularprogresscanvas"; +export default CircularProgressCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/CircularProgressCanvas.js b/ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/CircularProgressCanvas.js new file mode 100644 index 000000000..a28f98441 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/CircularProgressCanvas.js @@ -0,0 +1,2 @@ +import CircularProgressCanvas from '../../../plugins/circularprogresscanvas.js'; +export default CircularProgressCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/Factory.d.ts new file mode 100644 index 000000000..2b95a7523 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/Factory.d.ts @@ -0,0 +1,13 @@ +import CircularProgressCanvas from './CircularProgressCanvas'; + +export default function ( + config?: CircularProgressCanvas.IConfig +): CircularProgressCanvas; + +export default function ( + x?: number, y?: number, + radius?: number, + barColor?: string | number, + value?: number, + config?: CircularProgressCanvas.IConfig +): CircularProgressCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/Factory.js new file mode 100644 index 000000000..8676ce406 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/Factory.js @@ -0,0 +1,13 @@ +import CircularProgressCanvas from './CircularProgressCanvas.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('circularProgressCanvas', function (x, y, radius, barColor, value, config) { + var gameObject = new CircularProgressCanvas(this.scene, x, y, radius, barColor, value, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.CircularProgressCanvas', CircularProgressCanvas); + +export default CircularProgressCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/click/Click.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/click/Click.d.ts new file mode 100644 index 000000000..01b6b406e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/click/Click.d.ts @@ -0,0 +1,2 @@ +import Click from '../../../plugins/button'; +export default Click; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/click/Click.js b/ui/src/phaser3-rex-plugins/templates/ui/click/Click.js new file mode 100644 index 000000000..093ae2ad8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/click/Click.js @@ -0,0 +1,2 @@ +import Click from '../../../plugins/button.js' +export default Click; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/click/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/click/Factory.d.ts new file mode 100644 index 000000000..448177fa9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/click/Factory.d.ts @@ -0,0 +1,7 @@ +// import * as Phaser from 'phaser'; +import Click from "./Click"; + +export default function ( + gameObject: Phaser.GameObjects.GameObject, + config?: Click.IConfig +): Click; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/click/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/click/Factory.js new file mode 100644 index 000000000..9158fc1eb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/click/Factory.js @@ -0,0 +1,11 @@ +import Click from './Click.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('click', function (gameObject, config) { + return new Click(gameObject, config); +}); + +SetValue(window, 'RexPlugins.UI.Click', Click); + +export default Click; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/clickoutside/ClickOutside.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/clickoutside/ClickOutside.d.ts new file mode 100644 index 000000000..576f9c7a5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/clickoutside/ClickOutside.d.ts @@ -0,0 +1,2 @@ +import ClickOutside from '../../../plugins/clickoutside'; +export default ClickOutside; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/clickoutside/ClickOutside.js b/ui/src/phaser3-rex-plugins/templates/ui/clickoutside/ClickOutside.js new file mode 100644 index 000000000..5527388e1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/clickoutside/ClickOutside.js @@ -0,0 +1,2 @@ +import ClickOutside from '../../../plugins/clickoutside.js' +export default ClickOutside; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/clickoutside/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/clickoutside/Factory.d.ts new file mode 100644 index 000000000..744850f15 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/clickoutside/Factory.d.ts @@ -0,0 +1,7 @@ +// import * as Phaser from 'phaser'; +import ClickOutside from "./ClickOutside"; + +export default function ( + gameObject: Phaser.GameObjects.GameObject, + config?: ClickOutside.IConfig +): ClickOutside; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/clickoutside/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/clickoutside/Factory.js new file mode 100644 index 000000000..0d818d15b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/clickoutside/Factory.js @@ -0,0 +1,11 @@ +import ClickOutside from './ClickOutside.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('clickOutside', function (gameObject, config) { + return new ClickOutside(gameObject, config); +}); + +SetValue(window, 'RexPlugins.UI.ClickOutside', ClickOutside); + +export default ClickOutside; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/ColorComponents.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/ColorComponents.d.ts new file mode 100644 index 000000000..826d9628d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/ColorComponents.d.ts @@ -0,0 +1,60 @@ +import Sizer from '../../sizer/Sizer'; +import RoundRectangle from '../../roundrectangle/RoundRectangle'; +import Label from '../../label/Label'; +import CanvasInput from '../../canvasinput/CanvasInput'; + +export default ColorComponents; + +declare namespace ColorComponents { + + interface IFormatLabelConfig { + space?: { + left?: number, right?: number, top?: number, bottom?: number, + }, + + background?: RoundRectangle.IConfig, + + text?: Phaser.GameObjects.TextStyle, + expandTextWidth?: boolean, + expandTextHeight?: boolean, + + align?: Label.AlignTypes, + } + + interface IConfig extends Sizer.IConfig { + background?: Phaser.GameObjects.GameObject, + + formatLabel?: Phaser.GameObjects.GameObject | IFormatLabelConfig; + + inputText0?: Phaser.GameObjects.GameObject, + inputText1?: Phaser.GameObjects.GameObject, + inputText2?: Phaser.GameObjects.GameObject, + inputText?: CanvasInput.IConfig, + + proportion?: { + formatLabel?: number, + + }, + + valuechangeCallback: (newValue: number, oldValue: number, colorComponents: ColorComponents) => void, + + value?: number + } +} + +declare class ColorComponents extends Sizer { + constructor( + scene: Phaser.Scene, + config?: ColorComponents.IConfig + ); + + setValue(value: number): this; + value: number; + + setColor(color: number): this; + color: number; + + setColorFormat(colorFormat: 'RGB' | 'HSV'): this; + toggleColorFormat(): this; + colorFormat: 'RGB' | 'HSV'; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/ColorComponents.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/ColorComponents.js new file mode 100644 index 000000000..1fdaed528 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/ColorComponents.js @@ -0,0 +1,187 @@ +import Sizer from '../../sizer/Sizer.js'; +import IsGameObject from '../../../../plugins/utils/system/IsGameObject.js'; +import CreateLabel from '../../utils/build/CreateLabel.js'; +import CreateInputText from '../../utils/build/CreateInputText.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Color = Phaser.Display.Color; +const ColorToRGBA = Phaser.Display.Color.ColorToRGBA; +const HSVToRGB = Phaser.Display.Color.HSVToRGB; +const Clamp = Phaser.Math.Clamp; + +class ColorComponents extends Sizer { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + config.orientation = 0; + super(scene, config); + this.type = 'rexColorComponents'; + + this.colorObject = new Color(); + + // Add elements + var background = GetValue(config, 'background', undefined); + + var formatLabel = GetValue(config, 'formatLabel', undefined); + if (!IsGameObject(formatLabel)) { + formatLabel = CreateLabel(scene, formatLabel) + .resetDisplayContent(); + } + + var components = []; + if (config.inputText0 && config.inputText1 && config.inputText2) { + components.push(config.inputText0); + components.push(config.inputText1); + components.push(config.inputText2); + } else { + var inputTextConfig = GetValue(config, 'inputText'); + for (var i = 0; i < 3; i++) { + var inputText = CreateInputText(scene, inputTextConfig) + .setMaxLength(3) + .setNumberInput() + + components.push(inputText); + } + } + + if (background) { + this.addBackground(background); + } + + var proportion = GetValue(config, 'proportion.formatLabel', 0); + var defaultExpand = (formatLabel.isRexContainerLite) ? true : false; + var expand = GetValue(config, 'expand.formatLabel', defaultExpand); + this.add( + formatLabel, + { proportion: proportion, expand: expand } + ); + + var proportion = (GetValue(inputTextConfig, 'width') === undefined) ? 1 : 0; + var expand = (GetValue(inputTextConfig, 'height') === undefined) ? true : false; + for (var i = 0, cnt = components.length; i < cnt; i++) { + this.add( + components[i], + { proportion: proportion, expand: expand } + ) + } + + this.addChildrenMap('background', background); + this.addChildrenMap('formatLabel', formatLabel); + this.addChildrenMap('components', components); + + this.onClick(formatLabel, this.toggleColorFormat, this); + + for (var i = 0, cnt = components.length; i < cnt; i++) { + components[i].on('close', function () { + this.updateColorObject(); + this.setValue(this.colorObject.color); + }, this); + } + + var callback = GetValue(config, 'valuechangeCallback', null); + if (callback !== null) { + var scope = GetValue(config, 'valuechangeCallbackScope', undefined); + this.on('valuechange', callback, scope); + } + + formatLabel.setText('RGB'); + this.setValue(GetValue(config, 'value', 0xffffff)); + } + + get value() { + return this._value; + } + + set value(value) { + value = Clamp(Math.floor(value), 0, 0xffffff); + + if (this._value === value) { + return; + } + + this._value = value; + + this.colorObject.setFromRGB(ColorToRGBA(value)); + this.updateComponents(); + + this.emit('valuechange', this._value); + } + + setValue(value) { + this.value = value; + return this; + } + + get color() { + return this._value; + } + + set color(color) { + this.value = color; + } + + setColor(color) { + this.color = color; + return this; + } + + get colorFormat() { + return this.childrenMap.formatLabel.text; + } + + set colorFormat(value) { + if (this.colorFormat === value) { + return; + } + this.childrenMap.formatLabel.setText(value); + this.updateComponents(); + } + + setColorFormat(colrType) { + this.colorFormat = colrType; + return this; + } + + toggleColorFormat() { + this.colorFormat = (this.colorFormat === 'RGB') ? 'HSV' : 'RGB'; + return this; + } + + updateComponents() { + var components = this.childrenMap.components; + var value0, value1, value2 + if (this.colorFormat === 'RGB') { + value0 = this.colorObject.red; + value1 = this.colorObject.green; + value2 = this.colorObject.blue; + } else { // colorFormat === 'HSV' + value0 = Math.floor(this.colorObject.h * 360); + value1 = Math.floor(this.colorObject.s * 100); + value2 = Math.floor(this.colorObject.v * 100); + } + + components[0].setValue(value0); + components[1].setValue(value1); + components[2].setValue(value2); + return this; + } + + updateColorObject() { + var components = this.childrenMap.components; + if (this.colorFormat === 'RGB') { + var red = Clamp(components[0].value, 0, 255); + var green = Clamp(components[1].value, 0, 255); + var blue = Clamp(components[2].value, 0, 255); + this.colorObject.setTo(red, green, blue); + } else { + var h = Clamp(components[0].value, 0, 359) / 360; + var s = Clamp(components[1].value, 0, 100) / 100; + var v = Clamp(components[2].value, 0, 100) / 100; + this.colorObject.setFromRGB(HSVToRGB(h, s, v)); + } + return this; + } +} + +export default ColorComponents; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/Factory.d.ts new file mode 100644 index 000000000..422b94cb2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/Factory.d.ts @@ -0,0 +1,5 @@ +import ColorComponents from './ColorComponents'; + +export default function ( + config?: ColorComponents.IConfig +): ColorComponents; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/Factory.js new file mode 100644 index 000000000..ca04d2f04 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/Factory.js @@ -0,0 +1,13 @@ +import ColorComponents from './ColorComponents.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('colorComponents', function (config) { + var gameObject = new ColorComponents(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.ColorComponents', ColorComponents); + +export default ColorComponents; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/ColorInput.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/ColorInput.d.ts new file mode 100644 index 000000000..c4c51077a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/ColorInput.d.ts @@ -0,0 +1,59 @@ +import ColorInputBase from '../colorinputbase/ColorInputBase'; +import RoundRectangle from '../../roundrectangle/RoundRectangle'; +import ColorComponents from '../colorcomponents/ColorComponents'; +import CanvasInput from '../../canvasinput/CanvasInput'; + +export default ColorInput; + +declare namespace ColorInput { + type TransitCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + duration: number + ) => void; + + interface IConfig extends ColorInputBase.IConfig { + colorPicker?: { + width?: number, height?: number, + + background?: RoundRectangle.IConfig, + createBackgroundCallback: ( + scene: Phaser.Scene, + ) => Phaser.GameObjects.GameObject, + + hPalettePosition?: 0 | 1 | 2 | 3 | 'bottom' | 'left' | 'top' | 'right', + + expandDirection?: 0 | 1 | 'down' | 'up', + + easeIn?: number, easeOut?: number, + + transitIn?: TransitCallbackType, + transitOut?: TransitCallbackType, + + bounds?: Phaser.Geom.Rectangle; + + space?: { + left?: number, right?: number, top?: number, bottom?: number, + item?: number, + } + }, + + colorComponents?: { + height?: number, + + formatLabel?: ColorComponents.IFormatLabelConfig, + + inputText?: CanvasInput.IConfig, + + space?: { + left?: number, right?: number, top?: number, bottom?: number, + }, + } + } +} + +declare class ColorInput extends ColorInputBase { + constructor( + scene: Phaser.Scene, + config?: ColorInput.IConfig + ); +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/ColorInput.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/ColorInput.js new file mode 100644 index 000000000..0198f7609 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/ColorInput.js @@ -0,0 +1,91 @@ +import ColorInputBase from '../colorinputbase/ColorInputBase.js'; +import Methods from './methods/Methods.js'; +import CreateBackground from '../../utils/build/CreateBackground.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class ColorInput extends ColorInputBase { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + super(scene, config); + this.type = 'rexColorInput'; + + if (!config.hasOwnProperty('colorPicker')) { + config.colorPicker = { + background: { color: 0x0 } + } + } + + var colorPickerConfig = config.colorPicker; + var hasColorPicker = (colorPickerConfig !== false) && (colorPickerConfig !== null); + + if (hasColorPicker) { + this.setColorPickerSize( + GetValue(colorPickerConfig, 'width', 160), + GetValue(colorPickerConfig, 'height', 170) + ); + + var createBackgroundCallback; + var background = GetValue(colorPickerConfig, 'background'); + if (background) { + createBackgroundCallback = function (scene) { + return CreateBackground(scene, background); + } + } else { + createBackgroundCallback = GetValue(colorPickerConfig, 'createBackgroundCallback'); + } + this.setCreateColorPickerBackgroundCallback(createBackgroundCallback); + + this.setColorPickerHPalettePosition(GetValue(colorPickerConfig, 'hPalettePosition', 0)); + this.setColorPickerExpandDirection(GetValue(colorPickerConfig, 'expandDirection')); + this.setColorPickerEaseInDuration(GetValue(colorPickerConfig, 'easeIn', 200)); + this.setColorPickerEaseOutDuration(GetValue(colorPickerConfig, 'easeOut', 200)); + this.setColorPickerTransitInCallback(GetValue(colorPickerConfig, 'transitIn')); + this.setColorPickerTransitOutCallback(GetValue(colorPickerConfig, 'transitOut')); + this.setColorPickerBounds(GetValue(colorPickerConfig, 'bounds')); + + var colorPickerSpaceConfig = GetValue(colorPickerConfig, 'space'); + if (colorPickerSpaceConfig === undefined) { + colorPickerSpaceConfig = { left: 10, right: 10, top: 10, bottom: 10, item: 8 } + } + this.setColorPickerSpace(colorPickerSpaceConfig); + } + + var colorComponentsConfig = config.colorComponents; + var hasColorComponents = (colorComponentsConfig !== false) && (colorComponentsConfig !== null); + if (hasColorPicker && hasColorComponents) { + this.setColorComponentsHeight(GetValue(colorComponentsConfig, 'height', 30)); + + this.setColorComponentsFormatLabelConfig(GetValue(colorComponentsConfig, 'formatLabel')); + + var colorComponentsInputTextConfig = GetValue(colorComponentsConfig, 'inputText'); + if (!colorComponentsInputTextConfig) { + colorComponentsInputTextConfig = GetValue(config, 'inputText'); + } + this.setColorComponentsInputTextConfig(colorComponentsInputTextConfig); + + var colorComponentsSpace = GetValue(colorComponentsConfig, 'space'); + if (colorComponentsSpace === undefined) { + colorComponentsSpace = { item: 8 } + } + this.setColorComponentsSpace(colorComponentsSpace); + } + + + + var swatch = this.childrenMap.swatch; + if (swatch && hasColorPicker) { + this.onClick(swatch, this.openColorPicker, this); + } + } +} + +Object.assign( + ColorInput.prototype, + Methods, +) + +export default ColorInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/Factory.d.ts new file mode 100644 index 000000000..9f1c9b668 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/Factory.d.ts @@ -0,0 +1,5 @@ +import ColorInput from './ColorInput'; + +export default function ( + config?: ColorInput.IConfig +): ColorInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/Factory.js new file mode 100644 index 000000000..74c5e93b5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/Factory.js @@ -0,0 +1,13 @@ +import ColorInput from './ColorInput.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('colorInput', function (config) { + var gameObject = new ColorInput(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.ColorInput', ColorInput); + +export default ColorInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/ColorPicker.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/ColorPicker.js new file mode 100644 index 000000000..22d36b3a7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/ColorPicker.js @@ -0,0 +1,101 @@ +import Sizer from '../../../sizer/Sizer.js'; +import ColorPicker from '../../colorpicker/ColorPicker.js'; +import ColorComponents from '../../colorcomponents/ColorComponents.js'; +import TouchEventStop from '../../../toucheventstop/TouchEventStop.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class ColorPickerPanel extends Sizer { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + config.orientation = 1; + super(scene, config); + this.type = 'rexColorInput.ColorPickerPanel'; + + // Add elements + var background = GetValue(config, 'background', undefined); + + var colorPicker = new ColorPicker(scene, { + hPalette: config.hPalette || {}, + svPalette: config.svPalette || {}, + space: { + item: GetValue(config, 'space.hPalette', 8) + } + }); + scene.add.existing(colorPicker); + + var colorComponents; + if (config.colorComponents) { + colorComponents = new ColorComponents(scene, config.colorComponents); + scene.add.existing(colorComponents); + } + + if (background) { + this.addBackground(background); + var touchEventStop = new TouchEventStop(background, { + stopAllLevels: false, + }); + } + + this.add( + colorPicker, + { proportion: 1, expand: true } + ); + + if (colorComponents) { + this.add( + colorComponents, + { proportion: 0, expand: true } + ); + } + + this.addChildrenMap('background', background); + this.addChildrenMap('colorPicker', colorPicker); + this.addChildrenMap('colorComponents', colorComponents); + + colorPicker.on('valuechange', function (value) { + this.setValue(value); + }, this) + + if (colorComponents) { + colorComponents.on('valuechange', function (value) { + this.setValue(value); + }, this) + } + + this.setValue(GetValue(config, 'value', 0xffffff)); + } + + get value() { + return this._value; + } + + set value(value) { + if (this._value === value) { + return; + } + + this._value = value; + + var colorPicker = this.childrenMap.colorPicker; + colorPicker.setValue(value); + + var colorComponents = this.childrenMap.colorComponents; + if (colorComponents) { + colorComponents.setValue(value); + } + + this.emit('valuechange', value); + } + + setValue(value) { + this.value = value; + return this; + } + +} + +export default ColorPickerPanel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/ConfigurationMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/ConfigurationMethods.js new file mode 100644 index 000000000..22f9b4894 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/ConfigurationMethods.js @@ -0,0 +1,107 @@ +var methods = { + // Color picker + setCreateColorPickerBackgroundCallback(callback) { + this.colorPickerCreateBackgroundCallback = callback; + return this; + }, + + setColorPickerHPalettePosition(position) { + this.colorPickerHPalettePosition = position; + return this; + }, + + setColorPickerExpandDirection(direction) { + if (typeof (direction) === 'string') { + direction = ColorPickerExpandDirections[direction]; + } + this.colorPickerExpandDirection = direction; + return this; + }, + + setColorPickerEaseInDuration(duration) { + if (duration === undefined) { + duration = 0; + } + this.colorPickerEaseInDuration = duration; + return this; + }, + + setColorPickerEaseOutDuration(duration) { + if (duration === undefined) { + duration = 0; + } + this.colorPickerEaseOutDuration = duration; + return this; + }, + + setColorPickerTransitInCallback(callback) { + this.colorPickerTransitInCallback = callback; + // callback = function(gameObject, duration) {} + return this; + }, + + setColorPickerTransitOutCallback(callback) { + this.colorPickerTransitOutCallback = callback; + // callback = function(gameObject, duration) {} + return this; + }, + + setColorPickerBounds(bounds) { + this.colorPickerBounds = bounds; + return this; + }, + + setColorPickerWidth(width) { + this.colorPickerWidth = width; + return this; + }, + + setColorPickerHeight(height) { + this.colorPickerHeight = height; + return this; + }, + + setColorPickerSize(width, height) { + this.setColorPickerWidth(width).setColorPickerHeight(height); + return this; + }, + + setColorPickerSpace(space) { + if (space === undefined) { + space = {}; + } + this.colorPickerSpace = space; + return this; + }, + + // Color components + setColorComponentsHeight(height) { + this.colorComponentsHeight = height; + return this; + }, + + setColorComponentsFormatLabelConfig(config) { + this.colorComponentsFormatLabelConfig = config; + return this; + }, + + setColorComponentsInputTextConfig(config) { + this.colorComponentsInputTextConfig = config; + return this; + }, + + setColorComponentsSpace(space) { + if (space === undefined) { + space = {}; + } + this.colorComponentsSpace = space; + return this; + }, +} + +const ColorPickerExpandDirections = { + down: 0, + up: 1 +} + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/CreateColorPicker.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/CreateColorPicker.js new file mode 100644 index 000000000..b565e1514 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/CreateColorPicker.js @@ -0,0 +1,56 @@ +import ColorPicker from './ColorPicker.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var CreateColorPicker = function (scene) { + var scene = this.scene; + + var background; + var createBackgroundCallback = this.colorPickerCreateBackgroundCallback; + if (createBackgroundCallback) { + background = createBackgroundCallback.call(this, scene); + scene.add.existing(background); + } + + var width = this.colorPickerWidth; + if (width === undefined) { + width = this.width; + } + + var height = this.colorPickerHeight; + if (height === undefined) { + height = width; + } + + var colorComponentsConfig; + if (this.colorComponentsHeight > 0) { + colorComponentsConfig = { + height: this.colorComponentsHeight, + formatLabel: this.colorComponentsFormatLabelConfig, + inputText: this.colorComponentsInputTextConfig, + space: this.colorComponentsSpace, + } + } else { + colorComponentsConfig = false; + } + + var colorPicker = new ColorPicker(scene, { + width: width, height: height, + + background: background, + space: this.colorPickerSpace, + + hPalette: { + position: this.colorPickerHPalettePosition, + }, + + colorComponents: colorComponentsConfig, + + value: this.value + }); + scene.add.existing(colorPicker); + + return colorPicker; +} + +export default CreateColorPicker; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/Methods.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/Methods.js new file mode 100644 index 000000000..e8e753b87 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/Methods.js @@ -0,0 +1,13 @@ +import ConfigurationMethods from './ConfigurationMethods.js' +import OpenColorPicker from './OpenColorPicker.js'; + +var methods = { + openColorPicker: OpenColorPicker +} + +Object.assign( + methods, + ConfigurationMethods, +); + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/OpenColorPicker.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/OpenColorPicker.js new file mode 100644 index 000000000..123c72353 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/OpenColorPicker.js @@ -0,0 +1,53 @@ +import CreateColorPicker from './CreateColorPicker.js'; +import DropDown from '../../../dropdown/DropDown.js'; + +var OpenColorPicker = function () { + if (this.colorPicker) { + return; + } + + // Layout it to get full height + var colorPicker = CreateColorPicker.call(this).layout(); + + var dropDownBehavior = new DropDown(colorPicker, { + // Transition + duration: { + in: this.colorPickerEaseInDuration, + out: this.colorPickerEaseOutDuration + }, + transitIn: this.colorPickerTransitInCallback, + transitOut: this.colorPickerTransitOutCallback, + + // Position + expandDirection: this.colorPickerExpandDirection, + + alignTargetX: this, + alignTargetY: this, + + bounds: this.colorPickerBounds, + + // Close condition + touchOutsideClose: true, + }) + .on('open', function () { + // After popping up + // Can click + colorPicker.on('valuechange', function (value) { + this.setValue(value); + }, this); + }, this) + + .on('close', function () { + this.colorPicker = undefined; + this.dropDownBehavior = undefined; + }, this) + + this.colorPicker = colorPicker; + this.dropDownBehavior = dropDownBehavior; + + this.pin(colorPicker); + + return this; +} + +export default OpenColorPicker; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/ColorInputBase.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/ColorInputBase.d.ts new file mode 100644 index 000000000..b607ad0a7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/ColorInputBase.d.ts @@ -0,0 +1,38 @@ +import Sizer from '../../sizer/Sizer'; +import RoundRectangle from '../../roundrectangle/RoundRectangle' +import CanvasInput from '../../canvasinput/CanvasInput'; + +export default ColorInputBase; + +declare namespace ColorInputBase { + interface ISwatchConfig extends RoundRectangle.IConfig { + size?: number, + } + + interface IConfig extends Sizer.IConfig { + background?: Phaser.GameObjects.GameObject, + + swatch?: Phaser.GameObjects.GameObject | ISwatchConfig, + swatchSize?: number, + squareExpandSwatch?: boolean, + + inputText?: CanvasInput.IConfig, + + valuechangeCallback: (newValue: number, oldValue: number, colorPicker: ColorInputBase) => void, + + value?: number | string + } +} + +declare class ColorInputBase extends Sizer { + constructor( + scene: Phaser.Scene, + config?: ColorInputBase.IConfig + ); + + setValue(value: number): this; + value: number; + + setColor(color: number): this; + color: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/ColorInputBase.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/ColorInputBase.js new file mode 100644 index 000000000..015b25f70 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/ColorInputBase.js @@ -0,0 +1,145 @@ +import Sizer from '../../sizer/Sizer.js'; +import CreateSwatch from './methods/CreateSwatch.js'; +import CreateInputText from '../../utils/build/CreateInputText.js'; +import ColorStringToInteger from '../../../../plugins/utils/color/ColorStringToInteger.js'; +import GetHexColorString from '../../../../plugins/utils/color/GetHexColorString.js'; +import SetSwatchColor from './methods/SetSwatchColor.js'; +import ResizeGameObject from '../../../../plugins/utils/size/ResizeGameObject.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const Clamp = Phaser.Math.Clamp; + +class ColorInput extends Sizer { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + config.orientation = 0; + super(scene, config); + this.type = 'rexColorInputLite'; + + // Add elements + var background = GetValue(config, 'background', undefined); + + var swatchConfig = GetValue(config, 'swatch'); + var swatchSize; + if (IsPlainObject(swatchConfig)) { + swatchSize = GetValue(swatchConfig, 'size'); + } + var swatch = CreateSwatch(scene, GetValue(config, 'swatch')); + + var inputTextConfig = GetValue(config, 'inputText', true); + var inputText; + if (inputTextConfig) { + inputText = CreateInputText(scene, inputTextConfig); + } + + if (background) { + this.addBackground(background); + } + + if (swatch) { + swatchSize = GetValue(config, 'swatchSize', swatchSize); + var squareExpandSwatch; + if (swatchSize !== undefined) { + ResizeGameObject(swatch, swatchSize, swatchSize); + squareExpandSwatch = false; + } else { + squareExpandSwatch = GetValue(config, 'squareExpandSwatch', true); + } + + var fitRatio = (squareExpandSwatch) ? 1 : 0; + this.add( + swatch, + { proportion: 0, expand: false, fitRatio: fitRatio } + ); + } + + if (inputText) { + var proportion = (GetValue(inputTextConfig, 'width') === undefined) ? 1 : 0; + var expand = (GetValue(inputTextConfig, 'height') === undefined) ? true : false; + this.add( + inputText, + { proportion: proportion, expand: expand } + ) + } + + this.addChildrenMap('background', background); + this.addChildrenMap('swatch', swatch); + this.addChildrenMap('inputText', inputText); + + + if (inputText) { + inputText.on('close', function () { + this.setValue(inputText.value); + }, this); + } + + var callback = GetValue(config, 'valuechangeCallback', null); + if (callback !== null) { + var scope = GetValue(config, 'valuechangeCallbackScope', undefined); + this.on('valuechange', callback, scope); + } + + this.setValue(GetValue(config, 'value', 0x0)); + } + + get value() { + return this._value; + } + + set value(value) { + if (typeof (value) === 'string') { + value = ColorStringToInteger(value); + if (value == null) { + var inputText = this.childrenMap.inputText; + if (inputText) { + inputText.setText(GetHexColorString(this._value)); + } + return; + } + } else { + value = Clamp(Math.floor(value), 0, 0xffffff); + } + + if (this._value === value) { + return; + } + + this._value = value; + + var swatch = this.childrenMap.swatch; + if (swatch) { + SetSwatchColor(swatch, value); + } + + var inputText = this.childrenMap.inputText; + if (inputText) { + inputText.setText(GetHexColorString(value)); + } + + this.emit('valuechange', this._value); + } + + setValue(value) { + this.value = value; + return this; + } + + get color() { + return this._value; + } + + set color(color) { + this.value = color; + } + + setColor(color) { + this.color = color; + return this; + } + +} + +export default ColorInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/Factory.d.ts new file mode 100644 index 000000000..c28fc457f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/Factory.d.ts @@ -0,0 +1,5 @@ +import ColorInputBase from './ColorInputBase'; + +export default function ( + config?: ColorInputBase.IConfig +): ColorInputBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/Factory.js new file mode 100644 index 000000000..d81af7fa2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/Factory.js @@ -0,0 +1,13 @@ +import ColorInputBase from './ColorInputBase.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('colorInputLite', function (config) { + var gameObject = new ColorInputBase(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.ColorInputBase', ColorInputBase); + +export default ColorInputBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/methods/CreateSwatch.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/methods/CreateSwatch.js new file mode 100644 index 000000000..d3327ef68 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/methods/CreateSwatch.js @@ -0,0 +1,16 @@ +import RoundRectangle from '../../../roundrectangle/RoundRectangle.js'; +import IsGameObject from '../../../../../plugins/utils/system/IsGameObject.js'; + +var CreateSwatch = function (scene, config) { + if (config === false) { + return null; + } else if (IsGameObject(config)) { + return config; + } + + var swatch = new RoundRectangle(scene, config); + scene.add.existing(swatch); + return swatch; +} + +export default CreateSwatch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/methods/SetSwatchColor.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/methods/SetSwatchColor.js new file mode 100644 index 000000000..85a00df87 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/methods/SetSwatchColor.js @@ -0,0 +1,13 @@ +var SetSwatchColor = function (swatch, color) { + if (!swatch) { + return; + } + + if (swatch.setTint) { + swatch.setTint(color); + } else if (swatch.setFillStyle) { + swatch.setFillStyle(color); + } +} + +export default SetSwatchColor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/ColorPicker.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/ColorPicker.d.ts new file mode 100644 index 000000000..5e8ea508d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/ColorPicker.d.ts @@ -0,0 +1,38 @@ +import Sizer from '../../sizer/Sizer'; + +export default ColorPicker; + +declare namespace ColorPicker { + interface IConfig extends Sizer.IConfig { + background?: Phaser.GameObjects.GameObject, + + hPalette?: { + position?: 0 | 1 | 2 | 3 | 'bottom' | 'left' | 'top' | 'right', + size?: number, width?: number, height?: number, + }, + + svPalette?: { + width?: number, height?: number, + }, + + valuechangeCallback: (newValue: number, oldValue: number, colorPicker: ColorPicker) => void, + valuechangeCallbackScope?: Object, + + value?: number, + } +} + +declare class ColorPicker extends Sizer { + constructor( + scene: Phaser.Scene, + config?: ColorPicker.IConfig + ); + + setValue(value: number): this; + value: number; + + setColor(color: number): this; + color: number; + + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/ColorPicker.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/ColorPicker.js new file mode 100644 index 000000000..4365258a5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/ColorPicker.js @@ -0,0 +1,177 @@ +import Sizer from '../../sizer/Sizer.js'; +import HPalette from './methods/HPalette.js'; +import SVPalette from './methods/SVPalette.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class ColorPicker extends Sizer { + constructor(scene, config) { + super(scene, config); + this.type = 'rexColorPicker'; + this.freezePalettes = false; + + // orientation + var hPalettePosition = GetValue(config, 'hPalette.position', 0); + if (typeof (hPalettePosition) === 'string') { + hPalettePosition = HPalettePositionNamesMap[hPalettePosition]; + } + var orientation = ( + (hPalettePosition === 0) || // bottom + (hPalettePosition === 2) // top + ) ? 1 : // y + 0; // x + this.setOrientation(orientation) + + // Add elements + var background = GetValue(config, 'background', undefined); + + var hPaletteWidth, hPaletteHeight; + if (this.orientation === 0) { + var hPaletteWidth = GetValue(config, 'hPalette.width', undefined); + if (hPaletteWidth === undefined) { + hPaletteWidth = GetValue(config, 'hPalette.size', 10); + } + } else { + hPaletteHeight = GetValue(config, 'hPalette.height', undefined); + if (hPaletteHeight === undefined) { + hPaletteHeight = GetValue(config, 'hPalette.size', 10); + } + } + + var hPalette = new HPalette(scene, { + width: hPaletteWidth, + height: hPaletteHeight + }); + scene.add.existing(hPalette); + + var svPaletteWidth = GetValue(config, 'svPalette.width', undefined); + var svPaletteHeight = GetValue(config, 'svPalette.height', undefined); + + var svPalette = new SVPalette(scene, { + width: svPaletteWidth, + height: svPaletteHeight + }); + scene.add.existing(svPalette); + + if (background) { + this.addBackground(background); + } + + var hPaletteAddConfig = { + proportion: 0, expand: true + } + + var svPaletteProportion, svPaletteExpand; + if (this.orientation === 0) { + svPaletteProportion = (svPaletteWidth === undefined) ? 1 : 0; + svPaletteExpand = (svPaletteHeight === undefined) ? true : false; + } else { + svPaletteProportion = (svPaletteHeight === undefined) ? 1 : 0; + svPaletteExpand = (svPaletteWidth === undefined) ? true : false; + } + var svPaletteAddConfig = { + proportion: svPaletteProportion, expand: svPaletteExpand + } + + if ((hPalettePosition === 0) || (hPalettePosition === 3)) { // bottom, right + this + .add(svPalette, svPaletteAddConfig) + .add(hPalette, hPaletteAddConfig) + } else { // left, top + this + .add(hPalette, hPaletteAddConfig) + .add(svPalette, svPaletteAddConfig) + } + + hPalette + .on('input', function () { + svPalette.setHue(hPalette.getHue()); + this.setValue(svPalette.color, true); + }, this) + + svPalette + .on('input', function () { + this.setValue(svPalette.color, true); + }, this) + + this.addChildrenMap('background', background); + this.addChildrenMap('hPalette', hPalette); + this.addChildrenMap('svPalette', svPalette); + + var callback = GetValue(config, 'valuechangeCallback', null); + if (callback !== null) { + var scope = GetValue(config, 'valuechangeCallbackScope', undefined); + this.on('valuechange', callback, scope); + } + + this.setValue(GetValue(config, 'value', 0xffffff)); + } + + get value() { + return this._value; + } + + set value(value) { + if (this._value === value) { + return; + } + + var oldValue = this._value; + this._value = value; + + if (!this.freezePalettes) { + this.updatePalettes(); + } + + this.emit('valuechange', value, oldValue, this); + } + + setValue(value, freezePalettes) { + this.freezePalettes = !!freezePalettes; + this.value = value; + this.freezePalettes = false; + return this; + } + + get color() { + return this._value; + } + + set color(color) { + this.value = color; + } + + setColor(color) { + this.color = color; + return this; + } + + updatePalettes() { + this.childrenMap.hPalette.setColor(this.color); + this.childrenMap.svPalette.setColor(this.color); + return this; + } + + runLayout(parent, newWidth, newHeight) { + if (this.ignoreLayout) { + return this; + } + + super.runLayout(parent, newWidth, newHeight); + + this.childrenMap.hPalette.setMarkerPosition(this.value); + this.childrenMap.svPalette.setMarkerPosition(this.value); + + return this; + } + +} + +var HPalettePositionNamesMap = { + bottom: 0, + left: 1, + top: 2, + right: 3 +} + +export default ColorPicker; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/Factory.d.ts new file mode 100644 index 000000000..a6c15e53d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/Factory.d.ts @@ -0,0 +1,5 @@ +import ColorPicker from './ColorPicker'; + +export default function ( + config?: ColorPicker.IConfig +): ColorPicker; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/Factory.js new file mode 100644 index 000000000..ecb53c2ac --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/Factory.js @@ -0,0 +1,13 @@ +import ColorPicker from './ColorPicker.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('colorPicker', function (config) { + var gameObject = new ColorPicker(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.ColorPicker', ColorPicker); + +export default ColorPicker; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/HPalette.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/HPalette.js new file mode 100644 index 000000000..bf0bf116f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/HPalette.js @@ -0,0 +1,96 @@ +import OverlapSizer from '../../../overlapsizer/OverlapSizer.js'; +import HPaletteCanvas from './HPaletteCanvas.js'; +import RoundRectangle from '../../../roundrectangle/RoundRectangle.js'; +import { LocalToWorld } from './Transform.js'; + +class HPalette extends OverlapSizer { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + super(scene, config); + + var orientation = (config.width != null) ? 1 : 0; + var paletteCanvas = (new HPaletteCanvas(scene)) + .setOrientation(orientation) + scene.add.existing(paletteCanvas); + this.type = 'rexColorPicker.HPalette'; + + paletteCanvas + .setInteractive() + .on('pointerdown', this.onPaletteCanvasPointerDown, this) + .on('pointermove', this.onPaletteCanvasPointerDown, this) + + var marker = new RoundRectangle(scene, { strokeColor: 0xffffff, strokeWidth: 2 }); + scene.add.existing(marker); + + this + .add( + paletteCanvas, + { key: 'paletteCanvas', expand: true } + ) + .add( + marker, + { key: 'marker', expand: false } + ) + } + + resize(width, height) { + if ((this.width === width) && (this.height === height)) { + return this; + } + + super.resize(width, height); + + var size = Math.min(width, height); + this.childrenMap.marker.setSize(size, size); + + return this; + } + + onPaletteCanvasPointerDown(pointer, localX, localY, event) { + if (!pointer.isDown) { + return; + } + + var paletteCanvas = this.childrenMap.paletteCanvas; + var color = paletteCanvas.getColor(localX, localY); + this.setMarkerPosition(color); + + this.emit('input', color); + } + + get color() { + return this.childrenMap.paletteCanvas.color; + } + + setColor(color) { + if (this.color === color) { + return this; + } + + var paletteCanvas = this.childrenMap.paletteCanvas; + paletteCanvas.setColor(color); + this.setMarkerPosition(color); + + return this; + } + + setMarkerPosition(color) { + var paletteCanvas = this.childrenMap.paletteCanvas; + var marker = this.childrenMap.marker; + + var localXY = paletteCanvas.colorToLocalPosition(color, true); + LocalToWorld(paletteCanvas, localXY.x, localXY.y, marker); + this.resetChildPositionState(marker); + + return this; + } + + getHue(localX, localY) { + var paletteCanvas = this.childrenMap.paletteCanvas; + return paletteCanvas.getHue(localX, localY); + } +} + +export default HPalette; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/HPaletteCanvas.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/HPaletteCanvas.js new file mode 100644 index 000000000..d1029c05c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/HPaletteCanvas.js @@ -0,0 +1,107 @@ +import Canvas from '../../../canvas/Canvas.js'; +import GetOrientationMode from '../../../utils/GetOrientationMode.js'; +import { DrawHPalette } from '../../../../../plugins/utils/canvas/DrawHSVPalette.js'; + +const Color = Phaser.Display.Color; +const Percent = Phaser.Math.Percent; +const ColorToRGBA = Phaser.Display.Color.ColorToRGBA; +const HSVToRGB = Phaser.Display.Color.HSVToRGB; + +class HPaletteCanvas extends Canvas { + constructor(scene, x, y, width, height, orientation) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (width === undefined) { width = 2; } + if (height === undefined) { height = 2; } + + super(scene, x, y, width, height); + this.type = 'rexColorPicker.HPaletteCanvas'; + + this.colorObject = new Color(); + + this.setOrientation(orientation); + this.setSize(width, height); + } + + setOrientation(orientation) { + this.orientation = GetOrientationMode(orientation); + return this; + } + + updateTexture() { + DrawHPalette(this.canvas, this.context, this.orientation); + super.updateTexture(); + return this; + } + + get color() { + return this.colorObject.color; + } + + get hue() { + return this._hue; + } + + set hue(value) { + this._hue = value; + } + + getHue(localX, localY) { + if (localX === undefined) { + return this.hue; + } + + if (this.orientation === 0) { + this.hue = Percent(localX, 0, this.width); + } else { + this.hue = Percent(localY, 0, this.height); + } + + return this.hue; + } + + getColor(localX, localY) { + if (localX === undefined) { + return this.color; + } + + var h = this.getHue(localX, localY); + this.colorObject.setFromRGB(HSVToRGB(h, 1, 1)); + return this.colorObject.color; + } + + setColor(color) { + if (this.color === color) { + return this; + } + + return this; + } + + colorToLocalPosition(color, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + if (LocalXY === undefined) { + LocalXY = {}; + } + out = LocalXY; + } + + this.colorObject.setFromRGB(ColorToRGBA(color)); + + if (this.orientation === 0) { + out.x = this.width * this.colorObject.h; + out.y = this.height / 2; + } else { + out.x = this.width / 2; + out.y = this.height * this.colorObject.h; + } + + return out; + } +} + +var LocalXY = undefined; + +export default HPaletteCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/SVPalette.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/SVPalette.js new file mode 100644 index 000000000..747b4a87f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/SVPalette.js @@ -0,0 +1,79 @@ +import OverlapSizer from '../../../overlapsizer/OverlapSizer.js'; +import SVPaletteCanvas from './SVPaletteCanvas.js'; +import RoundRectangle from '../../../roundrectangle/RoundRectangle.js'; +import { LocalToWorld } from './Transform.js'; + +class SVPalette extends OverlapSizer { + constructor(scene, config) { + super(scene, config); + + var paletteCanvas = new SVPaletteCanvas(scene); + scene.add.existing(paletteCanvas); + this.type = 'rexColorPicker.SVPalette'; + + paletteCanvas + .setInteractive() + .on('pointerdown', this.onPaletteCanvasPointerDown, this) + .on('pointermove', this.onPaletteCanvasPointerDown, this) + + var marker = new RoundRectangle(scene, { radius: 5, strokeColor: 0xffffff, strokeWidth: 2 }); + scene.add.existing(marker); + + this + .add( + paletteCanvas, + { key: 'paletteCanvas', expand: true } + ) + .add( + marker, + { key: 'marker', expand: false } + ) + } + + onPaletteCanvasPointerDown(pointer, localX, localY, event) { + if (!pointer.isDown) { + return; + } + + var paletteCanvas = this.childrenMap.paletteCanvas; + var color = paletteCanvas.getColor(localX, localY); + this.setMarkerPosition(color); + + this.emit('input', color); + } + + get color() { + return this.childrenMap.paletteCanvas.color; + } + + setHue(hue) { + var paletteCanvas = this.childrenMap.paletteCanvas; + paletteCanvas.setHue(hue); // Redraw paletteCanvas + // Position of marker does not change + return this; + } + + setColor(color) { + if (this.color === color) { + return this; + } + + var paletteCanvas = this.childrenMap.paletteCanvas; + paletteCanvas.setColor(color); // Redraw paletteCanvas + this.setMarkerPosition(color); + return this; + } + + setMarkerPosition(color) { + var paletteCanvas = this.childrenMap.paletteCanvas; + var marker = this.childrenMap.marker; + + var localXY = paletteCanvas.colorToLocalPosition(color, true); + LocalToWorld(paletteCanvas, localXY.x, localXY.y, marker); + this.resetChildPositionState(marker); + + return this; + } +} + +export default SVPalette; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/SVPaletteCanvas.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/SVPaletteCanvas.js new file mode 100644 index 000000000..7ba907a65 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/SVPaletteCanvas.js @@ -0,0 +1,98 @@ +import Canvas from '../../../canvas/Canvas.js'; +import { DrawSVPalette } from '../../../../../plugins/utils/canvas/DrawHSVPalette.js'; + +const Color = Phaser.Display.Color; +const Percent = Phaser.Math.Percent; +const ColorToRGBA = Phaser.Display.Color.ColorToRGBA; +const HSVToRGB = Phaser.Display.Color.HSVToRGB; + +class SVPaletteCanvas extends Canvas { + constructor(scene, x, y, width, height, hue) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (width === undefined) { width = 2; } + if (height === undefined) { height = 2; } + + super(scene, x, y, width, height); + this.type = 'rexColorPicker.SVPaletteCanvas'; + + if (hue === undefined) { + hue = 1; + } + + this.colorObject = new Color(); + + this.setHue(hue); + this.setSize(width, height); + } + + get color() { + return this.colorObject.color; + } + + get hue() { + return this._hue; + } + + set hue(hue) { + if (this._hue === hue) { + return; + } + this._hue = hue; + this.colorObject.h = hue; + this.dirty = true; + } + + setHue(hue) { + this.hue = hue; + return this; + } + + updateTexture() { + DrawSVPalette(this.canvas, this.context, this.hue); + super.updateTexture(); + return this; + } + + getColor(localX, localY) { + if (localX === undefined) { + return this.colorObject.color; + } + + var s = Percent(localX, 0, this.width); + var v = 1 - Percent(localY, 0, this.height); + this.colorObject.setFromRGB(HSVToRGB(this.hue, s, v)); + return this.colorObject.color; + } + + setColor(color) { + if (this.color === color) { + return this; + } + + this.colorObject.setFromRGB(ColorToRGBA(color)); + this.setHue(this.colorObject.h); + return this; + } + + colorToLocalPosition(color, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + if (LocalXY === undefined) { + LocalXY = {}; + } + out = LocalXY; + } + + this.colorObject.setFromRGB(ColorToRGBA(color)); + out.x = this.width * this.colorObject.s; + out.y = this.height * (1 - this.colorObject.v); + + return out; + } +} + +var LocalXY = undefined; + +export default SVPaletteCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/Transform.js b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/Transform.js new file mode 100644 index 000000000..945123d9c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/Transform.js @@ -0,0 +1,27 @@ +var RotateAround = Phaser.Math.RotateAround; +var LocalToWorld = function (gameObject, localX, localY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + if (GlobOut === undefined) { + GlobOut = {}; + } + out = GlobOut; + } + + localX -= (gameObject.width * gameObject.originX); + localY -= (gameObject.height * gameObject.originY); + var point = { + x: localX * gameObject.scaleX, + y: localY * gameObject.scaleY + }; + RotateAround(point, 0, 0, -gameObject.rotation); + + out.x = gameObject.x + localX; + out.y = gameObject.y + localY; + + return out; +} + +var GlobOut; +export { LocalToWorld }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/ConfirmDialog.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/ConfirmDialog.d.ts new file mode 100644 index 000000000..64644b76d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/ConfirmDialog.d.ts @@ -0,0 +1,127 @@ +import Dialog from '../dialog/Dialog'; +import { GeneralCreateGameObjectCallbackType } from '../utils/build/GeneralCreateGameObjectCallbackType'; +import CreateBackground from '../utils/build/CreateBackground'; +import SimpleLabel from '../simplelabel/SimpleLabel'; +import CreateTextArea from '../utils/build/CreateTextArea'; +import Label from '../label/Label'; + +export default ConfirmDialog; + +declare namespace ConfirmDialog { + type AlignTypes = number | 'left' | 'center' | 'right'; + + interface IConfigClick { + mode: 0 | 1 | 'pointerup' | 'pointerdown' | 'release' | 'press', + clickInterval?: number + } + + interface IConfig { + x?: number, + y?: number, + width?: number, + height?: number, + + space?: { + left?: number, right?: number, top?: number, bottom?: number, + + title?: number, + titleLeft?: number, + titleRight?: number, + + content?: number, + contentLeft?: number, + contentRight?: number, + + actionsLeft?: number, + actionsRight?: number, + action?: number, + + choices?: number, + choicesLeft?: number, + choicesRight?: number, + choice?: number, + choiceLine?: number, + choiceColumn?: number, choiceRow?: number, + choicesBackgroundLeft?: number, + choicesBackgroundRight?: number, + choicesBackgroundTop?: number, + choicesBackgroundBottom?: number, + }; + + background?: CreateBackground.IConfig, + + title?: SimpleLabel.IConfig, + + content?: SimpleLabel.IConfig | CreateTextArea.IConfig, + + buttonMode?: 0 | 1 | 2; + button?: SimpleLabel.IConfig, + buttonA?: SimpleLabel.IConfig, + buttonB?: SimpleLabel.IConfig, + + choicesType?: string, + choice?: SimpleLabel.IConfig, + choicesWidth?: number, + choicesHeight?: number, + + proportion?: { + title?: number, + content?: number, + actions?: number, + choices?: number, + }, + + expand?: { + title?: boolean, + content?: boolean, + actions?: boolean, + choices?: boolean, + }, + + align?: { + title?: AlignTypes, + content?: AlignTypes, + actions?: AlignTypes, + choices?: AlignTypes, + }, + + click?: IConfigClick + } + + interface IResetChoiceDisplayContentConfig extends Label.IResetDisplayContentConfig { + value?: any; + } + + interface IResetDisplayContentConfig { + title?: string | Label.IResetDisplayContentConfig, + + content?: string | Label.IResetDisplayContentConfig, + + buttonA?: string | Label.IResetDisplayContentConfig, + buttonB?: string | Label.IResetDisplayContentConfig, + + choices?: (string | IResetChoiceDisplayContentConfig)[] + } + + interface ICreatorsConfig { + background?: GeneralCreateGameObjectCallbackType, + title?: SimpleLabel.ICreatorsConfig, + content?: SimpleLabel.ICreatorsConfig | CreateTextArea.ICreatorsConfig, + button?: SimpleLabel.ICreatorsConfig, + buttonA?: SimpleLabel.ICreatorsConfig, + buttonB?: SimpleLabel.ICreatorsConfig, + choice?: SimpleLabel.ICreatorsConfig, + } +} + +declare class ConfirmDialog extends Dialog { + constructor( + scene: Phaser.Scene, + config?: ConfirmDialog.IConfig, + creators?: ConfirmDialog.ICreatorsConfig + ); + + resetDisplayContent( + config?: ConfirmDialog.IResetDisplayContentConfig + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/ConfirmDialog.js b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/ConfirmDialog.js new file mode 100644 index 000000000..b11e24cc8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/ConfirmDialog.js @@ -0,0 +1,103 @@ +import Dialog from '../dialog/Dialog.js'; +import Methods from './methods/Methods.js'; +import RegisterEvents from './methods/RegisterEvents.js'; +import DeepClone from '../../../plugins/utils/object/DeepClone.js'; +import CreateBackground from '../utils/build/CreateBackground.js'; +import CreateLabel from '../utils/build/CreateLabel.js'; +import CreateContent from './methods/CreateContent.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; +import HasValue from '../../../plugins/utils/object/HasValue.js'; +import TextArea from '../textarea/TextArea.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class ConfirmDialog extends Dialog { + constructor(scene, config, creators) { + config = (config) ? DeepClone(config) : {}; + + if (creators === undefined) { + creators = {}; + } + + var createBackground = GetValue(creators, 'background', CreateBackground); + if (createBackground) { + config.background = createBackground(scene, config.background); + } else { + delete config.background; + } + + config.title = CreateLabel(scene, config.title, creators.title); + + config.content = CreateContent(scene, config.content, creators.content); + if (config.content instanceof TextArea) { + if (HasValue(config, 'height') && !HasValue(config, 'proportion.content')) { + SetValue(config, 'proportion.content', 1); + } + } + + var defaultButtonConfig = config.button; + var buttonAConfig = config.buttonA || defaultButtonConfig; + var buttonBConfig = config.buttonB || defaultButtonConfig; + var buttonMode = config.buttonMode; + if (buttonMode === undefined) { + buttonMode = (!!buttonAConfig && !!buttonBConfig) ? 2 : + (!!buttonAConfig) ? 1 : + 0; + } + + var defaultButtonCreator = creators.button; + var buttonACreators = creators.buttonA || defaultButtonCreator; + var buttonBCreators = creators.buttonB || defaultButtonCreator; + switch (buttonMode) { + case 2: + config.actions = [ + CreateLabel(scene, buttonAConfig, buttonACreators), + CreateLabel(scene, buttonBConfig, buttonBCreators), + ] + break; + + case 1: + config.actions = [ + CreateLabel(scene, buttonAConfig, buttonACreators), + ] + break; + + case 0: + break; + + default: + config.actions = []; + break; + } + + var defaultChoiceConfig = config.choice; + if (defaultChoiceConfig) { + config.choices = []; + } + + super(scene, config); + this.type = 'rexConfirmDialog'; + + this.buttonMode = buttonMode; + + this.defaultActionConfig = defaultButtonConfig; + this.defaultActionButtonCreator = defaultButtonCreator; + + this.defaultChoiceConfig = defaultChoiceConfig; + this.defaultChoiceCreator = creators.choice; + + var buttons = this.childrenMap.actions; + this.addChildrenMap('buttonA', (buttons) ? buttons[0] : null); + this.addChildrenMap('buttonB', (buttons) ? buttons[1] : null); + + // Interactive + RegisterEvents.call(this); + } +} + +Object.assign( + ConfirmDialog.prototype, + Methods +) + +export default ConfirmDialog; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/Factory.d.ts new file mode 100644 index 000000000..f0e63af4d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/Factory.d.ts @@ -0,0 +1,5 @@ +import ConfirmDialog from './ConfirmDialog'; + +export default function ( + config?: ConfirmDialog.IConfig +): ConfirmDialog; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/Factory.js new file mode 100644 index 000000000..ec21019be --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/Factory.js @@ -0,0 +1,13 @@ +import ConfirmDialog from './ConfirmDialog.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('confirmDialog', function (config, creators) { + var gameObject = new ConfirmDialog(this.scene, config, creators); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.ConfirmDialog', ConfirmDialog); + +export default ConfirmDialog; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/CreateContent.js b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/CreateContent.js new file mode 100644 index 000000000..8efa6c589 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/CreateContent.js @@ -0,0 +1,32 @@ +import CreateLabel from '../../utils/build/CreateLabel.js'; +import CreateTextArea from '../../utils/build/CreateTextArea.js' + +const GetValue = Phaser.Utils.Objects.GetValue; + +var CreateContent = function (scene, config, creators) { + var type = GetValue(config, '$type'); + if (type === undefined) { + if (config && + (config.hasOwnProperty('slider') || config.hasOwnProperty('scroller')) + ) { + type = 'textarea'; + } + } + + + var gameObject; + switch (type) { + case 'textarea': + gameObject = new CreateTextArea(scene, config, creators); + break; + + default: + gameObject = new CreateLabel(scene, config, creators); + break; + } + + scene.add.existing(gameObject); + return gameObject; +} + +export default CreateContent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/Methods.js b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/Methods.js new file mode 100644 index 000000000..de92c508e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/Methods.js @@ -0,0 +1,9 @@ +import ResetDisplayContent from './ResetDisplayContent.js'; +import Modal from './Modal.js'; + +var Methods = { + resetDisplayContent: ResetDisplayContent, + modal: Modal, +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/Modal.js b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/Modal.js new file mode 100644 index 000000000..fea389a57 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/Modal.js @@ -0,0 +1,29 @@ +import IsFunction from '../../../../plugins/utils/object/IsFunction.js'; +import ModalMethods from '../../basesizer/ModalMethods.js'; + +var Modal = function (config, onClose) { + if (IsFunction(config)) { + onClose = config; + config = undefined; + } + + if (config === undefined) { + config = {}; + } + + var zeroButtonMode = (this.buttonMode === 0); + + if (!config.hasOwnProperty('anyTouchClose')) { + config.anyTouchClose = zeroButtonMode; + } + + if (!config.hasOwnProperty('manualClose')) { + config.manualClose = !zeroButtonMode; + } + + ModalMethods.modal.call(this, config, onClose); + + return this; +} + +export default Modal; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/RegisterEvents.js b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/RegisterEvents.js new file mode 100644 index 000000000..a393dc5bb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/RegisterEvents.js @@ -0,0 +1,41 @@ +var OnPointerOverCallback = function (button) { + if (button.setHoverState) { + button.setHoverState(true); + } +} + +var OnPointerOutCallback = function (button) { + if (button.setHoverState) { + button.setHoverState(false); + } +} + +var OnChoiceButtonStateChange = function (button, groupName, index, value) { + if (button.setActiveState) { + button.setActiveState(value); + } +} + +var OnButtonEnable = function (button) { + if (button.setDisableState) { + button.setDisableState(false); + } +} + +var OnButtonDisable = function (button) { + if (button.setDisableState) { + button.setDisableState(true); + } +} + +var RegisterEvents = function () { + this + .on('button.over', OnPointerOverCallback) + .on('button.out', OnPointerOutCallback) + .on('button.enable', OnButtonEnable) + .on('button.disable', OnButtonDisable) + .on('button.statechange', OnChoiceButtonStateChange) + +} + +export default RegisterEvents; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/ResetDisplayContent.js b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/ResetDisplayContent.js new file mode 100644 index 000000000..3a21d1431 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/ResetDisplayContent.js @@ -0,0 +1,115 @@ +import CreateLabel from '../../utils/build/CreateLabel.js'; + +var ResetDisplayContent = function (config) { + if (config === undefined) { + config = {}; + } + + ResetTitle.call(this, config); + ResetContent.call(this, config); + ResetActions.call(this, config); + ResetChoices.call(this, config); + + return this; +} + +var ResetTitle = function (config) { + var title = this.childrenMap.title; + title.resetDisplayContent(config.title); +} + +var ResetContent = function (config) { + var content = this.childrenMap.content; + if (content.resetDisplayContent) { + // Label + content.resetDisplayContent(config.content); + } else { + // TextArea + var text = config.content || ''; + content.setText(text) + } +} + +var ResetActions = function (config) { + var actionButtons = this.childrenMap.actions; + if (!actionButtons) { + return; + } + + var buttonContentArray = config.buttons; + if (!buttonContentArray) { + var buttonA = actionButtons[0]; + if (buttonA) { + buttonA.resetDisplayContent(config.buttonA); + } + + var buttonB = actionButtons[1]; + if (buttonB) { + buttonB.resetDisplayContent(config.buttonB); + } + + } else { + var scene = this.scene; + var defaultActionConfig = this.defaultActionConfig; + var defaultActionButtonCreator = this.defaultActionButtonCreator; + for (var i = 0, cnt = buttonContentArray.length; i < cnt; i++) { + var buttonContent = buttonContentArray[i]; + var button = actionButtons[i]; + if (!button) { + button = CreateLabel(scene, defaultActionConfig, defaultActionButtonCreator); + this.addAction(button); + } + button.show().resetDisplayContent(buttonContent); + } + + this.buttonMode = buttonContentArray.length; + + for (var i = buttonContentArray.length, cnt = actionButtons.length; i < cnt; i++) { + actionButtons[i].hide(); + } + } +} + +var ResetChoices = function (config) { + var choices = this.childrenMap.choices; + if (!choices) { + return; + } + + var buttonContentArray = config.choices; + if (!buttonContentArray) { + buttonContentArray = []; + } + + var scene = this.scene; + var defaultChoiceConfig = this.defaultChoiceConfig; + var defaultActionButtonCreator = this.defaultActionButtonCreator; + for (var i = 0, cnt = buttonContentArray.length; i < cnt; i++) { + var buttonContent = buttonContentArray[i]; + if (typeof (buttonContent) === 'string') { + buttonContent = { text: buttonContent }; + } + + var button = choices[i]; + if (!button) { + button = CreateLabel(scene, defaultChoiceConfig, defaultActionButtonCreator); + this.addChoice(button); + } + + button.show().resetDisplayContent(buttonContent) + + var optionValue; + if (buttonContent.hasOwnProperty('value')) { + optionValue = buttonContent.value; + } else { + optionValue = buttonContent.text; + } + button.setName(optionValue) + } + + for (var i = buttonContentArray.length, cnt = choices.length; i < cnt; i++) { + choices[i].hide(); + } +} + +export default ResetDisplayContent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/container/Container.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/container/Container.d.ts new file mode 100644 index 000000000..68dbf258f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/container/Container.d.ts @@ -0,0 +1,2 @@ +import Container from '../../../plugins/containerlite'; +export default Container; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/container/Container.js b/ui/src/phaser3-rex-plugins/templates/ui/container/Container.js new file mode 100644 index 000000000..239256c56 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/container/Container.js @@ -0,0 +1,2 @@ +import Container from '../../../plugins/containerlite.js'; +export default Container; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/container/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/container/Factory.d.ts new file mode 100644 index 000000000..e95eba9ba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/container/Factory.d.ts @@ -0,0 +1,8 @@ +// import * as Phaser from 'phaser'; +import Container from "./Container"; + +export default function ( + x?: number, y?: number, + width?: number, height?: number, + children?: Phaser.GameObjects.GameObject[] +): Container; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/container/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/container/Factory.js new file mode 100644 index 000000000..7b7e558f7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/container/Factory.js @@ -0,0 +1,13 @@ +import Container from './Container.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('container', function (x, y, width, height, children) { + var gameObject = new Container(this.scene, x, y, width, height, children); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.Container', Container); + +export default Container; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/customprogress/CustomProgress.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/customprogress/CustomProgress.d.ts new file mode 100644 index 000000000..8116e2f82 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/customprogress/CustomProgress.d.ts @@ -0,0 +1,2 @@ +import CustomProgress from '../../../plugins/customprogress'; +export default CustomProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/customprogress/CustomProgress.js b/ui/src/phaser3-rex-plugins/templates/ui/customprogress/CustomProgress.js new file mode 100644 index 000000000..72c9f13ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/customprogress/CustomProgress.js @@ -0,0 +1,2 @@ +import CustomProgress from '../../../plugins/customprogress.js'; +export default CustomProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/customprogress/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/customprogress/Factory.d.ts new file mode 100644 index 000000000..54a591938 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/customprogress/Factory.d.ts @@ -0,0 +1,5 @@ +import CustomProgress from "./CustomProgress"; + +export default function ( + config?: CustomProgress.IConfig +): CustomProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/customprogress/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/customprogress/Factory.js new file mode 100644 index 000000000..9be40cdee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/customprogress/Factory.js @@ -0,0 +1,13 @@ +import CustomProgress from './CustomProgress.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('customProgress', function (x, y, width, height, config) { + var gameObject = new CustomProgress(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.CustomProgress', CustomProgress); + +export default CustomProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/customshapes/CustomShapes.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/customshapes/CustomShapes.d.ts new file mode 100644 index 000000000..a7b6fc7bf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/customshapes/CustomShapes.d.ts @@ -0,0 +1,2 @@ +import CustomShapes from '../../../plugins/customshapes'; +export default CustomShapes; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/customshapes/CustomShapes.js b/ui/src/phaser3-rex-plugins/templates/ui/customshapes/CustomShapes.js new file mode 100644 index 000000000..60aef11f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/customshapes/CustomShapes.js @@ -0,0 +1,2 @@ +import CustomShapes from '../../../plugins/customshapes.js'; +export default CustomShapes; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/customshapes/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/customshapes/Factory.d.ts new file mode 100644 index 000000000..f3b08950e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/customshapes/Factory.d.ts @@ -0,0 +1,5 @@ +import CustomShapes from "./CustomShapes"; + +export default function ( + config?: CustomShapes.IConfig +): CustomShapes; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/customshapes/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/customshapes/Factory.js new file mode 100644 index 000000000..e638bf05f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/customshapes/Factory.js @@ -0,0 +1,13 @@ +import CustomShapes from './CustomShapes.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('customShapes', function (x, y, width, height, config) { + var gameObject = new CustomShapes(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.CustomShapes', CustomShapes); + +export default CustomShapes; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dialog/Dialog.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/dialog/Dialog.d.ts new file mode 100644 index 000000000..1011ed3dc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dialog/Dialog.d.ts @@ -0,0 +1,310 @@ +// import * as Phaser from 'phaser'; +import Sizer from '../sizer/Sizer'; +import { ModalBehavoir } from '../modal/Modal'; + +export default Dialog; + +declare namespace Dialog { + + type AlignTypes = number | 'left' | 'center' | 'right'; + + interface IConfigClick { + mode: 0 | 1 | 'pointerup' | 'pointerdown' | 'release' | 'press', + clickInterval?: number + } + + interface IConfig extends Sizer.IConfig { + space?: { + left?: number, right?: number, top?: number, bottom?: number, + + title?: number, + titleLeft?: number, + titleRight?: number, + + content?: number, + contentLeft?: number, + contentRight?: number, + + description?: number, + descriptionLeft?: number, + descriptionRight?: number, + + choices?: number, + choicesLeft?: number, + choicesRight?: number, + + choice?: number, + choiceLine?: number, + choiceColumn?: number, choiceRow?: number, + choicesBackgroundLeft?: number, + choicesBackgroundRight?: number, + choicesBackgroundTop?: number, + choicesBackgroundBottom?: number, + + actionsLeft?: number, + actionsRight?: number, + + action?: number, + + toolbarItem?: number, + leftToolbarItem?: number, + + }; + + background?: Phaser.GameObjects.GameObject, + + title?: Phaser.GameObjects.GameObject, + + toolbar?: Phaser.GameObjects.GameObject[], + + toolbarBackground?: Phaser.GameObjects.GameObject, + + leftToolbar?: Phaser.GameObjects.GameObject[], + + leftToolbarBackground?: Phaser.GameObjects.GameObject, + + content?: Phaser.GameObjects.GameObject, + + description?: Phaser.GameObjects.GameObject, + + choicesType?: string, + choicesWidth?: number, + choicesHeight?: number, + choices?: Phaser.GameObjects.GameObject[], + choicesBackground?: Phaser.GameObjects.GameObject, + + actions?: Phaser.GameObjects.GameObject[], + actionsBackground?: Phaser.GameObjects.GameObject, + + proportion?: { + title?: number, + content?: number, + description?: number, + choices?: number, + actions?: number, + }, + + expand?: { + title?: boolean, + content?: boolean, + description?: boolean, + choices?: boolean, + actions?: boolean, + }, + + align?: { + title?: AlignTypes, + content?: AlignTypes, + description?: AlignTypes, + choices?: AlignTypes, + actions?: AlignTypes, + }, + + click?: IConfigClick + } + + interface IModalConfig extends ModalBehavoir.IConfig { + defaultBehavior?: boolean, + } + + type CloseEventDataType = { + index: number, + text: string, + button: Phaser.GameObjects.GameObject, + dialog: Dialog, + value: any + } + + type OnModalCloseCallbackType = (data: CloseEventDataType | Dialog) => void; +} + +declare class Dialog extends Sizer { + constructor( + scene: Phaser.Scene, + config?: Dialog.IConfig + ); + + emitChoiceClick( + index: number | Phaser.GameObjects.GameObject + ): this; + + emitActionClick( + index: number | Phaser.GameObjects.GameObject + ): this; + + emitToolbarClick( + index: number | Phaser.GameObjects.GameObject + ): this; + + emitLeftToolbarClick( + index: number | Phaser.GameObjects.GameObject + ): this; + + setChoiceEnable( + index: number | Phaser.GameObjects.GameObject, + enable?: boolean + ): this; + + setActionEnable( + index: number | Phaser.GameObjects.GameObject, + enable?: boolean + ): this; + + setToolbarEnable( + index: number | Phaser.GameObjects.GameObject, + enable?: boolean + ): this; + + setLeftToolbarEnable( + index: number | Phaser.GameObjects.GameObject, + enable?: boolean + ): this; + + toggleChoiceEnable( + index: number | Phaser.GameObjects.GameObject + ): this; + + toggleActionEnable( + index: number | Phaser.GameObjects.GameObject + ): this; + + toggleToolbarEnable( + index: number | Phaser.GameObjects.GameObject + ): this; + + toggleLeftToolbarEnable( + index: number | Phaser.GameObjects.GameObject + ): this; + + getChoiceEnable( + index: number | Phaser.GameObjects.GameObject + ): boolean; + + getActionEnable( + index: number | Phaser.GameObjects.GameObject + ): boolean; + + getToolbarEnable( + index: number | Phaser.GameObjects.GameObject + ): boolean; + + getLeftToolbarEnable( + index: number | Phaser.GameObjects.GameObject + ): boolean; + + addChoice(gameObject: Phaser.GameObjects.GameObject): this; + + addAction(gameObject: Phaser.GameObjects.GameObject): this; + + addToolbar(gameObject: Phaser.GameObjects.GameObject): this; + + addLeftToolbar(gameObject: Phaser.GameObjects.GameObject): this; + + removeChoice( + index: number | Phaser.GameObjects.GameObject, + destroyChild?: boolean + ): this; + + removeAction( + index: number | Phaser.GameObjects.GameObject, + destroyChild?: boolean + ): this; + + removeToolbar( + index: number | Phaser.GameObjects.GameObject, + destroyChild?: boolean + ): this; + + removeLeftToolbar( + index: number | Phaser.GameObjects.GameObject, + destroyChild?: boolean + ): this; + + clearChoices(destroyChild?: boolean): this; + + clearActions(destroyChild?: boolean): this; + + clearToolbar(destroyChild?: boolean): this; + + clearLeftToolbar(destroyChild?: boolean): this; + + showChoice( + index: number | Phaser.GameObjects.GameObject + ): this; + + showAction( + index: number | Phaser.GameObjects.GameObject + ): this; + + showToolbar( + index: number | Phaser.GameObjects.GameObject + ): this; + + showLeftToolbar( + index: number | Phaser.GameObjects.GameObject + ): this; + + hideChoice( + index: number | Phaser.GameObjects.GameObject + ): this; + + hideAction( + index: number | Phaser.GameObjects.GameObject + ): this; + + hideToolbar( + index: number | Phaser.GameObjects.GameObject + ): this; + + hideLeftToolbar( + index: number | Phaser.GameObjects.GameObject + ): this; + + forEachChoice( + callback: (button: Phaser.GameObjects.GameObject, index: number, buttons: Phaser.GameObjects.GameObject[]) => void, + scop?: unknown + ): this; + + forEachAction( + callback: (button: Phaser.GameObjects.GameObject, index: number, buttons: Phaser.GameObjects.GameObject[]) => void, + scop?: unknown + ): this; + + forEachToolbar( + callback: (button: Phaser.GameObjects.GameObject, index: number, buttons: Phaser.GameObjects.GameObject[]) => void, + scop?: unknown + ): this; + + forEachLeftToolbar( + callback: (button: Phaser.GameObjects.GameObject, index: number, buttons: Phaser.GameObjects.GameObject[]) => void, + scop?: unknown + ): this; + + setAllButtonsEnable(enable?: boolean): this; + + getChoicesButtonState(name: string): boolean; + getChoicesButtonState(): { [name: string]: boolean }; + + getChoicessButtonStates(): { [name: string]: boolean }; + + setChoicesButtonState(name: string, state?: boolean): this; + + clearChoicesButtonStates(): this; + + getChoicesSelectButtonName(): string; + + modal( + config?: Dialog.IModalConfig, + onClose?: Dialog.OnModalCloseCallbackType + ): this; + + modal( + onClose?: Dialog.OnModalCloseCallbackType + ): this; + + modalPromise( + config?: Dialog.IModalConfig, + ): Promise; + + modalClose(closeEventData?: Dialog.CloseEventDataType): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dialog/Dialog.js b/ui/src/phaser3-rex-plugins/templates/ui/dialog/Dialog.js new file mode 100644 index 000000000..d0731dd19 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dialog/Dialog.js @@ -0,0 +1,306 @@ +import Sizer from '../sizer/Sizer.js'; +import OverlapSizer from '../overlapsizer/OverlapSizer.js'; +import Buttons from '../buttons/Buttons.js'; +import FixWidthButtons from '../fixwidthbuttons/FixWidthButtons.js'; +import GridButtons from '../gridbuttons/GridButtons.js'; +import Methods from './methods/Methods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Dialog extends Sizer { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + // Create sizer + config.orientation = 1; // Top to bottom + super(scene, config); + this.type = 'rexDialog'; + this.eventEmitter = GetValue(config, 'eventEmitter', this); + + // Add elements + var background = GetValue(config, 'background', undefined); + var title = GetValue(config, 'title', undefined); + var toolbar = GetValue(config, 'toolbar', undefined); + var toolbarBackground = GetValue(config, 'toolbarBackground', undefined); + var leftToolbar = GetValue(config, 'leftToolbar', undefined); + var leftToolbarBackground = GetValue(config, 'leftToolbarBackground', undefined); + var content = GetValue(config, 'content', undefined); + var description = GetValue(config, 'description', undefined); + var choicesSizer; + var choices = GetValue(config, 'choices', undefined); + var choicesBackground = GetValue(config, 'choicesBackground', undefined); + var actionsSizer; + var actions = GetValue(config, 'actions', undefined); + var actionsBackground = GetValue(config, 'actionsBackground', undefined); + var clickConfig = GetValue(config, 'click', undefined); + + if (background) { + this.addBackground(background); + } + + var toolbarSizer; + if (toolbar) { + toolbarSizer = new Buttons(scene, { + groupName: 'toolbar', + background: toolbarBackground, + buttons: toolbar, + orientation: 0, // Left-right + space: { item: GetValue(config, 'space.toolbarItem', 0) }, + click: clickConfig, + eventEmitter: this.eventEmitter, + }); + } + + var leftToolbarSizer; + if (leftToolbar) { + leftToolbarSizer = new Buttons(scene, { + groupName: 'leftToolbar', + background: leftToolbarBackground, + buttons: leftToolbar, + orientation: 0, // Left-right + space: { item: GetValue(config, 'space.leftToolbarItem', 0) }, + click: clickConfig, + eventEmitter: this.eventEmitter, + }); + } + + // title or toolbar or leftToolbar + if (title || toolbar || leftToolbar) { + var titleExpandWidth = !!title && GetValue(config, 'expand.title', true); + var titleAlign = GetValue(config, 'align.title', 'center'); + var useOverlapSizer = + // Has title, title is not exapnd-width, title align to center + (title && !titleExpandWidth && (titleAlign === 'center')) || + // No title + (!title && (toolbar || leftToolbar)); + var useSizer = !useOverlapSizer; + + var titleSizer; + if (useSizer) { + titleSizer = new Sizer(scene, { orientation: 0 }); + } else { + titleSizer = new OverlapSizer(scene); + } + + var titleChildExpand = (useSizer) ? true : { height: true }; + + // Add leftToolbar + if (leftToolbarSizer) { + titleSizer.add( + leftToolbarSizer, + { align: 'left', expand: titleChildExpand } + ); + } + + // Add title + if (title) { + // Add space if not expand, align to right + if (useSizer && !titleExpandWidth && (titleAlign === 'right')) { + titleSizer.addSpace(); + } + + var padding = { + left: GetValue(config, 'space.titleLeft', 0), + right: GetValue(config, 'space.titleRight', 0) + } + var proportion = (titleExpandWidth) ? 1 : 0; + titleSizer.add( + title, + { align: titleAlign, proportion: proportion, expand: titleChildExpand, padding: padding } + ); + + // Add space if not expand, align to left + if (useSizer && !titleExpandWidth && (titleAlign === 'left')) { + titleSizer.addSpace(); + } + } + + // Add toolbar + if (toolbarSizer) { + // Add space if not title + if (useSizer && !title) { + titleSizer.addSpace(); + } + titleSizer.add( + toolbarSizer, + { align: 'right', expand: titleChildExpand } + ); + } + + // Add sizer to dialog + var titleSpace = GetValue(config, 'space.title', 0); + var padding; + if (content || description || choices || actions) { + padding = { bottom: titleSpace }; + } + var proportion = GetValue(config, 'proportion.title', 0); + this.add( + titleSizer, + { padding: padding, proportion: proportion, expand: true } + ); + } + + if (content) { + var align = GetValue(config, 'align.content', 'center'); + var contentSpace = GetValue(config, 'space.content', 0); + var padding = { + left: GetValue(config, 'space.contentLeft', 0), + right: GetValue(config, 'space.contentRight', 0), + bottom: ((description || choices || actions) ? contentSpace : 0) + } + var proportion = GetValue(config, 'proportion.content', 0); + var expand = GetValue(config, 'expand.content', true); + this.add( + content, + { align: align, padding: padding, proportion: proportion, expand: expand } + ); + } + + if (description) { + var align = GetValue(config, 'align.description', 'center'); + var descriptionSpace = GetValue(config, 'space.description', 0); + var padding = { + left: GetValue(config, 'space.descriptionLeft', 0), + right: GetValue(config, 'space.descriptionRight', 0), + bottom: ((choices || actions) ? descriptionSpace : 0) + } + var proportion = GetValue(config, 'proportion.description', 0); + var expand = GetValue(config, 'expand.description', true); + this.add( + description, + { align: align, padding: padding, proportion: proportion, expand: expand } + ); + } + + if (choices) { + var choicesType = GetValue(config, 'choicesType', '').split('-'); + var ButtonsClass = Contains(choicesType, 'wrap') ? FixWidthButtons : + Contains(choicesType, 'grid') ? GridButtons : + Buttons; + var buttonsType = Contains(choicesType, 'radio') ? 'radio' : + Contains(choicesType, 'checkboxes') ? 'checkboxes' : undefined; + + var space = { + left: GetValue(config, 'space.choicesBackgroundLeft', 0), + right: GetValue(config, 'space.choicesBackgroundRight', 0), + top: GetValue(config, 'space.choicesBackgroundTop', 0), + bottom: GetValue(config, 'space.choicesBackgroundBottom', 0), + }; + var itemSpace = GetValue(config, 'space.choice', 0); + if (ButtonsClass === Buttons) { + space.item = itemSpace; + } else if (ButtonsClass === FixWidthButtons) { + space.item = itemSpace; + space.line = GetValue(config, 'space.choiceLine', itemSpace); + } else { // GridButtons + space.column = GetValue(config, 'space.choiceColumn', itemSpace); + space.row = GetValue(config, 'space.choiceRow', itemSpace); + } + + var choicesConfig = { + width: GetValue(config, 'choicesWidth', undefined), + height: GetValue(config, 'choicesHeight', undefined), + groupName: 'choices', + buttonsType: buttonsType, + background: choicesBackground, + buttons: choices, + space: space, + click: clickConfig, + eventEmitter: this.eventEmitter, + setValueCallback: GetValue(config, 'choicesSetValueCallback', undefined), + setValueCallbackScope: GetValue(config, 'choicesSetValueCallbackScope', undefined) + }; + + if (ButtonsClass === Buttons) { + choicesConfig.orientation = Contains(choicesType, 'x') ? 0 : 1; + } + + choicesSizer = new ButtonsClass(scene, choicesConfig); + var choicesSpace = GetValue(config, 'space.choices', 0); + var padding = { + left: GetValue(config, 'space.choicesLeft', 0), + right: GetValue(config, 'space.choicesRight', 0), + bottom: ((actions) ? choicesSpace : 0) + } + var align = GetValue(config, 'align.choices', 'center'); + var proportion = GetValue(config, 'proportion.choices', 0); + var expand = GetValue(config, 'expand.choices', true); + this.add( + choicesSizer, + { align: align, padding: padding, proportion: proportion, expand: expand } + ); + + this.buttonsType = buttonsType; + } + + if (actions) { + actionsSizer = new Buttons(scene, { + groupName: 'actions', + background: actionsBackground, + buttons: actions, + orientation: 0, // Left-right + space: { item: GetValue(config, 'space.action', 0) }, + expand: GetValue(config, 'expand.actions', false), + align: GetValue(config, 'align.actions', 'center'), + click: clickConfig, + eventEmitter: this.eventEmitter, + }) + var padding = { + left: GetValue(config, 'space.actionsLeft', 0), + right: GetValue(config, 'space.actionsRight', 0) + } + var proportion = GetValue(config, 'proportion.action', 0); + this.add( + actionsSizer, + { align: 'center', padding: padding, proportion: proportion, expand: true } + ); + } + + EmitButtonEvent(this, 'click'); + EmitButtonEvent(this, 'over'); + EmitButtonEvent(this, 'out'); + EmitButtonEvent(this, 'enable'); + EmitButtonEvent(this, 'disable'); + + this.addChildrenMap('background', background); + this.addChildrenMap('title', title); + this.addChildrenMap('toolbar', toolbar); + this.addChildrenMap('leftToolbar', leftToolbar); + this.addChildrenMap('content', content); + this.addChildrenMap('description', description); + this.addChildrenMap('choices', (choicesSizer) ? choicesSizer.buttons : undefined); + this.addChildrenMap('actions', (actionsSizer) ? actionsSizer.buttons : undefined); + this.addChildrenMap('choicesSizer', choicesSizer); + this.addChildrenMap('actionsSizer', actionsSizer); + this.addChildrenMap('toolbarSizer', toolbarSizer); + this.addChildrenMap('leftToolbarSizer', leftToolbarSizer); + } +} + +var Contains = function (arr, item) { + return arr.indexOf(item) !== -1; +} + +var ButtonsGroupEventNameMap = { + actions: 'action', + choices: 'choice', + toolbar: 'toolbar', + leftToolbar: 'leftToolbar' +} + +var EmitButtonEvent = function (dialog, postEventName) { + dialog.on(`button.${postEventName}`, function (button, groupName, index, pointer, event) { + if (!ButtonsGroupEventNameMap.hasOwnProperty(groupName)) { + return + } + dialog.emit(`${ButtonsGroupEventNameMap[groupName]}.${postEventName}`, button, index, pointer, event); + }) +} + +Object.assign( + Dialog.prototype, + Methods +); + +export default Dialog; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dialog/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/dialog/Factory.d.ts new file mode 100644 index 000000000..571e99694 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dialog/Factory.d.ts @@ -0,0 +1,5 @@ +import Dialog from './Dialog'; + +export default function ( + config?: Dialog.IConfig +): Dialog; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dialog/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/dialog/Factory.js new file mode 100644 index 000000000..93ee0876f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dialog/Factory.js @@ -0,0 +1,13 @@ +import Dialog from './Dialog.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('dialog', function (config) { + var gameObject = new Dialog(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.Dialog', Dialog); + +export default Dialog; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dialog/methods/ButtonMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/dialog/methods/ButtonMethods.js new file mode 100644 index 000000000..585de5d01 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dialog/methods/ButtonMethods.js @@ -0,0 +1,333 @@ +export default { + getChoice(index) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + return choicesSizer.getButton(index); + } else { + return undefined; + } + }, + + getAction(index) { + return this.childrenMap.actionsSizer.getButton(index); + }, + + getToolbar(index) { + return this.childrenMap.toolbarSizer.getButton(index); + }, + + getLeftToolbar(index) { + return this.childrenMap.leftToolbarSizer.getButton(index); + }, + + setChoiceEnable(index, enabled) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + choicesSizer.setButtonEnable(index, enabled); + } + return this; + }, + + setActionEnable(index, enabled) { + this.childrenMap.actionsSizer.setButtonEnable(index, enabled); + return this; + }, + + setToolbarEnable(index, enabled) { + this.childrenMap.toolbarSizer.setButtonEnable(index, enabled); + return this; + }, + + setLeftToolbarEnable(index, enabled) { + this.childrenMap.leftToolbarSizer.setButtonEnable(index, enabled); + return this; + }, + + toggleChoiceEnable(index) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + choicesSizer.toggleButtonEnable(index); + } + return this; + }, + + toggleActionEnable(index) { + this.childrenMap.actionsSizer.toggleButtonEnable(index); + return this; + }, + + toggleToolbarEnable(index) { + this.childrenMap.toolbarSizer.toggleButtonEnable(index); + return this; + }, + + toggleLeftToolbarEnable(index) { + this.childrenMap.leftToolbarSizer.toggleButtonEnable(index); + return this; + }, + + getChoiceEnable(index) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + return choicesSizer.getButtonEnable(index); + } else { + return false; + } + }, + + getActionEnable(index) { + return this.childrenMap.actionsSizer.getButtonEnable(index); + }, + + getToolbarEnable(index) { + return this.childrenMap.toolbarSizer.getButtonEnable(index); + }, + + getLeftToolbarEnable(index) { + return this.childrenMap.leftToolbarSizer.getButtonEnable(index); + }, + + emitChoiceClick(index) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + choicesSizer.emitButtonClick(index); + } + return this; + }, + + emitActionClick(index) { + this.childrenMap.actionsSizer.emitButtonClick(index); + return this; + }, + + emitToolbarClick(index) { + this.childrenMap.toolbarSizer.emitButtonClick(index); + return this; + }, + + emitLeftToolbarClick(index) { + this.childrenMap.leftToolbarSizer.emitButtonClick(index); + return this; + }, + + showChoice(index) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + choicesSizer.showButton(index); + } + return this; + }, + + showAction(index) { + this.childrenMap.actionsSizer.showButton(index); + return this; + }, + + showToolbar(index) { + this.childrenMap.toolbarSizer.showButton(index); + return this; + }, + + showLeftToolbar(index) { + this.childrenMap.leftToolbarSizer.showButton(index); + return this; + }, + + hideChoice(index) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + choicesSizer.hideButton(index); + } + return this; + }, + + hideAction(index) { + this.childrenMap.actionsSizer.hideButton(index); + return this; + }, + + hideToolbar(index) { + this.childrenMap.toolbarSizer.hideButton(index); + return this; + }, + + hideLeftToolbar(index) { + this.childrenMap.leftToolbarSizer.hideButton(index); + return this; + }, + + addChoice(gameObject) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + choicesSizer.addButton(gameObject); + } + return this; + }, + + addAction(gameObject) { + this.childrenMap.actionsSizer.addButton(gameObject); + return this; + }, + + addToolbar(gameObject) { + this.childrenMap.toolbarSizer.addButton(gameObject); + return this; + }, + + addLeftToolbar(gameObject) { + this.childrenMap.leftToolbarSizer.addButton(gameObject); + return this; + }, + + removeChoice(index, destroyChild) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + choicesSizer.removeButton(index, destroyChild); + } + return this; + }, + + removeAction(index, destroyChild) { + this.childrenMap.actionsSizer.removeButton(index, destroyChild); + return this; + }, + + removeToolbar(index, destroyChild) { + this.childrenMap.toolbarSizer.removeButton(index, destroyChild); + return this; + }, + + removeLeftToolbar(index, destroyChild) { + this.childrenMap.leftToolbarSizer.removeButton(index, destroyChild); + return this; + }, + + clearChoices(destroyChild) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + choicesSizer.clearButtons(destroyChild); + } + return this; + }, + + clearActions(destroyChild) { + this.childrenMap.actionsSizer.clearButtons(destroyChild); + return this; + }, + + clearToolbar(destroyChild) { + this.childrenMap.toolbarSizer.clearButtons(destroyChild); + return this; + }, + + clearLeftToolbar(destroyChild) { + this.childrenMap.leftToolbarSizer.clearButtons(destroyChild); + return this; + }, + + forEachChoice(callback, scope) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + choicesSizer.forEachButtton(callback, scope); + } + return this; + }, + + forEachAction(callback, scope) { + this.childrenMap.actionsSizer.forEachButtton(callback, scope); + return this; + }, + + forEachToolbar(callback, scope) { + this.childrenMap.toolbarSizer.forEachButtton(callback, scope); + return this; + }, + + forEachLeftToolbar(callback, scope) { + this.childrenMap.leftToolbarSizer.forEachButtton(callback, scope); + return this; + }, + + setAllButtonsEnable(enabled) { + if (enabled === undefined) { + enabled = true; + } + + if (this.childrenMap.toolbarSizer) { + this.setToolbarEnable(enabled); + } + if (this.childrenMap.leftToolbarSizer) { + this.setLeftToolbarEnable(enabled); + } + if (this.childrenMap.actionsSizer) { + this.setActionEnable(enabled); + } + if (this.childrenMap.choicesSizer) { + this.setChoiceEnable(enabled); + } + + return this; + }, + + // Checkboxes + getChoicesButtonStates() { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + return choicesSizer.getAllButtonsState(); + } else { + return {}; + } + }, + + getChoicesButtonState(name) { + var choicesSizer = this.childrenMap.choicesSizer; + if (name === undefined) { + if (choicesSizer) { + return choicesSizer.getAllButtonsState(); + } else { + return {} + } + } else { + if (choicesSizer) { + return choicesSizer.getButtonState(name); + } else { + return false; + } + } + }, + + setChoicesButtonState(name, state) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + choicesSizer.setButtonState(name, state); + } + return this; + }, + + clearChoicesButtonStates() { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + choicesSizer.clearAllButtonsState(); + } + return this; + }, + + // Radio buttons + getChoicesSelectedButtonName() { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + return choicesSizer.getSelectedButtonName(); + } else { + return ''; + } + }, + + setChoicesSelectedButtonName(name) { + var choicesSizer = this.childrenMap.choicesSizer; + if (choicesSizer) { + choicesSizer.setSelectedButtonName(name); + } + return this; + }, + +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dialog/methods/Methods.js b/ui/src/phaser3-rex-plugins/templates/ui/dialog/methods/Methods.js new file mode 100644 index 000000000..c768cbb73 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dialog/methods/Methods.js @@ -0,0 +1,12 @@ +import ButtonMethods from './ButtonMethods.js'; +import ModalMethods from './ModalMethods.js'; + +var Methods = {}; + +Object.assign( + Methods, + ButtonMethods, + ModalMethods, +) + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dialog/methods/ModalMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/dialog/methods/ModalMethods.js new file mode 100644 index 000000000..0d8a3787a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dialog/methods/ModalMethods.js @@ -0,0 +1,43 @@ +import ModalMethods from '../../basesizer/ModalMethods.js'; + +export default { + onCreateModalBehavior(self) { + self.on('button.click', function (button, groupName, index, pointer, event) { + if (groupName !== 'actions') { + return; + } + + var closeEventData = { + index: index, + text: button.text, + button: button, + dialog: self + } + + + switch (self.buttonsType) { + case 'radio': + closeEventData.value = self.getChoicesSelectedButtonName(); + break; + case 'checkboxes': + closeEventData.value = self.getChoicesButtonStates(); + break; + default: + closeEventData.value = undefined; + } + + self.modalClose(closeEventData); + }); + }, + + modal(config, onClose) { + if (config && (config.defaultBehavior === false)) { + this.onCreateModalBehavior = false; + } else { + delete this.onCreateModalBehavior; + } + + ModalMethods.modal.call(this, config, onClose); + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/drag/Drag.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/drag/Drag.d.ts new file mode 100644 index 000000000..979a4674c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/drag/Drag.d.ts @@ -0,0 +1,2 @@ +import Drag from '../../../plugins/drag'; +export default Drag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/drag/Drag.js b/ui/src/phaser3-rex-plugins/templates/ui/drag/Drag.js new file mode 100644 index 000000000..732c06df1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/drag/Drag.js @@ -0,0 +1,2 @@ +import Drag from '../../../plugins/drag.js'; +export default Drag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/drag/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/drag/Factory.d.ts new file mode 100644 index 000000000..9dacc3d1a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/drag/Factory.d.ts @@ -0,0 +1,7 @@ +// import * as Phaser from 'phaser'; +import Drag from "./Drag"; + +export default function ( + gameObject: Phaser.GameObjects.GameObject, + config?: Drag.IConfig +): Drag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/drag/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/drag/Factory.js new file mode 100644 index 000000000..4605f875b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/drag/Factory.js @@ -0,0 +1,11 @@ +import Drag from './Drag.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('drag', function (gameObject, config) { + return new Drag(gameObject, config); +}); + +SetValue(window, 'RexPlugins.UI.Drag', Drag); + +export default Drag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dropdown/DropDown.js b/ui/src/phaser3-rex-plugins/templates/ui/dropdown/DropDown.js new file mode 100644 index 000000000..4006d3e4e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dropdown/DropDown.js @@ -0,0 +1,3 @@ +import DropDown from '../../../plugins/dropdown.js'; + +export default DropDown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/DropDownList.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/DropDownList.d.ts new file mode 100644 index 000000000..3648d8717 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/DropDownList.d.ts @@ -0,0 +1,130 @@ +import Label from '../label/Label'; + +export default DropDownList; + +declare namespace DropDownList { + type CreateButtonCallbackType = ( + this: DropDownList, + scene: Phaser.Scene, + option: any, + index: number, + options: any[] + ) => Phaser.GameObjects.GameObject; + + type CreateBackgroundCallbackType = ( + this: DropDownList, + scene: Phaser.Scene, + ) => Phaser.GameObjects.GameObject; + + type OnButtonClickCallbackType = ( + this: DropDownList, + button: Phaser.GameObjects.GameObject, + index: number, + pointer: Phaser.Input.Pointer, + event: Phaser.Types.Input.EventData + ) => void; + + type OnButtonOverCallbackType = ( + this: DropDownList, + button: Phaser.GameObjects.GameObject, + index: number, + pointer: Phaser.Input.Pointer, + event: Phaser.Types.Input.EventData + ) => void; + + type OnButtonOutCallbackType = ( + this: DropDownList, + button: Phaser.GameObjects.GameObject, + index: number, + pointer: Phaser.Input.Pointer, + event: Phaser.Types.Input.EventData + ) => void; + + type AlignParentType = 'text' | 'icon'; + + type ExpandDirectionType = 0 | 1 | 'down' | 'up'; + + type SetValueCallbackType = ( + dropDownList: DropDownList, + value?: any, + previousValue?: any, + ) => void; + + type ListSpaceType = { + left?: number, right?: number, top?: number, bottom?: number, item?: number + }; + + type WrapListSpaceType = { + left?: number, right?: number, top?: number, bottom?: number, item?: number, line?: number + } + + interface IConfig extends Label.IConfig { + options?: any[], + list?: { + createBackgroundCallback?: CreateBackgroundCallbackType; + createButtonCallback?: CreateButtonCallbackType; + + onButtonClick?: OnButtonClickCallbackType; + onButtonOver?: OnButtonOverCallbackType; + onButtonOut?: OnButtonOutCallbackType; + + easeIn?: number; + easeOut?: number; + + wrap?: boolean; + width?: number; + height?: number; + alignParent?: AlignParentType; + alignSide?: string; + expandDirection?: ExpandDirectionType; + bounds?: Phaser.Geom.Rectangle; + + space?: ListSpaceType | WrapListSpaceType; + + draggable?: boolean; + }, + + setValueCallback?: SetValueCallbackType; + setValueCallbackScope?: object; + value?: any; + } +} + +declare class DropDownList extends Label { + constructor( + scene: Phaser.Scene, + config?: DropDownList.IConfig + ); + + setOptions(options: any[]): this; + + openListPanel(): this; + closeListPanel(): this; + toggleListPanel(): this; + + setValue(value?: any): this; + value: any; + + setCreateButtonCallback(callback?: DropDownList.CreateBackgroundCallbackType): this; + setCreateBackgroundCallback(callback?: DropDownList.CreateBackgroundCallbackType): this; + + setButtonClickCallback(callback?: DropDownList.OnButtonClickCallbackType): this; + setButtonOverCallback(callback?: DropDownList.OnButtonOverCallbackType): this; + setButtonOutCallback(callback?: DropDownList.OnButtonOutCallbackType): this; + + setListEaseInDuration(duration?: number): this; + setListEaseOutDuration(duration?: number): this; + + setWrapEnable(enable?: boolean): this; + setListWidth(width?: number): this; + setListHeight(height?: number): this; + setListSize(width?: number, height?: number): this; + + setListAlignmentMode(mode?: DropDownList.AlignParentType): this; + setListAlignmentSide(side?: string): this; + setListBounds(bounds: Phaser.Geom.Rectangle): this; + + setListSpace(space?: DropDownList.ListSpaceType | DropDownList.WrapListSpaceType): this; + + setListDraggable(enable?: boolean): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/DropDownList.js b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/DropDownList.js new file mode 100644 index 000000000..999395ca3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/DropDownList.js @@ -0,0 +1,119 @@ +import Label from '../label/Label.js'; +import Methods from './methods/Methods.js' + + +const GetValue = Phaser.Utils.Objects.GetValue; + +class DropDownList extends Label { + constructor(scene, config) { + super(scene, config); + this.type = 'rexDropDownList'; + this.timer = undefined; + + this.setOptions(GetValue(config, 'options')); + + var listConfig = GetValue(config, 'list'); + this.setWrapEnable(GetValue(listConfig, 'wrap', false)); + this.setCreateButtonCallback(GetValue(listConfig, 'createButtonCallback')); + this.setCreateListBackgroundCallback(GetValue(listConfig, 'createBackgroundCallback')); + this.setButtonClickCallback(GetValue(listConfig, 'onButtonClick')); + this.setButtonOverCallback(GetValue(listConfig, 'onButtonOver')); + this.setButtonOutCallback(GetValue(listConfig, 'onButtonOut')); + this.setListExpandDirection(GetValue(listConfig, 'expandDirection')); + this.setListEaseInDuration(GetValue(listConfig, 'easeIn', 500)); + this.setListEaseOutDuration(GetValue(listConfig, 'easeOut', 100)); + this.setListTransitInCallback(GetValue(listConfig, 'transitIn')); + this.settListTransitOutCallback(GetValue(listConfig, 'transitOut')); + this.setListSize(GetValue(listConfig, 'width'), GetValue(listConfig, 'height')); + this.setListAlignmentMode(GetValue(listConfig, 'alignParent', 'text')); + this.setListAlignmentSide(GetValue(listConfig, 'alignSide', '')); + this.setListBounds(GetValue(listConfig, 'bounds')); + this.setListSpace(GetValue(listConfig, 'space')); + this.setListDraggable(GetValue(listConfig, 'draggable', false)); + + this.setValueChangeCallback( + GetValue(config, 'setValueCallback'), + GetValue(config, 'setValueCallbackScope') + ); + this.setValue(GetValue(config, 'value')); + + this.onClick(this.toggleListPanel, this); + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + if (this.listPanel) { + this.listPanel.destroy(fromScene); + this.listPanel = undefined; + } + + super.destroy(fromScene); + } + + setOptions(options) { + if (options === undefined) { + options = []; + } + this.options = options; + return this; + } + + setValueChangeCallback(callback, scope) { + this.valueChangeCallback = callback; + this.valueChangeCallbackScope = scope; + return this; + } + + setValue(value) { + this.value = value; + return this; + } + + get value() { + return this._value; + } + + set value(value) { + if (this._value === value) { + return; + } + + var previousValue = this._value; + this._value = value; + + var callback = this.valueChangeCallback, + scope = this.valueChangeCallbackScope; + if (callback) { + if (scope) { + callback.call(scope, this, value, previousValue); + } else { + callback(this, value, previousValue) + } + } + + this.emit('valuechange', this, value, previousValue); + + } + + emitButtonClick(index) { + var option = this.options[index]; + if (!option) { + return this; + } + + this.emit('button.click', this, undefined, option, index); + return this; + } + +} + +Object.assign( + DropDownList.prototype, + Methods, +); + +export default DropDownList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/Factory.d.ts new file mode 100644 index 000000000..7e6119454 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/Factory.d.ts @@ -0,0 +1,5 @@ +import DropDownList from './DropDownList'; + +export default function ( + config?: DropDownList.IConfig +): DropDownList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/Factory.js new file mode 100644 index 000000000..9443aaf31 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/Factory.js @@ -0,0 +1,13 @@ +import DropDownList from './DropDownList.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('dropDownList', function (config) { + var gameObject = new DropDownList(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.DropDownList', DropDownList); + +export default DropDownList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/Methods.js b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/Methods.js new file mode 100644 index 000000000..afa71232b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/Methods.js @@ -0,0 +1,18 @@ +import ConfigurationMethods from './listpanel/ConfigurationMethods.js'; +import OpenListPanel from './listpanel/OpenListPanel.js'; +import CloseListPanel from './listpanel/CloseListPanel.js'; +import ToggleListPanel from './listpanel/ToggleListPanel.js'; + +var Methods = { + openListPanel: OpenListPanel, + closeListPanel: CloseListPanel, + toggleListPanel: ToggleListPanel, +} + +Object.assign( + Methods, + ConfigurationMethods, +); + +export default Methods; + diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/CloseListPanel.js b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/CloseListPanel.js new file mode 100644 index 000000000..343fd49ab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/CloseListPanel.js @@ -0,0 +1,11 @@ +var CloseListPanel = function () { + if (!this.dropDownBehavior) { + return this; + } + + this.dropDownBehavior.requestClose(); + + return this; +} + +export default CloseListPanel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/ConfigurationMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/ConfigurationMethods.js new file mode 100644 index 000000000..3a970b129 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/ConfigurationMethods.js @@ -0,0 +1,129 @@ +var methods = { + setWrapEnable(enable) { + if (enable === undefined) { + enable = true; + } + + this.listWrapEnable = enable; + return this; + }, + + setCreateButtonCallback(callback) { + this.listCreateButtonCallback = callback; + return this; + }, + + setCreateListBackgroundCallback(callback) { + this.listCreateBackgroundCallback = callback; + return this; + }, + + setButtonClickCallback(callback) { + this.listOnButtonClick = callback; + return this; + }, + + setButtonOverCallback(callback) { + this.listOnButtonOver = callback; + return this; + }, + + setButtonOutCallback(callback) { + this.listOnButtonOut = callback; + return this; + }, + + setListExpandDirection(direction) { + if (typeof (direction) === 'string') { + direction = ListExpandDirections[direction]; + } + this.listExpandDirection = direction; + return this; + }, + + setListEaseInDuration(duration) { + if (duration === undefined) { + duration = 0; + } + this.listEaseInDuration = duration; + return this; + }, + + setListEaseOutDuration(duration) { + if (duration === undefined) { + duration = 0; + } + this.listEaseOutDuration = duration; + return this; + }, + + setListTransitInCallback(callback) { + this.listTransitInCallback = callback; + // callback = function(gameObject, duration) {} + return this; + }, + + settListTransitOutCallback(callback) { + this.listTransitOutCallback = callback; + // callback = function(gameObject, duration) {} + return this; + }, + + setListBounds(bounds) { + this.listBounds = bounds; + return this; + }, + + setListWidth(width) { + this.listWidth = width; + return this; + }, + + setListHeight(height) { + this.listHeight = height; + return this; + }, + + setListSize(width, height) { + this.setListWidth(width).setListHeight(height); + return this; + }, + + setListAlignmentMode(mode) { + this.listAlignMode = mode; + return this; + }, + + setListAlignmentSide(side) { + if (side === undefined) { + side = ''; + } + + this.listAlignSide = side; + return this; + }, + + setListSpace(space) { + if (space === undefined) { + space = {}; + } + this.listSpace = space; + return this; + }, + + setListDraggable(enable) { + if (enable === undefined) { + enable = true; + } + this.listDraggable = enable; + return this; + }, + +} + +const ListExpandDirections = { + down: 0, + up: 1 +} + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/CreateListPanel.js b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/CreateListPanel.js new file mode 100644 index 000000000..d15bfe807 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/CreateListPanel.js @@ -0,0 +1,64 @@ +import Buttons from '../../../buttons/Buttons.js'; +import FixWidthButtons from '../../../fixwidthbuttons/FixWidthButtons.js'; + +var CreateListPanel = function () { + var scene = this.scene; + + var background; + var createBackgroundCallback = this.listCreateBackgroundCallback; + if (createBackgroundCallback) { + background = createBackgroundCallback.call(this, scene); + scene.add.existing(background); + } + + var buttons = []; + var createButtonCallback = this.listCreateButtonCallback; + if (createButtonCallback) { + var options = this.options; + for (var i = 0, cnt = options.length; i < cnt; i++) { + var button = createButtonCallback.call(this, scene, options[i], i, options); + scene.add.existing(button); + buttons.push(button); + } + } + + var width = this.listWidth; + if (width === undefined) { + if (this.listAlignMode === 'text') { + width = this.getElement('text').width; + } else { + width = this.width; + } + } + var height = this.listHeight; + + var listPanel; + if (!this.listWrapEnable) { + listPanel = new Buttons(scene, { + width: width, height: height, + + orientation: 'y', + background: background, + buttons: buttons, + + space: this.listSpace, + draggable: this.listDraggable, + }); + } else { + listPanel = new FixWidthButtons(scene, { + width: width, height: height, + + background: background, + buttons: buttons, + + space: this.listSpace, + draggable: this.listDraggable, + }); + } + + scene.add.existing(listPanel); + + return listPanel; +} + +export default CreateListPanel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/OpenListPanel.js b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/OpenListPanel.js new file mode 100644 index 000000000..30455ed17 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/OpenListPanel.js @@ -0,0 +1,83 @@ +import CreateListPanel from './CreateListPanel.js'; +import DropDown from '../../../dropdown/DropDown.js'; + +var OpenListPanel = function () { + if (this.listPanel) { + return this; + } + + var listPanel = CreateListPanel.call(this); + + // Button over/out + listPanel + .on('button.over', function (button, index, pointer, event) { + if (this.listOnButtonOver) { + this.listOnButtonOver.call(this, button, index, pointer, event); + } + + this.emit('button.over', this, listPanel, button, index, pointer, event); + }, this) + .on('button.out', function (button, index, pointer, event) { + if (this.listOnButtonOut) { + this.listOnButtonOut.call(this, button, index, pointer, event); + } + + this.emit('button.out', this, listPanel, button, index, pointer, event); + }, this); + + + var alignTargetX; + if (!this.listAlignMode || (this.listAlignMode === 'label')) { + alignTargetX = this; + } else { + alignTargetX = this.getElement(this.listAlignMode) + } + + var dropDownBehavior = new DropDown(listPanel, { + // Transition + duration: { + in: this.listEaseInDuration, + out: this.listEaseOutDuration + }, + transitIn: this.listTransitInCallback, + transitOut: this.listTransitOutCallback, + + // Position + expandDirection: this.listExpandDirection, + + alignTargetX: alignTargetX, + alignTargetY: this, + alignSide: this.listAlignSide, + + bounds: this.listBounds, + + // Close condition + anyTouchClose: true, + }) + .on('open', function () { + // After popping up + // Can click + listPanel.on('button.click', function (button, index, pointer, event) { + if (this.listOnButtonClick) { + this.listOnButtonClick.call(this, button, index, pointer, event); + } + this.emit('button.click', this, listPanel, button, index, pointer, event); + }, this); + + this.emit('list.open', this, listPanel); + }, this) + + .on('close', function () { + this.listPanel = undefined; + this.dropDownBehavior = undefined; + }, this) + + this.listPanel = listPanel; + this.dropDownBehavior = dropDownBehavior; + + this.pin(listPanel); + + return this; +} + +export default OpenListPanel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/ToggleListPanel.js b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/ToggleListPanel.js new file mode 100644 index 000000000..7394797ec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/ToggleListPanel.js @@ -0,0 +1,10 @@ +var ToggleListPanel = function () { + if (!this.listPanel) { + this.openListPanel(); + } else { + this.closeListPanel(); + } + return this; +} + +export default ToggleListPanel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dynamictext/DynamicText.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/dynamictext/DynamicText.d.ts new file mode 100644 index 000000000..29569d8c1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dynamictext/DynamicText.d.ts @@ -0,0 +1,2 @@ +import DynamicText from '../../../plugins/dynamictext'; +export default DynamicText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dynamictext/DynamicText.js b/ui/src/phaser3-rex-plugins/templates/ui/dynamictext/DynamicText.js new file mode 100644 index 000000000..c244780d1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dynamictext/DynamicText.js @@ -0,0 +1,2 @@ +import DynamicText from '../../../plugins/dynamictext.js'; +export default DynamicText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dynamictext/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/dynamictext/Factory.d.ts new file mode 100644 index 000000000..1187d805f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dynamictext/Factory.d.ts @@ -0,0 +1,5 @@ +import DynamicText from "./DynamicText"; + +export default function ( + config?: DynamicText.IConfig +): DynamicText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/dynamictext/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/dynamictext/Factory.js new file mode 100644 index 000000000..cc3a85051 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/dynamictext/Factory.js @@ -0,0 +1,13 @@ +import DynamicText from './DynamicText.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('dynamicText', function (x, y, width, height, config) { + var gameObject = new DynamicText(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.DynamicText', DynamicText); + +export default DynamicText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/easemove/EaseMove.js b/ui/src/phaser3-rex-plugins/templates/ui/easemove/EaseMove.js new file mode 100644 index 000000000..8782c417d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/easemove/EaseMove.js @@ -0,0 +1,2 @@ +import { EaseMove, EaseMoveTo, EaseMoveFrom } from '../../../plugins/easemove.js'; +export { EaseMove, EaseMoveTo, EaseMoveFrom }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/easemove/EaseMove.ts b/ui/src/phaser3-rex-plugins/templates/ui/easemove/EaseMove.ts new file mode 100644 index 000000000..80fe9fa41 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/easemove/EaseMove.ts @@ -0,0 +1,2 @@ +import { EaseMove, EaseMoveTo, EaseMoveFrom } from '../../../plugins/easemove'; +export { EaseMove, EaseMoveTo, EaseMoveFrom }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fade/Fade.js b/ui/src/phaser3-rex-plugins/templates/ui/fade/Fade.js new file mode 100644 index 000000000..f1d17dc92 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fade/Fade.js @@ -0,0 +1,5 @@ +import Fade from '../../../plugins/fade.js'; +import FadeIn from '../../../plugins/fade-in.js'; +import FadeOutDestroy from '../../../plugins/fade-out-destroy.js'; + +export { Fade, FadeIn, FadeOutDestroy }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fade/Fade.ts b/ui/src/phaser3-rex-plugins/templates/ui/fade/Fade.ts new file mode 100644 index 000000000..857a8cdc4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fade/Fade.ts @@ -0,0 +1,5 @@ +import Fade from '../../../plugins/fade.js'; +import FadeIn from '../../../plugins/fade-in'; +import FadeOutDestroy from '../../../plugins/fade-out-destroy'; + +export { Fade, FadeIn, FadeOutDestroy }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/filechooser/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/filechooser/Factory.d.ts new file mode 100644 index 000000000..f1a7c08fd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/filechooser/Factory.d.ts @@ -0,0 +1,5 @@ +import { FileChooser } from './FileChooser.js'; + +export default function ( + config?: FileChooser.IConfig +): FileChooser; diff --git a/ui/src/phaser3-rex-plugins/templates/ui/filechooser/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/filechooser/Factory.js new file mode 100644 index 000000000..30abe8924 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/filechooser/Factory.js @@ -0,0 +1,13 @@ +import { FileChooser } from './FileChooser.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('fileChooser', function (config) { + var gameObject = new FileChooser(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.FileChooser', FileChooser); + +export default FileChooser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/filechooser/FileChooser.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/filechooser/FileChooser.d.ts new file mode 100644 index 000000000..225ddcaa6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/filechooser/FileChooser.d.ts @@ -0,0 +1,2 @@ +import { OpenFileChooser, FileChooser } from '../../../plugins/filechooser'; +export { OpenFileChooser, FileChooser }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/filechooser/FileChooser.js b/ui/src/phaser3-rex-plugins/templates/ui/filechooser/FileChooser.js new file mode 100644 index 000000000..1cfe923e7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/filechooser/FileChooser.js @@ -0,0 +1,2 @@ +import { OpenFileChooser, FileChooser } from '../../../plugins/filechooser.js'; +export { OpenFileChooser, FileChooser }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/filedropzone/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/filedropzone/Factory.d.ts new file mode 100644 index 000000000..ecb4da5b9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/filedropzone/Factory.d.ts @@ -0,0 +1,5 @@ +import FileDropZone from './FileDropZone.js'; + +export default function ( + config?: FileDropZone.IConfig +): FileDropZone; diff --git a/ui/src/phaser3-rex-plugins/templates/ui/filedropzone/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/filedropzone/Factory.js new file mode 100644 index 000000000..83919aa76 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/filedropzone/Factory.js @@ -0,0 +1,13 @@ +import FileDropZone from './FileDropZone.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('fileDropZone', function (config) { + var gameObject = new FileDropZone(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.FileDropZone', FileDropZone); + +export default FileDropZone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/filedropzone/FileDropZone.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/filedropzone/FileDropZone.d.ts new file mode 100644 index 000000000..ff69253df --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/filedropzone/FileDropZone.d.ts @@ -0,0 +1,2 @@ +import FileDropZone from '../../../plugins/filedropzone'; +export default FileDropZone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/filedropzone/FileDropZone.js b/ui/src/phaser3-rex-plugins/templates/ui/filedropzone/FileDropZone.js new file mode 100644 index 000000000..daf209af3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/filedropzone/FileDropZone.js @@ -0,0 +1,2 @@ +import FileDropZone from '../../../plugins/filedropzone.js'; +export default FileDropZone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/Factory.d.ts new file mode 100644 index 000000000..b2a071090 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/Factory.d.ts @@ -0,0 +1,5 @@ +import FileSelectorButton from './FileSelectorButton.js'; + +export default function ( + config?: FileSelectorButton.IConfig +): FileSelectorButton; diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/Factory.js new file mode 100644 index 000000000..dee98e108 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/Factory.js @@ -0,0 +1,13 @@ +import FileSelectorButton from './FileSelectorButton.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('fileSelectorButton', function (config) { + var gameObject = new FileSelectorButton(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.FileSelectorButton', FileSelectorButton); + +export default FileSelectorButton; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/FileChooserMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/FileChooserMethods.js new file mode 100644 index 000000000..d030e0776 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/FileChooserMethods.js @@ -0,0 +1,21 @@ +export default { + setAccept(accept) { + this.childrenMap.fileChooser.setAccept(accept); + return this; + }, + + setMultiple(enabled) { + this.childrenMap.fileChooser.setMultiple(enabled); + return this; + }, + + loadFile(file, loaderType, key, cacheType, onComplete) { + this.childrenMap.fileChooser.loadFile(file, loaderType, key, cacheType, onComplete); + return this; + }, + + loadFilePromise(file, loaderType, key, cacheType) { + return this.childrenMap.fileChooser.loadFilePromise(file, loaderType, key, cacheType); + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/FileSelectorButton.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/FileSelectorButton.d.ts new file mode 100644 index 000000000..82bea2324 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/FileSelectorButton.d.ts @@ -0,0 +1,45 @@ +import Label from '../label/Label'; + +export default FileSelectorButton; + +declare namespace FileSelectorButton { + interface IConfig extends Label.IConfig { + accept?: string, + multiple?: boolean, + } +} + +declare class FileSelectorButton extends Label { + constructor( + scene: Phaser.Scene, + config?: FileSelectorButton.IConfig + ); + + readonly files: File[]; + + setAccept(accept: string): this; + + setMultiple(multiple?: boolean): this; + + loadFile( + file: File, + loaderType: string, + key: string, + cacheType?: string + ): this; + + loadFile( + file: File, + loaderType: string, + key: string, + cacheType?: string, + onComplete?: (data: any) => void + ): this; + + loadFilePromise( + file: File, + loaderType: string, + key: string, + cacheType?: string + ): Promise; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/FileSelectorButton.js b/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/FileSelectorButton.js new file mode 100644 index 000000000..ed34d94e0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/FileSelectorButton.js @@ -0,0 +1,45 @@ +import Label from '../label/Label.js'; +import { FileChooser } from '../filechooser/FileChooser.js'; +import FileChooserMethods from './FileChooserMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class FileSelectorButton extends Label { + constructor(scene, config) { + super(scene, config); + this.type = 'rexFileSelectorButton'; + + var fileChooser = new FileChooser(scene); + scene.add.existing(fileChooser); + this.addBackground(fileChooser); + + this.addChildrenMap('fileChooser', fileChooser); + + this.setAccept(GetValue(config, 'accept', '')); + this.setMultiple(GetValue(config, 'multiple', false)); + + fileChooser + .on('change', function (gameObject) { + var files = gameObject.files; + if (files.length === 0) { + return; + } + + files = Array.from(files); + this.emit('select', files, this); + }, this) + + } + + get files() { + return this.childrenMap.fileChooser.files; + } + +} + +Object.assign( + FileSelectorButton.prototype, + FileChooserMethods, +) + +export default FileSelectorButton; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/AddChildMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/AddChildMethods.js new file mode 100644 index 000000000..e06abf1b9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/AddChildMethods.js @@ -0,0 +1,46 @@ +import FixWidthSizer from '../fixwidthsizer/FixWidthSizer.js'; +import IsArray from '../../../plugins/utils/object/IsArray.js'; + +const SizerAdd = FixWidthSizer.prototype.add; + +var Add = function (gameObject) { + SizerAdd.call(this, gameObject); + this.buttonGroup.add(gameObject); + return this; +}; + +export default { + addButton(gameObject) { + if (IsArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + Add.call(this, gameObjects[i]); + } + } else { + Add.call(this, gameObject); + } + return this; + }, + + addButtons(gameObjects) { + if (IsArray(gameObjects[0])) { + // 2d array + var lines = gameObjects, line; + for (var lineIdx = 0, lastLineIdx = (lines.length - 1); lineIdx <= lastLineIdx; lineIdx++) { + line = lines[lineIdx]; + for (var i = 0, cnt = line.length; i < cnt; i++) { + Add.call(this, line[i]); + } + if (lineIdx > lastLineIdx) { + SizerAdd.addNewLine(this); + } + } + } else { + // 1d array + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + Add.call(this, gameObjects[i]); + } + } + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/Factory.d.ts new file mode 100644 index 000000000..41489232a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/Factory.d.ts @@ -0,0 +1,5 @@ +import FixWidthButtons from './FixWidthButtons'; + +export default function ( + config?: FixWidthButtons.IConfig +): FixWidthButtons; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/Factory.js new file mode 100644 index 000000000..885f1f6a3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/Factory.js @@ -0,0 +1,13 @@ +import FixWidthButtons from './FixWidthButtons.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('fixWidthButtons', function (config) { + var gameObject = new FixWidthButtons(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.FixWidthButtons', FixWidthButtons); + +export default FixWidthButtons; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/FixWidthButtons.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/FixWidthButtons.d.ts new file mode 100644 index 000000000..db8767c8a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/FixWidthButtons.d.ts @@ -0,0 +1,89 @@ +// import * as Phaser from 'phaser'; +import FixWidthSizer from '../fixwidthsizer/FixWidthSizer'; +import { IConfig as IConfigButtons } from '../utils/buttongroup/Buttons'; + + +export default FixWidthButtons; + +declare namespace FixWidthButtons { + + interface IConfig extends FixWidthSizer.IConfig, IConfigButtons { + background?: Phaser.GameObjects.GameObject, + + buttons?: Phaser.GameObjects.GameObject[], + } + +} + +declare class FixWidthButtons extends FixWidthSizer { + constructor( + scene: Phaser.Scene, + config?: FixWidthButtons.IConfig + ); + + emitButtonClick( + index: number | Phaser.GameObjects.GameObject + ): this; + + setButtonEnable( + index?: number | Phaser.GameObjects.GameObject | boolean, + enable?: boolean + ): this; + + toggleButtonEnable( + index?: number | Phaser.GameObjects.GameObject + ): this; + + getButtonEnable( + index: number | Phaser.GameObjects.GameObject + ): boolean; + + getButton( + index: number + ): Phaser.GameObjects.GameObject | null; + + addButton( + gameObject: Phaser.GameObjects.GameObject + ): this; + + removeButton( + gameObject: Phaser.GameObjects.GameObject, + destroyChild?: boolean + ): this; + + clearButtons( + destroyChild?: boolean + ): this; + + showButton( + index: number | Phaser.GameObjects.GameObject + ): this; + + hideButton( + index: number | Phaser.GameObjects.GameObject + ): this; + + forEachButtton( + callback: (button: Phaser.GameObjects.GameObject, index: number, buttons: Phaser.GameObjects.GameObject[]) => void, + scop?: unknown + ): this; + + readonly buttons: Phaser.GameObjects.GameObject[]; + + value: unknown; + + setSelectedButtonName( + name: string + ): this; + + getSelectedButtonName(): string; + + setButtonState( + name: string, + state?: boolean + ): this; + + getButtonState( + name: string + ): boolean; +} diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/FixWidthButtons.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/FixWidthButtons.js new file mode 100644 index 000000000..9c5c9e2f7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/FixWidthButtons.js @@ -0,0 +1,87 @@ +import FixWidthSizer from '../fixwidthsizer/FixWidthSizer.js'; +import AddChildMethods from './AddChildMethods.js'; +import RemoveChildMethods from './RemoveChildMethods.js'; +import ButtonGroup from '../utils/buttongroup/ButtonGroup.js'; +import ButtonMethods from '../utils/buttongroup/ButtonMethods.js'; +import ButtonStateMethods from '../utils/buttongroup/ButtonStateMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Buttons extends FixWidthSizer { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + var buttonSpace = config.space; + if (typeof (buttonSpace) === 'number') { + config.space = { item: buttonSpace, line: buttonSpace }; + } + + // Create + super(scene, config); + this.type = 'rexFixWidthButtons'; + this.buttonGroup = new ButtonGroup({ + parent: this, + eventEmitter: GetValue(config, 'eventEmitter', this), + groupName: GetValue(config, 'groupName', undefined), + clickConfig: GetValue(config, 'click', undefined) + }) + .setButtonsType(config); + + // Add elements + var background = GetValue(config, 'background', undefined); + var buttons = GetValue(config, 'buttons', undefined); + + // Buttons properties + this.buttonsAlign = GetValue(config, 'align', undefined); + + if (background) { + this.addBackground(background); + } + + if (buttons) { + this.addButtons(buttons); + } + + this.addChildrenMap('background', background); + this.addChildrenMap('buttons', this.buttonGroup.buttons); + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + super.destroy(fromScene); + this.buttonGroup.destroy(); + this.buttonGroup = undefined; + } + + get buttons() { + return this.buttonGroup.buttons; + } + + get groupName() { + return this.buttonGroup.groupName; + } + + set groupName(value) { + this.buttonGroup.groupName = value; + } + + get eventEmitter() { + return this.buttonGroup.eventEmitter; + } +} + +Object.assign( + Buttons.prototype, + AddChildMethods, + RemoveChildMethods, + ButtonMethods, + ButtonStateMethods +); + +export default Buttons; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/RemoveChildMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/RemoveChildMethods.js new file mode 100644 index 000000000..cbc46a21d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/RemoveChildMethods.js @@ -0,0 +1,50 @@ +import FixWidthSizer from '../fixwidthsizer/FixWidthSizer.js'; +import IsArray from '../../../plugins/utils/object/IsArray.js'; + +const SizerRmove = FixWidthSizer.prototype.remove; +const SizerClear = FixWidthSizer.prototype.clear; + +var Remove = function (gameObject, destroyChild) { + var gameObject = this.getButton(gameObject); + if (!gameObject) { + return this; + } + + this.buttonGroup.remove(gameObject); + SizerRmove.call(this, gameObject, destroyChild); + return this; +}; + +export default { + remove(gameObject, destroyChild) { + if (IsArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + Remove.call(this, gameObjects[i], destroyChild); + } + } else { + Remove.call(this, gameObject, destroyChild); + } + return this; + }, + + clear(destroyChild) { + var buttons = this.buttonGroup.buttons; + buttons.length = 0; + SizerClear.call(this, destroyChild); + return this; + }, + + removeButton(gameObject, destroyChild) { + this.remove(gameObject, destroyChild); + return this; + }, + + clearButtons(destroyChild) { + var buttons = this.buttonGroup.buttons; + for (var i = buttons.length - 1; i >= 0; i--) { + Remove.call(this, buttons[i], destroyChild); + } + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/AddChildMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/AddChildMethods.js new file mode 100644 index 000000000..af8c8d048 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/AddChildMethods.js @@ -0,0 +1,74 @@ +import AddChild from '../basesizer/utils/AddChild.js'; +import GetBoundsConfig from '../utils/GetBoundsConfig.js'; +import IsArray from '../../../plugins/utils/object/IsArray.js'; +import GetNearestChildIndex from './GetNearestChildIndex.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const ALIGN_CENTER = Phaser.Display.Align.CENTER; + +var Add = function (gameObject, paddingConfig, childKey, index) { + if (gameObject === '\n') { + this.addNewLine(); + return this; + } + + AddChild.call(this, gameObject); + + if (IsPlainObject(paddingConfig)) { + var config = paddingConfig; + paddingConfig = GetValue(config, 'padding', 0); + childKey = GetValue(config, 'key', undefined); + index = GetValue(config, 'index', undefined); + } + if (paddingConfig === undefined) { + paddingConfig = 0; + } + + var config = this.getSizerConfig(gameObject); + config.align = ALIGN_CENTER; + config.padding = GetBoundsConfig(paddingConfig); + if ((index === undefined) || (index >= this.sizerChildren.length)) { + this.sizerChildren.push(gameObject); + } else { + this.sizerChildren.splice(index, 0, gameObject); + } + + if (childKey !== undefined) { + this.addChildrenMap(childKey, gameObject) + } + return this; +}; + +export default { + add(gameObject, paddingConfig, childKey) { + if (IsArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + Add.call(this, gameObjects[i], paddingConfig); + } + } else { + Add.call(this, gameObject, paddingConfig, childKey); + } + return this; + }, + + addNewLine() { + this.sizerChildren.push('\n'); + return this; + }, + + insert(index, gameObject, paddingConfig, childKey) { + Add.call(this, gameObject, paddingConfig, childKey, index); + return this; + }, + + insertAtPosition(x, y, gameObject, paddingConfig, childKey) { + var index = GetNearestChildIndex.call(this, x, y); + if (index === -1) { + index = undefined; + } + this.insert(index, gameObject, paddingConfig, childKey); + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/Factory.d.ts new file mode 100644 index 000000000..3f248fb62 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/Factory.d.ts @@ -0,0 +1,17 @@ +import FixWidthSizer from './FixWidthSizer'; + + +export default function ( + config?: FixWidthSizer.IConfig +): FixWidthSizer; + +export default function ( + x: number, y: number, + config?: FixWidthSizer.IConfig +): FixWidthSizer; + +export default function ( + x: number, y: number, + width: number, height: number, + config?: FixWidthSizer.IConfig +): FixWidthSizer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/Factory.js new file mode 100644 index 000000000..d32aee99a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/Factory.js @@ -0,0 +1,13 @@ +import FixWidthSizer from './FixWidthSizer.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('fixWidthSizer', function (x, y, minWidth, minHeight, config) { + var gameObject = new FixWidthSizer(this.scene, x, y, minWidth, minHeight, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.FixWidthSizer', FixWidthSizer); + +export default FixWidthSizer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/FixWidthSizer.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/FixWidthSizer.d.ts new file mode 100644 index 000000000..5bdc1da80 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/FixWidthSizer.d.ts @@ -0,0 +1,124 @@ +// import * as Phaser from 'phaser'; +import BaseSizer from '../basesizer/BaseSizer.js'; + +export default FixWidthSizer; + +declare namespace FixWidthSizer { + type AlignTypes = 0 | 1 | 2 | 3 | 4 | 5 | + 'left' | 'right' | 'center' | 'justify' | 'justify-left' | 'justify-right' | 'justify-center'; + + type PaddingTypes = number | + { + left?: number, + right?: number, + top?: number, + bottom?: number + }; + + interface IConfig extends BaseSizer.IConfig { + x?: number, + y?: number, + width?: number, + height?: number, + + space?: { + left?: number, right?: number, top?: number, bottom?: number, + + item?: number, line?: number, + + indentLeftOdd?: number, indentLeftEven?: number, + indentTopOdd?: number, indentTopEven?: number, + }, + + rtl?: boolean, + + align?: AlignTypes; + } +} + +declare class FixWidthSizer extends BaseSizer { + sizerChildren: Phaser.GameObjects.GameObject[]; + + constructor( + scene: Phaser.Scene, + config?: FixWidthSizer.IConfig + ); + + constructor( + scene: Phaser.Scene, + x: number, y: number, + config?: FixWidthSizer.IConfig + ); + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + config?: FixWidthSizer.IConfig + ); + + add( + gameObject: Phaser.GameObjects.GameObject, + config?: { + padding?: FixWidthSizer.PaddingTypes, + key?: string, + index?: number, + } + ): this; + + add( + gameObject: Phaser.GameObjects.GameObject, + padding?: FixWidthSizer.PaddingTypes, + key?: string, + index?: number + ): this; + + insert( + index: number, + gameObject: Phaser.GameObjects.GameObject, + config?: { + padding?: FixWidthSizer.PaddingTypes, + key?: string, + } + ): this; + + insert( + index: number, + gameObject: Phaser.GameObjects.GameObject, + paddingConfig?: FixWidthSizer.PaddingTypes, + key?: string + ): this; + + insertAtPosition( + x: number, + y: number, + gameObject: Phaser.GameObjects.GameObject, + config?: { + padding?: FixWidthSizer.PaddingTypes, + key?: string, + } + ): this; + + insertAtPosition( + x: number, + y: number, + gameObject: Phaser.GameObjects.GameObject, + paddingConfig?: FixWidthSizer.PaddingTypes, + key?: string + ): this; + + addNewLine(): this; + + remove( + gameObject: Phaser.GameObjects.GameObject, + destroyChild?: boolean + ): this; + + removeAll( + destroyChild?: boolean + ): this; + + clear( + destroyChild?: boolean + ): this; +} diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/FixWidthSizer.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/FixWidthSizer.js new file mode 100644 index 000000000..112b241a4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/FixWidthSizer.js @@ -0,0 +1,124 @@ +import BaseSizer from '../basesizer/BaseSizer.js'; +import Methods from './Methods.js'; +import GetOrientationMode from '../utils/GetOrientationMode.js'; +import GetMaxChildWidth from './GetMaxChildWidth.js'; +import GetMaxChildHeight from './GetMaxChildHeight.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class FixWidthSizer extends BaseSizer { + constructor(scene, x, y, minWidth, minHeight, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + minWidth = GetValue(config, 'width', undefined); + minHeight = GetValue(config, 'height', undefined); + } else if (IsPlainObject(minWidth)) { + config = minWidth; + minWidth = GetValue(config, 'width', undefined); + minHeight = GetValue(config, 'height', undefined); + } + + super(scene, x, y, minWidth, minHeight, config); + + this.type = 'rexFixWidthSizer'; + this.sizerChildren = []; + this.setOrientation(GetValue(config, 'orientation', 0)); + this.setItemSpacing(GetValue(config, 'space.item', 0)); + this.setLineSpacing(GetValue(config, 'space.line', 0)); + this.setIntentLeft( + GetValue(config, 'space.indentLeftOdd', 0), + GetValue(config, 'space.indentLeftEven', 0) + ); + this.setIntentTop( + GetValue(config, 'space.indentTopOdd', 0), + GetValue(config, 'space.indentTopEven', 0) + ); + this.setAlign(GetValue(config, 'align', 0)); + this.setJustifyPercentage(GetValue(config, 'justifyPercentage', 0.25)); + this.setRTL(GetValue(config, 'rtl', false)); + + this.addChildrenMap('items', this.sizerChildren); + } + + setOrientation(orientation) { + this.orientation = GetOrientationMode(orientation); + return this; + } + + setItemSpacing(space) { + this.space.item = space; + return this; + } + + setLineSpacing(space) { + this.space.line = space; + return this; + } + + setIntentLeft(odd, even) { + this.space.indentLeftOdd = odd; + this.space.indentLeftEven = even; + return this; + } + + setIntentTop(odd, even) { + this.space.indentTopOdd = odd; + this.space.indentTopEven = even; + return this; + } + + setAlign(align) { + if (typeof (align) === 'string') { + align = ALIGN[align]; + } + this.align = align; + return this; + } + + setJustifyPercentage(value) { + this.justifyPercentage = value; + return this; + } + + setRTL(enabled) { + if (enabled === undefined) { + enabled = true; + } + this.rtl = enabled; + return this; + } + + get maxChildWidth() { + if (this._maxChildWidth === undefined) { + this._maxChildWidth = GetMaxChildWidth.call(this); + } + return this._maxChildWidth; + } + + get maxChildHeight() { + if (this._maxChildHeight === undefined) { + this._maxChildHeight = GetMaxChildHeight.call(this); + } + return this._maxChildHeight; + } +} + +const ALIGN = { + left: 0, top: 0, + right: 1, bottom: 1, + center: 2, + justify: 3, + 'justify-left': 3, 'justify-top': 3, + 'justify-right': 4, 'justify-bottom': 4, + 'justify-center': 5 +} + +Object.assign( + FixWidthSizer.prototype, + Methods +); + +export default FixWidthSizer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetChildrenHeight.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetChildrenHeight.js new file mode 100644 index 000000000..b5f4cbb68 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetChildrenHeight.js @@ -0,0 +1,10 @@ +var GetChildrenHeight = function () { + if (this.rexSizer.hidden) { + return 0; + } + + // After RunChildrenWrap + return this.widthWrapResult.height + this.space.top + this.space.bottom; +} + +export default GetChildrenHeight; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetChildrenSizers.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetChildrenSizers.js new file mode 100644 index 000000000..5ff1f5b54 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetChildrenSizers.js @@ -0,0 +1,17 @@ +var GetChildrenSizers = function (out) { + if (out === undefined) { + out = []; + } + var children = this.sizerChildren, child; + for (var i = 0, cnt = children.length; i < cnt; i++) { + child = children[i]; + if (child === '\n') { + continue; + } + if (child.isRexSizer) { + out.push(child); + } + } + return out; +} +export default GetChildrenSizers; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetChildrenWidth.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetChildrenWidth.js new file mode 100644 index 000000000..fe142bdbf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetChildrenWidth.js @@ -0,0 +1,10 @@ +var GetChildrenWidth = function () { + if (this.rexSizer.hidden) { + return 0; + } + + // Before RunChildrenWrap + return this.maxChildWidth + this.space.left + this.space.right; +} + +export default GetChildrenWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetMaxChildHeight.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetMaxChildHeight.js new file mode 100644 index 000000000..30bac27c9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetMaxChildHeight.js @@ -0,0 +1,22 @@ +import { GetDisplayHeight } from '../../../plugins/utils/size/GetDisplaySize.js'; + +var GetMaxChildHeight = function (children) { + if (children === undefined) { + children = this.sizerChildren; + } + var result = 0; + var child, childHeight; + for (var i = 0, cnt = children.length; i < cnt; i++) { + child = children[i]; + if (child === '\n') { + continue; + } + + childHeight = (child.isRexSizer) ? + Math.max(child.minHeight, child.childrenHeight) : + (child.hasOwnProperty('minHeight')) ? child.minHeight : GetDisplayHeight(child); + result = Math.max(childHeight, result); + } + return result; +} +export default GetMaxChildHeight; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetMaxChildWidth.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetMaxChildWidth.js new file mode 100644 index 000000000..eab557e60 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetMaxChildWidth.js @@ -0,0 +1,18 @@ +var GetMaxChildWidth = function (children) { + if (children === undefined) { + children = this.sizerChildren; + } + var result = 0; + var child, childWidth; + for (var i = 0, cnt = children.length; i < cnt; i++) { + child = children[i]; + if (child === '\n') { + continue; + } + + childWidth = this.getChildWidth(child); + result = Math.max(childWidth, result); + } + return result; +} +export default GetMaxChildWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetNearestChildIndex.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetNearestChildIndex.js new file mode 100644 index 000000000..e76113c0c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetNearestChildIndex.js @@ -0,0 +1,41 @@ +const DistanceBetween = Phaser.Math.Distance.Between; + +var GetNearestChildIndex = function (x, y) { + var children = this.sizerChildren; + if (children.length === 0) { + return -1; + } + + var nearestIndex = -1, + minDistance = Infinity; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + // position is not at this line + if (Math.abs(child.centerY - y) > (child.height / 2)) { + continue; + } + + // Check left bound + var distance = DistanceBetween(child.left, child.centerY, x, y); + if (minDistance > distance) { + minDistance = distance; + nearestIndex = i; + } + + // Is last child of this line + var nextChild = children[i + 1]; + if (nextChild && (nextChild.y === child.y)) { + continue; + } + + var distance = DistanceBetween(child.right, child.centerY, x, y); + if (minDistance > distance) { + minDistance = distance; + nearestIndex = i + 1; + } + } + + return nearestIndex; +} + +export default GetNearestChildIndex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/LayoutChildren.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/LayoutChildren.js new file mode 100644 index 000000000..e26bc91c8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/LayoutChildren.js @@ -0,0 +1,102 @@ +import PreLayoutChild from '../basesizer/utils/PreLayoutChild.js'; +import LayoutChild from '../basesizer/utils/LayoutChild.js'; +import { GetDisplayWidth, GetDisplayHeight } from '../../../plugins/utils/size/GetDisplaySize.js'; + + +var LayoutChildren = function () { + var innerLineWidth = this.innerWidth; + var justifyPercentage = this.justifyPercentage; + var itemSpace = this.space.item, + lineSpace = this.space.line, + indentLeftOdd = this.space.indentLeftOdd, + indentLeftEven = this.space.indentLeftEven, + indentTopOdd = this.space.indentTopOdd, + indentTopEven = this.space.indentTopEven; + + var child, childConfig, padding, justifySpace = 0, indentLeft, indentTop; + var startX = this.innerLeft, + startY = this.innerTop; + var x, y, width, height; // Align zone + var lines = this.widthWrapResult.lines; + var line, lineChlidren, remainderLineWidth; + + var itemX, + itemY = startY; + for (var i = 0, icnt = lines.length; i < icnt; i++) { + // Layout this line + line = lines[i]; + lineChlidren = line.children; + if (this.rtl) { + lineChlidren.reverse(); + } + + indentLeft = (i % 2) ? indentLeftEven : indentLeftOdd; + itemX = startX + indentLeft; + + remainderLineWidth = (innerLineWidth - line.width); + switch (this.align) { + case 0: // left + break; + case 1: // right + itemX += remainderLineWidth; + break; + case 2: // center + itemX += remainderLineWidth / 2; + break; + case 3: // justify-left + justifySpace = GetJustifySpace(innerLineWidth, remainderLineWidth, justifyPercentage, lineChlidren.length); + break; + case 4: // justify-right + justifySpace = GetJustifySpace(innerLineWidth, remainderLineWidth, justifyPercentage, lineChlidren.length); + if (justifySpace === 0) { + // Align right + itemX += remainderLineWidth; + } + break; + case 5: // justify-center + justifySpace = GetJustifySpace(innerLineWidth, remainderLineWidth, justifyPercentage, lineChlidren.length); + if (justifySpace === 0) { + // Align center + itemX += remainderLineWidth / 2; + } + break; + } + + var isFirstChild = true; + for (var j = 0, jcnt = lineChlidren.length; j < jcnt; j++) { + child = lineChlidren[j]; + if (child.rexSizer.hidden) { + continue; + } + + childConfig = child.rexSizer; + padding = childConfig.padding; + + PreLayoutChild.call(this, child); + + x = (itemX + padding.left); + + if (isFirstChild) { + isFirstChild = false; + } else { + x += itemSpace; + } + + indentTop = (j % 2) ? indentTopEven : indentTopOdd; + y = (itemY + indentTop + padding.top); + width = GetDisplayWidth(child); + height = GetDisplayHeight(child); + itemX = x + width + padding.right + justifySpace; + + LayoutChild.call(this, child, x, y, width, height, childConfig.align); + } + + itemY += line.height + lineSpace; + } +} + +var GetJustifySpace = function (total, remainder, justifyPercentage, childCount) { + return ((remainder / total) <= justifyPercentage) ? (remainder / (childCount - 1)) : 0; +} + +export default LayoutChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/Methods.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/Methods.js new file mode 100644 index 000000000..2780c443c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/Methods.js @@ -0,0 +1,25 @@ +import GetChildrenWidth from './GetChildrenWidth.js'; +import GetChildrenHeight from './GetChildrenHeight.js'; +import GetChildrenSizers from './GetChildrenSizers.js'; +import PreLayout from './PreLayout.js'; +import LayoutChildren from './LayoutChildren.js'; +import RunWidthWrap from './RunWidthWrap.js'; +import AddChildMethods from './AddChildMethods.js'; +import RemoveChildMethods from './RemoveChildMethods.js'; + +var methods = { + getChildrenWidth: GetChildrenWidth, + getChildrenHeight: GetChildrenHeight, + getChildrenSizers: GetChildrenSizers, + preLayout: PreLayout, + layoutChildren: LayoutChildren, + runWidthWrap: RunWidthWrap, +}; + +Object.assign( + methods, + AddChildMethods, + RemoveChildMethods +); + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/PreLayout.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/PreLayout.js new file mode 100644 index 000000000..1d6b0b3d0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/PreLayout.js @@ -0,0 +1,9 @@ +import PreLayoutBase from '../basesizer/PreLayout.js'; + +var PreLayout = function () { + this._maxChildWidth = undefined; + this._maxChildHeight = undefined; + PreLayoutBase.call(this); + return this; +} +export default PreLayout; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/RemoveChildMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/RemoveChildMethods.js new file mode 100644 index 000000000..7e71690af --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/RemoveChildMethods.js @@ -0,0 +1,28 @@ +import RemoveChild from '../basesizer/utils/RemoveChild.js'; +import ClearChildren from '../basesizer/utils/ClearChildren.js'; + +const RemoveItem = Phaser.Utils.Array.Remove; + +export default { + remove(gameObject, destroyChild) { + if (this.getParentSizer(gameObject) !== this) { + return this; + } + RemoveItem(this.sizerChildren, gameObject); + RemoveChild.call(this, gameObject, destroyChild); + return this; + }, + + removeAll(destroyChild) { + for (var i = this.sizerChildren.length - 1; i >= 0; i--) { + this.remove(this.sizerChildren[i], destroyChild); + } + return this; + }, + + clear(destroyChild) { + this.sizerChildren.length = 0; + ClearChildren.call(this, destroyChild); + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/RunChildrenWrap.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/RunChildrenWrap.js new file mode 100644 index 000000000..48eab7877 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/RunChildrenWrap.js @@ -0,0 +1,93 @@ +import { GetDisplayWidth, GetDisplayHeight } from '../../../plugins/utils/size/GetDisplaySize.js'; + +var RunChildrenWrap = function (lineWidth, out) { + if (out === undefined) { + out = { + lines: [], + width: 0, + height: 0 + } + } else { + out.lines.length = 0; + out.width = 0; + out.height = 0; + } + + var children = this.sizerChildren; + var itemSpace = this.space.item, + lineSpace = this.space.line, + indentLeftOdd = this.space.indentLeftOdd, + indentLeftEven = this.space.indentLeftEven, + indentTopOdd = this.space.indentTopOdd, + indentTopEven = this.space.indentTopEven; + var child, childWidth, childHeight, remainder = 0, indentLeft; + var lines = out.lines, + lastLine = undefined, + newLine; + for (var i = 0, cnt = children.length; i < cnt; i++) { + child = children[i]; + if (child === '\n') { + child = undefined; + childWidth = 0; + newLine = true; + } else { + if (child.rexSizer.hidden) { + continue; + } + + if (child.isRexSizer) { + child.layout(); // Use original size + } + + childWidth = GetChildWidth(child); + newLine = (remainder < childWidth) || (lastLine === undefined); + } + // New line + if (newLine) { + if (lastLine) { + lastLine.width = lineWidth - (remainder + itemSpace); + out.width = Math.max(out.width, lastLine.width); + out.height += lastLine.height + lineSpace; + } + + lastLine = { + children: [], + // width: 0, + height: 0 + }; + lines.push(lastLine); + + var indentLeft = (lines.length % 2) ? indentLeftOdd : indentLeftEven; + remainder = lineWidth - indentLeft; + } + + remainder -= (childWidth + itemSpace); + if (child) { + lastLine.children.push(child); + childHeight = GeChildHeight(child); + lastLine.height = Math.max(lastLine.height, childHeight); + } + } + + if (lastLine) { + lastLine.width = lineWidth - (remainder + itemSpace); + out.width = Math.max(out.width, lastLine.width); + out.height += lastLine.height; + } + + out.height += Math.max(indentTopOdd, indentTopEven); + + return out; +} + +var GetChildWidth = function (child) { + var padding = child.rexSizer.padding; + return GetDisplayWidth(child) + padding.left + padding.right; +} + +var GeChildHeight = function (child) { + var padding = child.rexSizer.padding; + return GetDisplayHeight(child) + padding.top + padding.bottom; +} + +export default RunChildrenWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/RunWidthWrap.js b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/RunWidthWrap.js new file mode 100644 index 000000000..eaaa36848 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/RunWidthWrap.js @@ -0,0 +1,10 @@ +import RunChildrenWrapBase from '../basesizer/RunWidthWrap.js'; +import RunChildrenWrap from './RunChildrenWrap.js'; + +var RunWidthWrap = function (width) { + var innerWidth = width - this.space.left - this.space.right; + this.widthWrapResult = RunChildrenWrap.call(this, innerWidth, this.widthWrapResult); + RunChildrenWrapBase.call(this, width); +} + +export default RunWidthWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/flip/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/flip/Factory.d.ts new file mode 100644 index 000000000..9f355849b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/flip/Factory.d.ts @@ -0,0 +1,7 @@ +// import * as Phaser from 'phaser'; +import Flip from "./Flip"; + +export default function ( + gameObject: Phaser.GameObjects.GameObject, + config?: Flip.IConfig +): Flip; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/flip/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/flip/Factory.js new file mode 100644 index 000000000..f5905bf84 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/flip/Factory.js @@ -0,0 +1,11 @@ +import Flip from './Flip.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('flip', function (gameObject, config) { + return new Flip(gameObject, config); +}); + +SetValue(window, 'RexPlugins.UI.Flip', Flip); + +export default Flip; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/flip/Flip.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/flip/Flip.d.ts new file mode 100644 index 000000000..a766f4a69 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/flip/Flip.d.ts @@ -0,0 +1,2 @@ +import Flip from '../../../plugins/flip'; +export default Flip; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/flip/Flip.js b/ui/src/phaser3-rex-plugins/templates/ui/flip/Flip.js new file mode 100644 index 000000000..5ebe8b6f7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/flip/Flip.js @@ -0,0 +1,2 @@ +import Flip from '../../../plugins/flip.js'; +export default Flip; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/folder/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/folder/Factory.d.ts new file mode 100644 index 000000000..01ba897bb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/folder/Factory.d.ts @@ -0,0 +1,5 @@ +import Folder from './Folder'; + +export default function ( + config?: Folder.IConfig +): Folder; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/folder/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/folder/Factory.js new file mode 100644 index 000000000..733c026da --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/folder/Factory.js @@ -0,0 +1,13 @@ +import Folder from './Folder.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('folder', function (config) { + var gameObject = new Folder(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.Folder', Folder); + +export default Folder; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/folder/Folder.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/folder/Folder.d.ts new file mode 100644 index 000000000..202d5f243 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/folder/Folder.d.ts @@ -0,0 +1,65 @@ +// import * as Phaser from 'phaser'; +import Sizer from '../sizer/Sizer'; +import OpenCloseTransition from '../../../plugins/behaviors/openclosetransition/OpenCloseTransition'; + +export default Folder; + +declare namespace Folder { + + interface IConfig extends Sizer.IConfig { + background?: Phaser.GameObjects.GameObject, + + title: Phaser.GameObjects.GameObject, + + child: Phaser.GameObjects.GameObject, + customChildOrigin?: boolean, + + toggleByTarget?: Phaser.GameObjects.GameObject, + toggleClickConfig?: { + mode?: 0 | 1 | 'pointerdown' | 'pointerup' | 'press' | 'release', + clickInterval?: number, + threshold?: number, + }, + + align?: { + title?: Sizer.AlignTypes, + child?: Sizer.AlignTypes, + }, + + expand?: { + title?: boolean, + child?: boolean, + }, + + transition?: { + duration?: number, + expandCallback?: OpenCloseTransition.TransitCallbackType, + collapseCallback?: OpenCloseTransition.TransitCallbackType, + }, + + reLayoutTarget?: Phaser.GameObjects.GameObject, + + onExpandStart?: (folder: this) => void, + onExpandComplete?: (folder: this) => void, + onCollapseStart?: (folder: this) => void, + onCollapseComplete?: (folder: this) => void, + } +} + +declare class Folder extends Sizer { + constructor( + scene: Phaser.Scene, + config?: Folder.IConfig + ); + + setTransitionDuration(duration?: number): this; + transitionDuration: number; + + setExpandCallback(callback?: OpenCloseTransition.TransitCallbackType): this; + setCollapseCallback(callback?: OpenCloseTransition.TransitCallbackType): this; + + expand(duration?: number): this; + collapse(duration?: number): this; + toggle(duration?: number): this; + readonly expanded: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/folder/Folder.js b/ui/src/phaser3-rex-plugins/templates/ui/folder/Folder.js new file mode 100644 index 000000000..a17543c0b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/folder/Folder.js @@ -0,0 +1,122 @@ +import Sizer from '../sizer/Sizer'; +import ChildTransition from './methods/ChildTransition.js'; +import ExpandMethods from './methods/ExpandMethods.js'; +import ClickMethods from '../basesizer/ClickMethods'; +import ConfigurationMethods from './methods/ConfigurationMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Folder extends Sizer { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + if (!config.hasOwnProperty('orientation')) { + config.orientation = 1; + } + + super(scene, config); + this.type = 'rexFolder'; + + this.expanded = undefined; + this.expandDirection = (this.orientation === 1) ? 'y' : 'x'; + + var background = config.background; + var title = config.title; + var child = config.child; + + // background + if (background) { + this.addBackground(background); + } + + // title + var defaultAlign = (this.orientation === 1) ? 'left' : 'top'; + var align = GetValue(config, 'align.title', defaultAlign); + var expand = GetValue(config, 'expand.title', true); + this.add( + title, + { + proportion: 0, align: align, expand: expand, + } + ); + + var toggleByTarget = GetValue(config, 'toggleByTarget', undefined); + var toggleClickConfig = GetValue(config, 'toggleClickConfig'); + + if (toggleByTarget === undefined) { + toggleByTarget = title; + } + if (toggleByTarget) { + ClickMethods.onClick.call( + toggleByTarget, + function () { + this.toggle(); + }, + this, + toggleClickConfig + ); + } + + // child + this.childTransition = new ChildTransition(child); + + var customOrigin = GetValue(config, 'customChildOrigin', false); + if (!customOrigin) { + var origin = (!this.rtl) ? 0 : 1; + child.setOrigin(origin); + } + + var align = GetValue(config, 'align.child', 'left'); + var expand = GetValue(config, 'expand.child', true); + var proportion = (expand) ? 1 : 0; + this.add( + child, + { + proportion: proportion, align: align, expand: expand, + + } + ); + + this.addChildrenMap('title', title); + this.addChildrenMap('child', child); + this.addChildrenMap('background', background); + + var transitionConfig = config.transition; + this.setTransitionDuration(GetValue(transitionConfig, 'duration', 200)); + this.setExpandCallback(GetValue(transitionConfig, 'expandCallback', undefined)); + this.setCollapseCallback(GetValue(transitionConfig, 'collapseCallback', undefined)); + + this.reLayoutTarget = GetValue(config, 'reLayoutTarget', undefined); + + var onExpandStart = config.onExpandStart; + if (onExpandStart) { + this.on('expand.start', onExpandStart); + } + + var onExpandComplete = config.onExpandComplete; + if (onExpandComplete) { + this.on('expand.complete', onExpandComplete); + } + + var onCollapseStart = config.onCollapseStart; + if (onCollapseStart) { + this.on('collapse.start', onCollapseStart); + } + + var onCollapseComplete = config.onCollapseComplete; + if (onCollapseComplete) { + this.on('collapse.complete', onCollapseComplete); + } + + } +} + +Object.assign( + Folder.prototype, + ExpandMethods, + ConfigurationMethods, +) + +export default Folder; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/folder/methods/ChildTransition.js b/ui/src/phaser3-rex-plugins/templates/ui/folder/methods/ChildTransition.js new file mode 100644 index 000000000..33cdd3633 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/folder/methods/ChildTransition.js @@ -0,0 +1,24 @@ +import OpenCloseTransition from '../../../../plugins/behaviors/openclosetransition/OpenCloseTransition.js'; + +class Transition extends OpenCloseTransition { + constructor(gameObject, config) { + if (config === undefined) { + config = {}; + } + config.destroy = false; + super(gameObject, config); + } + + onOpen() { + this.emit('open', this.parent, this); + super.onOpen(); + } + + onClose() { + this.emit('close', this.parent, this); + super.onClose(); + } + +} + +export default Transition; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/folder/methods/ConfigurationMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/folder/methods/ConfigurationMethods.js new file mode 100644 index 000000000..8ec01518e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/folder/methods/ConfigurationMethods.js @@ -0,0 +1,37 @@ +import ScaleMethods from '../../basesizer/ScaleMethods.js'; + +var DefaultExpandCallback = function (gameObject, duration) { + ScaleMethods.popUp.call(gameObject, duration, this.expandDirection); +}; + +var DefaultCollapseCallback = function (gameObject, duration) { + ScaleMethods.scaleDown.call(gameObject, duration, this.expandDirection) +} + +export default { + setTransitionDuration(duration) { + this.transitionDuration = duration; + + this.childTransition + .setTransitInTime(duration) + .setTransitOutTime(duration); + + return this; + }, + + setExpandCallback(callback) { + if (callback === undefined) { + callback = DefaultExpandCallback.bind(this); + } + this.childTransition.setTransitInCallback(callback); + return this; + }, + + setCollapseCallback(callback) { + if (callback === undefined) { + callback = DefaultCollapseCallback.bind(this); + } + this.childTransition.setTransitOutCallback(callback); + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/folder/methods/ExpandMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/folder/methods/ExpandMethods.js new file mode 100644 index 000000000..61018ca59 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/folder/methods/ExpandMethods.js @@ -0,0 +1,75 @@ +export default { + expand(duration) { + if (this.expanded === true) { + return this; + } + + if (duration === undefined) { + duration = this.transitionDuration; + } + + this.expanded = true; + + var title = this.childrenMap.title; + var child = this.childrenMap.child; + + this.show(child); + + var layoutTarget = (this.reLayoutTarget) ? this.reLayoutTarget : this.getTopmostSizer(); + layoutTarget.layout(); + + title.emit('folder.expand', duration, this); + child.emit('folder.expand', duration, this); + this.emit('expand.start', this); + + this.childTransition + .once('open', function () { + this.emit('expand.complete', this); + }, this) + .requestOpen(null, duration); + + return this; + }, + + collapse(duration) { + if (this.expanded === false) { + return this; + } + + if (duration === undefined) { + duration = this.transitionDuration; + } + + this.expanded = false; + + var title = this.childrenMap.title; + var child = this.childrenMap.child; + + title.emit('folder.collapse', duration, this); + child.emit('folder.collapse', duration, this); + this.emit('collapse.start', this); + + this.childTransition + .once('close', function () { + this.setChildScale(child, 1, 1).hide(child); + + var layoutTarget = (this.reLayoutTarget) ? this.reLayoutTarget : this.getTopmostSizer(); + layoutTarget.layout(); + + this.emit('collapse.complete', this); + }, this) + .requestClose(null, duration); + + return this; + }, + + toggle(duration) { + if (this.expanded) { + this.collapse(duration); + } else { + this.expand(duration); + } + + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/Factory.d.ts new file mode 100644 index 000000000..1b79ee713 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/Factory.d.ts @@ -0,0 +1,7 @@ +import FullWindowRectangle from './FullWindowRectangle'; + +export default function ( + fillColor?: number, + fillAlpha?: number + +): FullWindowRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/Factory.js new file mode 100644 index 000000000..f191129b7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/Factory.js @@ -0,0 +1,13 @@ +import FullWindowRectangle from './FullWindowRectangle.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('fullWindowRectangle', function (fillColor, fillAlpha) { + var gameObject = new FullWindowRectangle(this.scene, fillColor, fillAlpha); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.FullWindowRectangle', FullWindowRectangle); + +export default FullWindowRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/FullWindowRectangle.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/FullWindowRectangle.d.ts new file mode 100644 index 000000000..c711cc1d3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/FullWindowRectangle.d.ts @@ -0,0 +1,2 @@ +import FullWindowRectangle from "../../../plugins/fullwindowrectangle"; +export default FullWindowRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/FullWindowRectangle.js b/ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/FullWindowRectangle.js new file mode 100644 index 000000000..cb55f1263 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/FullWindowRectangle.js @@ -0,0 +1,2 @@ +import FullWindowRectangle from '../../../plugins/fullwindowrectangle.js'; +export default FullWindowRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/AddChildMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/AddChildMethods.js new file mode 100644 index 000000000..d8257ed54 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/AddChildMethods.js @@ -0,0 +1,18 @@ +import GridSizer from '../gridsizer/GridSizer.js'; + +const SizerAdd = GridSizer.prototype.add; + +export default { + addButton(gameObject, columnIndex, rowIndex) { + SizerAdd.call(this, gameObject, columnIndex, rowIndex, undefined, 0, this.buttonsExpand); + this.buttonGroup.add(gameObject); + return this; + }, + + addButtons(gameObjects, rowThenColumn) { + for (var i = 0, cnt = gameObjects; i < cnt; i++) { + this.addButton(gameObjects[i], undefined, rowThenColumn); + } + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/Factory.d.ts new file mode 100644 index 000000000..8bf397363 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/Factory.d.ts @@ -0,0 +1,5 @@ +import GridButtons from './GridButtons'; + +export default function ( + config?: GridButtons.IConfig +): GridButtons; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/Factory.js new file mode 100644 index 000000000..092ec6ee8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/Factory.js @@ -0,0 +1,13 @@ +import GridButtons from './GridButtons.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('gridButtons', function (config) { + var gameObject = new GridButtons(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.GridButtons', GridButtons); + +export default GridButtons; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/GridButtons.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/GridButtons.d.ts new file mode 100644 index 000000000..99ee01cdb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/GridButtons.d.ts @@ -0,0 +1,101 @@ +// import * as Phaser from 'phaser'; +import GridSizer from '../gridsizer/GridSizer'; +import { IConfig as IConfigButtons } from '../utils/buttongroup/Buttons'; + + +export default GridButtons; + +declare namespace GridButtons { + type CreateCellContainerCallbackType = ( + scene: Phaser.Scene, + x: number, y: number, + config: { + column?: number, row?: number, + + align?: GridSizer.AlignTypes, + padding?: GridSizer.PaddingTypes, + expand?: boolean, + key?: string + } + ) => Phaser.GameObjects.GameObject; + + interface IConfig extends GridSizer.IConfig, IConfigButtons { + background?: Phaser.GameObjects.GameObject, + + buttons?: Phaser.GameObjects.GameObject[][], + createCellContainerCallback?: CreateCellContainerCallbackType + } +} + +declare class GridButtons extends GridSizer { + constructor( + scene: Phaser.Scene, + config?: GridButtons.IConfig + ); + + emitButtonClick( + index: number | Phaser.GameObjects.GameObject + ): this; + + setButtonEnable( + index?: number | Phaser.GameObjects.GameObject | boolean, + enable?: boolean + ): this; + + toggleButtonEnable( + index?: number | Phaser.GameObjects.GameObject + ): this; + + getButtonEnable( + index: number | Phaser.GameObjects.GameObject + ): boolean; + + getButton( + index: number + ): Phaser.GameObjects.GameObject | null; + + addButton( + gameObject: Phaser.GameObjects.GameObject + ): this; + + removeButton( + gameObject: Phaser.GameObjects.GameObject, + destroyChild?: boolean + ): this; + + clearButtons( + destroyChild?: boolean + ): this; + + showButton( + index: number | Phaser.GameObjects.GameObject + ): this; + + hideButton( + index: number | Phaser.GameObjects.GameObject + ): this; + + forEachButtton( + callback: (button: Phaser.GameObjects.GameObject, index: number, buttons: Phaser.GameObjects.GameObject[]) => void, + scop?: unknown + ): this; + + readonly buttons: Phaser.GameObjects.GameObject[]; + + value: unknown; + + setSelectedButtonName( + name: string + ): this; + + getSelectedButtonName(): string; + + setButtonState( + name: string, + state?: boolean + ): this; + + getButtonState( + name: string + ): boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/GridButtons.js b/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/GridButtons.js new file mode 100644 index 000000000..52d92f80f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/GridButtons.js @@ -0,0 +1,124 @@ +import GridSizer from '../gridsizer/GridSizer.js'; +import AddChildMethods from './AddChildMethods.js'; +import RemoveChildMethods from './RemoveChildMethods.js'; +import ButtonGroup from '../utils/buttongroup/ButtonGroup.js'; +import ButtonMethods from '../utils/buttongroup/ButtonMethods.js'; +import ButtonStateMethods from '../utils/buttongroup/ButtonStateMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class GridButtons extends GridSizer { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + var rowCount = GetValue(config, 'row', 0); + var columnCount = GetValue(config, 'column', (config.col || 0)); + var createCellContainerCallback = GetValue(config, 'createCellContainerCallback'); + var buttons = GetValue(config, 'buttons', undefined); + var buttonsExpand = GetValue(config, 'expand', true); + var buttonProportion = (buttonsExpand) ? 1 : 0; + + if (createCellContainerCallback) { + config.createCellContainerCallback = undefined; + } + if (buttons !== undefined) { + rowCount = Math.max(rowCount, buttons.length); + for (var i = 0, cnt = buttons.length; i < cnt; i++) { + columnCount = Math.max(columnCount, buttons[i].length); + } + } + + config.row = rowCount; + config.column = columnCount; + config.columnProportions = buttonProportion; + config.rowProportions = buttonProportion; + + // Create + super(scene, config); + this.type = 'rexGridButtons'; + this.buttonGroup = new ButtonGroup({ + parent: this, + eventEmitter: GetValue(config, 'eventEmitter', this), + groupName: GetValue(config, 'groupName', undefined), + clickConfig: GetValue(config, 'click', undefined) + }) + .setButtonsType(config); + + // Add elements + var background = GetValue(config, 'background', undefined); + + // Buttons properties + this.buttonsExpand = buttonsExpand; + var space = GetValue(config, 'space', undefined); + if (typeof (space) === 'number') { + space = { itemX: space, itemY: space }; + } + + if (background) { + this.addBackground(background); + } + + if (buttons) { + var rowButtons, button; + for (var r = 0, rcnt = buttons.length; r < rcnt; r++) { // row + rowButtons = buttons[r]; + for (var c = 0, ccnt = rowButtons.length; c < ccnt; c++) { // col + button = rowButtons[c]; + if (button) { + this.addButton(button, c, r); + } + } + } + } else if (createCellContainerCallback) { + for (var y = 0; y < rowCount; y++) { + for (var x = 0; x < columnCount; x++) { + var button = createCellContainerCallback(scene, x, y); + if (button) { + this.addButton(button, x, y); + } + } + } + } + + this.addChildrenMap('background', background); + this.addChildrenMap('buttons', this.buttonGroup.buttons); + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + super.destroy(fromScene); + this.buttonGroup.destroy(); + this.buttonGroup = undefined; + } + + get buttons() { + return this.buttonGroup.buttons; + } + + get groupName() { + return this.buttonGroup.groupName; + } + + set groupName(value) { + this.buttonGroup.groupName = value; + } + + get eventEmitter() { + return this.buttonGroup.eventEmitter; + } +} + +Object.assign( + GridButtons.prototype, + AddChildMethods, + RemoveChildMethods, + ButtonMethods, + ButtonStateMethods +); + +export default GridButtons; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/RemoveChildMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/RemoveChildMethods.js new file mode 100644 index 000000000..3a7022c3b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/RemoveChildMethods.js @@ -0,0 +1,50 @@ +import GridSizer from '../gridsizer/GridSizer.js'; +import IsArray from '../../../plugins/utils/object/IsArray.js'; + +const SizerRmove = GridSizer.prototype.remove; +const SizerClear = GridSizer.prototype.clear; + +var Remove = function (gameObject, destroyChild) { + var gameObject = this.getButton(gameObject); + if (!gameObject) { + return this; + } + + this.buttonGroup.remove(gameObject); + SizerRmove.call(this, gameObject, destroyChild); + return this; +}; + +export default { + remove(gameObject, destroyChild) { + if (IsArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + Remove.call(this, gameObjects[i], destroyChild); + } + } else { + Remove.call(this, gameObject, destroyChild); + } + return this; + }, + + clear(destroyChild) { + var buttons = this.buttonGroup.buttons; + buttons.length = 0; + SizerClear.call(this, destroyChild); + return this; + }, + + removeButton(gameObject, destroyChild) { + this.remove(gameObject, destroyChild); + return this; + }, + + clearButtons(destroyChild) { + var buttons = this.buttonGroup.buttons; + for (var i = buttons.length - 1; i >= 0; i--) { + Remove.call(this, buttons[i], destroyChild); + } + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/AddChildMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/AddChildMethods.js new file mode 100644 index 000000000..deb49535f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/AddChildMethods.js @@ -0,0 +1,112 @@ +import AddChild from '../basesizer/utils/AddChild.js'; +import GetBoundsConfig from '../utils/GetBoundsConfig.js'; +import ALIGNMODE from '../utils/AlignConst.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const ALIGN_CENTER = Phaser.Display.Align.CENTER; + + +var GetEmptyCellIndex = function (columnIndex, rowIndex, cells, columnCount, rowCount) { + if ((typeof (columnIndex) === 'number') || (typeof (rowIndex) === 'number')) { + if (columnIndex === undefined) { + var idx; + for (var i = 0; i < columnCount; i++) { + idx = (rowIndex * columnCount) + i; + if (!cells[idx]) { + return idx; + } + } + } else if (rowIndex === undefined) { + var idx; + for (var i = 0; i < rowCount; i++) { + idx = (i * columnCount) + columnIndex; + if (!cells[idx]) { + return idx; + } + } + } else { + var idx = (rowIndex * columnCount) + columnIndex; + if (!cells[idx]) { + return idx; + } + } + + } else if (rowIndex === true) { + var idx; + for (var i = 0; i < columnCount; i++) { + for (var j = 0; j < rowCount; j++) { + idx = (j * columnCount) + i; + if (!cells[idx]) { + return idx; + } + } + } + } else { + for (var i = 0, cnt = cells.length; i < cnt; i++) { + if (!cells[i]) { + return i; + } + } + } + return null; +} + +var Add = function (gameObject, columnIndex, rowIndex, align, paddingConfig, expand, childKey) { + AddChild.call(this, gameObject); + if (IsPlainObject(columnIndex)) { + var config = columnIndex; + columnIndex = GetValue(config, 'column', undefined); + rowIndex = GetValue(config, 'row', undefined); + align = GetValue(config, 'align', ALIGN_CENTER); + paddingConfig = GetValue(config, 'padding', 0); + expand = GetValue(config, 'expand', false); + childKey = GetValue(config, 'key', undefined); + } + + // Get insert index + var itemIndex = GetEmptyCellIndex(columnIndex, rowIndex, this.sizerChildren, this.columnCount, this.rowCount); + if (itemIndex === null) { + // Specific index mode + if ((typeof (columnIndex) === 'number') && (typeof (rowIndex) === 'number')) { + return this; + } + + if ((rowIndex === true) || (typeof (rowIndex) === 'number')) { + this.addEmptyColumn(); + } else { + this.addEmptyRow(); + } + + // Get insert index again + itemIndex = GetEmptyCellIndex(columnIndex, rowIndex, this.sizerChildren, this.columnCount, this.rowCount); + } + + if (typeof (align) === 'string') { + align = ALIGNMODE[align]; + } + if (align === undefined) { + align = ALIGN_CENTER; + } + if (paddingConfig === undefined) { + paddingConfig = 0; + } + if (expand === undefined) { + expand = true; + } + + var config = this.getSizerConfig(gameObject); + config.align = align; + config.padding = GetBoundsConfig(paddingConfig); + config.expand = expand; + this.sizerChildren[itemIndex] = gameObject; + + if (childKey !== undefined) { + this.addChildrenMap(childKey, gameObject) + } + return this; +} + +export default { + add: Add +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/Factory.d.ts new file mode 100644 index 000000000..5b146e27a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/Factory.d.ts @@ -0,0 +1,24 @@ +import GridSizer from './GridSizer'; + + +export default function ( + config?: GridSizer.IConfig +): GridSizer; + +export default function ( + x: number, y: number, + config?: GridSizer.IConfig +): GridSizer; + +export default function ( + x: number, y: number, + width: number, height: number, + config?: GridSizer.IConfig +): GridSizer; + +export default function ( + x: number, y: number, + width: number, height: number, + column: number, row: number, + config?: GridSizer.IConfig +): GridSizer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/Factory.js new file mode 100644 index 000000000..27a65e2da --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/Factory.js @@ -0,0 +1,13 @@ +import GridSizer from './GridSizer.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('gridSizer', function (x, y, minWidth, minHeight, columnCount, rowCount, columnProportions, rowProportion, config) { + var gameObject = new GridSizer(this.scene, x, y, minWidth, minHeight, columnCount, rowCount, columnProportions, rowProportion, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.GridSizer', GridSizer); + +export default GridSizer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetChildrenHeight.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetChildrenHeight.js new file mode 100644 index 000000000..e4280856f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetChildrenHeight.js @@ -0,0 +1,49 @@ +import { GetDisplayHeight } from '../../../plugins/utils/size/GetDisplaySize.js'; +import Sum from '../../../plugins/utils/math/Sum.js'; + +var GetChildrenHeight = function (minimumMode) { + if (this.rexSizer.hidden) { + return 0; + } + + if (minimumMode === undefined) { + minimumMode = true; + } + + var result = 0, + rowHeight; + var children = this.sizerChildren; + var child, padding, childHeight, proportion; + + for (var i = 0; i < this.rowCount; i++) { + proportion = this.rowProportions[i]; + rowHeight = 0; + if ((proportion === 0) || minimumMode) { + for (var j = 0; j < this.columnCount; j++) { + child = children[(i * this.columnCount) + j]; + if (!child) { + continue; + } + if (child.rexSizer.hidden) { + continue; + } + + childHeight = (child.isRexSizer) ? + Math.max(child.minHeight, child.childrenHeight) : + (child.hasOwnProperty('minHeight')) ? child.minHeight : GetDisplayHeight(child); + padding = child.rexSizer.padding; + childHeight += (padding.top + padding.bottom); + rowHeight = Math.max(rowHeight, childHeight); + } + result += rowHeight; + } + // else,(proportion > 0) : rowHeight is 0 + this.rowHeight[i] = rowHeight; + } + + var space = this.space; + var indentTop = Math.max(space.indentTopOdd, space.indentTopEven); + return result + Sum(space.top, indentTop, ...space.row, space.bottom); +} + +export default GetChildrenHeight; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetChildrenSizers.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetChildrenSizers.js new file mode 100644 index 000000000..179c8baf0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetChildrenSizers.js @@ -0,0 +1,15 @@ +var GetChildrenSizers = function (out) { + if (out === undefined) { + out = []; + } + var children = this.sizerChildren, + child; + for (var i = 0, cnt = children.length; i < cnt; i++) { + child = children[i]; + if (child && child.isRexSizer) { + out.push(child); + } + } + return out; +} +export default GetChildrenSizers; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetChildrenWidth.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetChildrenWidth.js new file mode 100644 index 000000000..77af5e398 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetChildrenWidth.js @@ -0,0 +1,45 @@ +import Sum from '../../../plugins/utils/math/Sum.js'; + +var GetChildrenWidth = function (minimumMode) { + if (this.rexSizer.hidden) { + return 0; + } + + if (minimumMode === undefined) { + minimumMode = true; + } + + var result = 0, + columnWidth; + var children = this.sizerChildren; + var child, padding, childWidth, proportion; + + for (var i = 0; i < this.columnCount; i++) { + proportion = this.columnProportions[i]; + columnWidth = 0; + if ((proportion === 0) || minimumMode) { + for (var j = 0; j < this.rowCount; j++) { + child = children[(j * this.columnCount) + i]; + if (!child) { + continue; + } + if (child.rexSizer.hidden) { + continue; + } + + padding = child.rexSizer.padding; + childWidth = this.getChildWidth(child) + padding.left + padding.right; + columnWidth = Math.max(columnWidth, childWidth); + } + result += columnWidth; + } + // else,(proportion > 0) : columnWidth is 0 + this.columnWidth[i] = columnWidth; + } + + var space = this.space; + var indentLeft = Math.max(space.indentLeftOdd, space.indentLeftEven); + return result + Sum(space.left, indentLeft, ...space.column, space.right); +} + +export default GetChildrenWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetExpandedChildHeight.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetExpandedChildHeight.js new file mode 100644 index 000000000..76b5e4880 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetExpandedChildHeight.js @@ -0,0 +1,11 @@ +var GetExpandedChildHeight = function (child, rowHeight) { + var childHeight; + var childConfig = child.rexSizer; + if (childConfig.expand) { + var padding = childConfig.padding; + childHeight = rowHeight - padding.top - padding.bottom; + } + return childHeight; +} + +export default GetExpandedChildHeight; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetExpandedChildWidth.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetExpandedChildWidth.js new file mode 100644 index 000000000..73ab9a8a7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetExpandedChildWidth.js @@ -0,0 +1,11 @@ +var GetExpandedChildWidth = function (child, colWidth) { + var childWidth; + var childConfig = child.rexSizer; + if (childConfig.expand) { + var padding = childConfig.padding; + childWidth = colWidth - padding.left - padding.right; + } + return childWidth; +} + +export default GetExpandedChildWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetTotalColumnProportions.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetTotalColumnProportions.js new file mode 100644 index 000000000..8f440fdcd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetTotalColumnProportions.js @@ -0,0 +1,13 @@ +var GetTotalColumnProportions = function () { + var result = 0, + proportion; + for (var i = 0; i < this.columnCount; i++) { + proportion = this.columnProportions[i]; + if (proportion > 0) { + result += proportion; + } + } + return result; +} + +export default GetTotalColumnProportions; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetTotalRowProportions.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetTotalRowProportions.js new file mode 100644 index 000000000..ac6f0efbc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetTotalRowProportions.js @@ -0,0 +1,13 @@ +var GetTotalRowProportions = function () { + var result = 0, + proportion; + for (var i = 0; i < this.rowCount; i++) { + proportion = this.rowProportions[i]; + if (proportion > 0) { + result += proportion; + } + } + return result; +} + +export default GetTotalRowProportions; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GridSizer.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GridSizer.d.ts new file mode 100644 index 000000000..c0a025ea6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GridSizer.d.ts @@ -0,0 +1,145 @@ +// import * as Phaser from 'phaser'; +import BaseSizer from '../basesizer/BaseSizer.js'; + +export default GridSizer; + +declare namespace GridSizer { + type AlignTypes = number | 'center' | 'left' | 'right' | 'top' | 'bottom' | + 'left-top' | 'left-center' | 'left-bottom' | + 'center-top' | 'center-center' | 'center-bottom' | + 'right-top' | 'right-center' | 'right-bottom'; + type PaddingTypes = number | + { + left?: number, + right?: number, + top?: number, + bottom?: number + }; + + type CreateCellContainerCallbackType = ( + scene: Phaser.Scene, + x: number, y: number, + config: { + column?: number, row?: number, + + align?: GridSizer.AlignTypes, + padding?: GridSizer.PaddingTypes, + expand?: boolean, + key?: string + } + ) => Phaser.GameObjects.GameObject; + + interface IConfig extends BaseSizer.IConfig { + x?: number, + y?: number, + width?: number, + height?: number, + + column?: number, + row?: number, + + columnProportions?: number | number[], + rowProportions?: number | number[], + + space?: { + left?: number, right?: number, top?: number, bottom?: number, + + column?: number | number[], + row?: number | number[], + + indentLeftOdd?: number, indentLeftEven?: number, + indentTopOdd?: number, indentTopEven?: number, + }, + + createCellContainerCallback?: CreateCellContainerCallbackType + } + +} + + +declare class GridSizer extends BaseSizer { + sizerChildren: (Phaser.GameObjects.GameObject | null)[]; + + constructor( + scene: Phaser.Scene, + config?: GridSizer.IConfig + ); + + constructor( + scene: Phaser.Scene, + x: number, y: number, + config?: GridSizer.IConfig + ); + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + config?: GridSizer.IConfig + ); + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + column: number, row: number, + config?: GridSizer.IConfig + ); + + setColumnProportion(columnIndex: number, proportion: number): this; + setRowProportion(rowIndex: number, proportion: number): this; + + add( + gameObject: Phaser.GameObjects.GameObject, + config?: { + column?: number | undefined, + row?: number | undefined | true, + align?: GridSizer.AlignTypes, + padding?: GridSizer.PaddingTypes, + expand?: boolean, + key?: string + } + ): this; + + add( + gameObject: Phaser.GameObjects.GameObject, + columnIndex?: number | undefined, + rowIndex?: number | undefined | true, + align?: GridSizer.AlignTypes, + padding?: GridSizer.PaddingTypes, + expand?: boolean, + key?: string + ): this; + + remove( + gameObject: Phaser.GameObjects.GameObject, + destroyChild?: boolean + ): this; + + removeAt( + columnIndex: number, + rowIndex: number, + destroyChild?: boolean + ): this; + + removeAll( + destroyChild?: boolean + ): this; + + clear( + destroyChild?: boolean + ): this; + + columnCount: number; + rowCount: number; + + resetGrid( + column: number, row: number, + columnProportions?: number | number[], + rowProportions?: number | number[], + space?: { + column?: number | number[], + row?: number | number[], + } + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GridSizer.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GridSizer.js new file mode 100644 index 000000000..5c88ebe3e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GridSizer.js @@ -0,0 +1,171 @@ +import BaseSizer from '../basesizer/BaseSizer.js'; +import Methods from './Methods.js'; +import GetTotalColumnProportions from './GetTotalColumnProportions.js'; +import GetTotalRowProportions from './GetTotalRowProportions.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class GridSizer extends BaseSizer { + constructor(scene, x, y, minWidth, minHeight, columnCount, rowCount, columnProportions, rowProportions, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + minWidth = GetValue(config, 'width', undefined); + minHeight = GetValue(config, 'height', undefined); + columnCount = GetValue(config, 'column', (config.col || 0)); + rowCount = GetValue(config, 'row', 0); + columnProportions = GetValue(config, 'columnProportions', 0); + rowProportions = GetValue(config, 'rowProportions', 0); + } else if (IsPlainObject(minWidth)) { + config = minWidth; + minWidth = GetValue(config, 'width', undefined); + minHeight = GetValue(config, 'height', undefined); + columnCount = GetValue(config, 'column', (config.col || 0)); + rowCount = GetValue(config, 'row', 0); + columnProportions = GetValue(config, 'columnProportions', 0); + rowProportions = GetValue(config, 'rowProportions', 0); + } else if (IsPlainObject(columnCount)) { + config = columnCount; + columnCount = GetValue(config, 'column', (config.col || 0)); + rowCount = GetValue(config, 'row', 0); + columnProportions = GetValue(config, 'columnProportions', 0); + rowProportions = GetValue(config, 'rowProportions', 0); + } else if (IsPlainObject(columnProportions)) { + config = columnProportions; + columnProportions = GetValue(config, 'columnProportions', 0); + rowProportions = GetValue(config, 'rowProportions', 0); + } + super(scene, x, y, minWidth, minHeight, config); + + this.type = 'rexGridSizer'; + this.resetGrid( + columnCount, rowCount, + columnProportions, rowProportions, + GetValue(config, 'space', undefined) + ); + + this.setIndentLeft( + GetValue(config, 'space.indentLeftOdd', 0), + GetValue(config, 'space.indentLeftEven', 0) + ); + this.setIndentTop( + GetValue(config, 'space.indentTopOdd', 0), + GetValue(config, 'space.indentTopEven', 0) + ); + + this.addChildrenMap('items', this.sizerChildren); + + var createCellContainerCallback = GetValue(config, 'createCellContainerCallback'); + if (createCellContainerCallback) { + for (var y = 0, ycnt = this.rowCount; y < ycnt; y++) { + for (var x = 0, xcnt = this.columnCount; x < xcnt; x++) { + var addConfig = { column: x, row: y }; + var child = createCellContainerCallback(scene, x, y, addConfig); + if (child) { + this.add(child, addConfig); + } + } + } + } + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + super.destroy(fromScene); + + // More free resources + this.columnProportions = undefined; + this.rowProportions = undefined; + this.columnWidth = undefined; + this.rowHeight = undefined; + } + + setIndentLeft(odd, even) { + this.space.indentLeftOdd = odd; + this.space.indentLeftEven = even; + return this; + } + + setIndentTop(odd, even) { + this.space.indentTopOdd = odd; + this.space.indentTopEven = even; + return this; + } + + setColumnProportion(columnIndex, proportion) { + if (columnIndex >= this.columnProportions.length) { + return this; + } + this.columnProportions[columnIndex] = proportion; + return this; + } + + setRowProportion(rowIndex, proportion) { + if (rowIndex >= this.rowProportions.length) { + return this; + } + this.rowProportions[rowIndex] = proportion; + return this; + } + + get totalColumnProportions() { + if (this._totalColumnProportions === undefined) { + this._totalColumnProportions = GetTotalColumnProportions.call(this); + } + return this._totalColumnProportions; + } + + get totalRowProportions() { + if (this._totalRowProportions === undefined) { + this._totalRowProportions = GetTotalRowProportions.call(this); + } + return this._totalRowProportions; + } + + getChildAt(columnIndex, rowIndex) { + return this.sizerChildren[(rowIndex * this.columnCount) + columnIndex]; + } + + childToGridIndex(child, out) { + if (!child) { + return null; + } + + var index = this.sizerChildren.indexOf(child); + if (index === -1) { + return null; + } + + if (out === undefined) { + out = {}; + } + out.x = index % this.columnCount; + out.y = Math.floor(index / this.columnCount); + return out; + } + + getColumnWidth(columnIndex) { + var colProportion = this.columnProportions[columnIndex]; + var colWidth = (colProportion === 0) ? this.columnWidth[columnIndex] : (colProportion * this.proportionWidthLength); + return colWidth; + } + + getRowHeight(rowIndex) { + var rowProportion = this.rowProportions[rowIndex]; + var rowHeight = (rowProportion === 0) ? this.rowHeight[rowIndex] : (rowProportion * this.proportionHeightLength); + return rowHeight; + } +} + +Object.assign( + GridSizer.prototype, + Methods +); + +export default GridSizer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/InsertEmptyColumn.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/InsertEmptyColumn.js new file mode 100644 index 000000000..637859743 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/InsertEmptyColumn.js @@ -0,0 +1,34 @@ +var InsertEmptyColumn = function (colIndex, proportion, space) { + if (proportion === undefined) { + proportion = this.columnProportions[0] || 0; + } + if (space === undefined) { + space = this.space.column[0] || 0; + } + + this.columnCount += 1; + this.gridCount += this.rowCount; + + for (var i = this.rowCount - 1; i >= 0; i--) { + var insertIndex = (i * this.columnCount) + colIndex; + this.sizerChildren.splice(insertIndex, 0, null); + } + + this.columnProportions.push(proportion); + + this.columnWidth.length += 1; // this.columnWidth will be recalculated when layout() + + this.space.column.splice(colIndex, 0, space); + + return this; +} + +var AddEmptyColumn = function (proportion, space) { + InsertEmptyColumn.call(this, this.columnCount, proportion, space); + return this; +} + +export { + InsertEmptyColumn, + AddEmptyColumn +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/InsertEmptyRow.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/InsertEmptyRow.js new file mode 100644 index 000000000..d1bfc7947 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/InsertEmptyRow.js @@ -0,0 +1,35 @@ +var InseryEmptyRow = function (rowIndex, proportion, space) { + if (proportion === undefined) { + proportion = this.rowProportions[0] || 0; + } + if (space === undefined) { + space = this.space.row[0] || 0; + } + + this.rowCount += 1; + this.gridCount += this.columnCount; + + var args = [rowIndex * this.columnCount, 0]; + for (var i = 0; i < this.columnCount; i++) { + args.push(null); + } + this.sizerChildren.splice.apply(this.sizerChildren, args); + + this.rowProportions.push(proportion); + + this.rowHeight.length += 1; // this.rowHeight will be recalculated when layout() + + this.space.row.splice(rowIndex, 0, space); + + return this; +} + +var AddEmptyRow = function (proportion, space) { + InseryEmptyRow.call(this, this.rowCount, proportion, space); + return this; +} + +export { + InseryEmptyRow, + AddEmptyRow +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/LayoutChildren.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/LayoutChildren.js new file mode 100644 index 000000000..62b172e89 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/LayoutChildren.js @@ -0,0 +1,68 @@ +import ResizeGameObject from '../../../plugins/utils/size/ResizeGameObject.js'; +import PreLayoutChild from '../basesizer/utils/PreLayoutChild.js'; +import LayoutChild from '../basesizer/utils/LayoutChild.js'; +import CheckSize from '../basesizer/utils/CheckSize.js'; + +var LayoutChildren = function () { + var child, childConfig, padding; + var startX = this.innerLeft, + startY = this.innerTop; + var itemX, + itemY = startY; + var x, y, width, height; // Align zone + var childWidth, childHeight; + // Layout grid children + var columnSpace = this.space.column, + rowSpace = this.space.row, + indentLeftOdd = this.space.indentLeftOdd, + indentLeftEven = this.space.indentLeftEven, + indentTopOdd = this.space.indentTopOdd, + indentTopEven = this.space.indentTopEven; + + var colWidth, rowHeight; + var indentLeft, indentTop; + for (var rowIndex = 0; rowIndex < this.rowCount; rowIndex++) { + rowHeight = this.getRowHeight(rowIndex); + + indentLeft = (rowIndex % 2) ? indentLeftEven : indentLeftOdd; + itemX = startX + indentLeft; + for (var columnIndex = 0; columnIndex < this.columnCount; columnIndex++) { + colWidth = this.getColumnWidth(columnIndex); + + child = this.getChildAt(columnIndex, rowIndex); + if ((!child) || (child.rexSizer.hidden)) { + itemX += (colWidth + columnSpace[columnIndex]); + continue; + } + + PreLayoutChild.call(this, child); + + childWidth = this.getExpandedChildWidth(child, colWidth); + childHeight = this.getExpandedChildHeight(child, rowHeight); + if (child.isRexSizer) { + child.runLayout(this, childWidth, childHeight); + CheckSize(child, this); + } else { + ResizeGameObject(child, childWidth, childHeight); + } + + childConfig = child.rexSizer; + padding = childConfig.padding; + + x = (itemX + padding.left); + width = colWidth - padding.left - padding.right; + + indentTop = (columnIndex % 2) ? indentTopEven : indentTopOdd; + y = (itemY + indentTop + padding.top); + height = rowHeight - padding.top - padding.bottom; + + LayoutChild.call(this, child, x, y, width, height, childConfig.align); + + itemX += (colWidth + columnSpace[columnIndex]); + } + + itemY += (rowHeight + rowSpace[rowIndex]); + } +} + +export default LayoutChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/Methods.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/Methods.js new file mode 100644 index 000000000..ab6d7f975 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/Methods.js @@ -0,0 +1,45 @@ +import GetChildrenWidth from './GetChildrenWidth.js'; +import GetChildrenHeight from './GetChildrenHeight.js'; +import GetExpandedChildWidth from './GetExpandedChildWidth.js'; +import GetExpandedChildHeight from './GetExpandedChildHeight.js'; +import GetChildrenSizers from './GetChildrenSizers.js'; +import PreLayout from './PreLayout.js'; +import LayoutChildren from './LayoutChildren.js'; +import ResolveWidth from './ResolveWidth.js'; +import ResolveHeight from './ResolveHeight.js'; +import ResolveChildrenWidth from './ResolveChildrenWidth.js'; +import RunWidthWrap from './RunWidthWrap.js'; +import AddChildMethods from './AddChildMethods.js'; +import RemoveChildMethods from './RemoveChildMethods.js'; +import ResetGrid from './ResetGrid.js'; +import { InseryEmptyRow, AddEmptyRow } from './InsertEmptyRow.js'; +import { InsertEmptyColumn, AddEmptyColumn } from './InsertEmptyColumn.js'; + + +var methods = { + getChildrenWidth: GetChildrenWidth, + getChildrenHeight: GetChildrenHeight, + getExpandedChildWidth: GetExpandedChildWidth, + getExpandedChildHeight: GetExpandedChildHeight, + getChildrenSizers: GetChildrenSizers, + preLayout: PreLayout, + layoutChildren: LayoutChildren, + resolveWidth: ResolveWidth, + resolveHeight: ResolveHeight, + resolveChildrenWidth: ResolveChildrenWidth, + runWidthWrap: RunWidthWrap, + + resetGrid: ResetGrid, + inseryEmptyRow: InseryEmptyRow, + addEmptyRow: AddEmptyRow, + insertEmptyColumn: InsertEmptyColumn, + addEmptyColumn: AddEmptyColumn, +}; + +Object.assign( + methods, + AddChildMethods, + RemoveChildMethods +); + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/PreLayout.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/PreLayout.js new file mode 100644 index 000000000..2c0496853 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/PreLayout.js @@ -0,0 +1,11 @@ +import PreLayoutBase from '../basesizer/PreLayout.js'; + +var PreLayout = function () { + this._totalColumnProportions = undefined; + this._totalRowProportions = undefined; + this.proportionWidthLength = undefined; + this.proportionHeightLength = undefined; + PreLayoutBase.call(this); + return this; +} +export default PreLayout; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/RemoveChildMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/RemoveChildMethods.js new file mode 100644 index 000000000..39335ca21 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/RemoveChildMethods.js @@ -0,0 +1,45 @@ +import RemoveChild from '../basesizer/utils/RemoveChild.js'; +import ClearChildren from '../basesizer/utils/ClearChildren.js'; +import ArrayFill from '../../../plugins/utils/array/Fill.js'; + +export default { + remove(gameObject, destroyChild) { + if (this.getParentSizer(gameObject) !== this) { + return this; + } + + var idx = this.sizerChildren.indexOf(gameObject); + if (idx !== -1) { + this.sizerChildren[idx] = null; + } + + RemoveChild.call(this, gameObject, destroyChild); + return this; + }, + + removeAt(columnIndex, rowIndex, destroyChild) { + var child = this.getChildAt(columnIndex, rowIndex); + if (child) { + this.remove(child, destroyChild); + } + return this; + }, + + removeAll(destroyChild) { + for (var i = this.sizerChildren.length - 1; i >= 0; i--) { + var child = this.sizerChildren[i]; + if (!child) { + continue; + } + + this.remove(child, destroyChild); + } + return this; + }, + + clear(destroyChild) { + ArrayFill(this.sizerChildren, null); + ClearChildren.call(this, destroyChild); + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResetGrid.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResetGrid.js new file mode 100644 index 000000000..37c5edb83 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResetGrid.js @@ -0,0 +1,77 @@ +import ArrayFill from '../../../plugins/utils/array/Fill.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var ResetGrid = function (columnCount, rowCount, columnProportions, rowProportions, space) { + if (columnProportions === undefined) { + columnProportions = 0; + } + if (rowProportions === undefined) { + rowProportions = 0; + } + + this.columnCount = columnCount; + this.rowCount = rowCount; + this.gridCount = columnCount * rowCount; + + // children + if (this.sizerChildren === undefined) { + this.sizerChildren = []; + } else { + this.removeAll(); + } + this.sizerChildren.length = columnCount * rowCount; + ArrayFill(this.sizerChildren, null); + + // proportions + this.columnProportions = []; + this.columnProportions.length = columnCount; + if (typeof (columnProportions) === 'number') { + ArrayFill(this.columnProportions, columnProportions); + } else { + for (var i = 0; i < columnCount; i++) { + this.columnProportions[i] = columnProportions[i] || 0; + } + } + this.rowProportions = []; + this.rowProportions.length = rowCount; + if (typeof (rowProportions) === 'number') { + ArrayFill(this.rowProportions, rowProportions); + } else { + for (var i = 0; i < rowCount; i++) { + this.rowProportions[i] = rowProportions[i] || 0; + } + } + + // width & height + this.columnWidth = []; + this.columnWidth.length = columnCount; + this.rowHeight = []; + this.rowHeight.length = rowCount; + + // space + this.space.column = []; + this.space.column.length = columnCount - 1; + var columnSpace = GetValue(space, 'column', 0); + if (typeof (columnSpace) === 'number') { + ArrayFill(this.space.column, columnSpace); + } else { + for (var i = 0, cnt = this.space.column.length; i < cnt; i++) { + this.space.column[i] = columnSpace[i] || 0; + } + } + this.space.row = []; + this.space.row.length = rowCount - 1; + var rowSpace = GetValue(space, 'row', 0); + if (typeof (rowSpace) === 'number') { + ArrayFill(this.space.row, rowSpace); + } else { + for (var i = 0, cnt = this.space.row.length; i < cnt; i++) { + this.space.row[i] = rowSpace[i] || 0; + } + } + + return this; +} + +export default ResetGrid; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResolveChildrenWidth.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResolveChildrenWidth.js new file mode 100644 index 000000000..77e4f6b28 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResolveChildrenWidth.js @@ -0,0 +1,16 @@ +var ResolveChildrenWidth = function (parentWidth) { + // Resolve width of sizer children + var child, childWidth; + var colWidth; + for (var i in this.sizerChildren) { + child = this.sizerChildren[i]; + if (child && child.isRexSizer && !child.ignoreLayout) { + colWidth = this.getColumnWidth(parseInt(i) % this.columnCount); + childWidth = this.getExpandedChildWidth(child, colWidth); + childWidth = child.resolveWidth(childWidth); + child.resolveChildrenWidth(childWidth); + } + } +} + +export default ResolveChildrenWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResolveHeight.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResolveHeight.js new file mode 100644 index 000000000..6a6ea91dc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResolveHeight.js @@ -0,0 +1,24 @@ +import ResolveHeightBase from '../basesizer/ResolveHeight.js'; + +var ResolveHeight = function (height) { + var height = ResolveHeightBase.call(this, height); + + // Get proportionLength + if (this.proportionHeightLength === undefined) { + var totalRowProportions = this.totalRowProportions; + if (totalRowProportions > 0) { + var remainder = height - this.getChildrenHeight(false); + if (remainder >= 0) { + this.proportionHeightLength = remainder / totalRowProportions; + } else { + // Warning + } + } else { + this.proportionHeightLength = 0; + } + } + + return height; +} + +export default ResolveHeight; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResolveWidth.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResolveWidth.js new file mode 100644 index 000000000..7c06bfdac --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResolveWidth.js @@ -0,0 +1,24 @@ +import ResolveWidthBase from '../basesizer/ResolveWidth.js'; + +var ResolveWidth = function (width) { + var width = ResolveWidthBase.call(this, width); + + // Get proportionLength + if (this.proportionWidthLength === undefined) { + var totalColumnProportions = this.totalColumnProportions; + if (totalColumnProportions > 0) { + var remainder = width - this.getChildrenWidth(false); + if (remainder >= 0) { + this.proportionWidthLength = remainder / totalColumnProportions; + } else { + // Warning + } + } else { + this.proportionWidthLength = 0; + } + } + + return width; +} + +export default ResolveWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/RunWidthWrap.js b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/RunWidthWrap.js new file mode 100644 index 000000000..da329aec4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridsizer/RunWidthWrap.js @@ -0,0 +1,25 @@ +// Default method +var RunWidthWrap = function (width) { + var child, childWidth; + var colWidth; + for (var i in this.sizerChildren) { + child = this.sizerChildren[i]; + if ( + (!child) || + (child.isRexSizer && child.ignoreLayout) || + (!child.runWidthWrap) + ) { + continue; + } + + colWidth = this.getColumnWidth(parseInt(i) % this.columnCount); + childWidth = this.getExpandedChildWidth(child, colWidth); + if (child.isRexSizer) { + childWidth = child.resolveWidth(childWidth); + } + child.runWidthWrap(childWidth); + } + return this; +} + +export default RunWidthWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/Factory.d.ts new file mode 100644 index 000000000..66c0f9145 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/Factory.d.ts @@ -0,0 +1,5 @@ +import GridTable from './GridTable'; + +export default function ( + config?: GridTable.IConfig +): GridTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/Factory.js new file mode 100644 index 000000000..ae03ac32c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/Factory.js @@ -0,0 +1,13 @@ +import GridTable from './GridTable.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('gridTable', function (config) { + var gameObject = new GridTable(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.GridTable', GridTable); + +export default GridTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/GridTable.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/GridTable.d.ts new file mode 100644 index 000000000..671601c0c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/GridTable.d.ts @@ -0,0 +1,63 @@ +// import * as Phaser from 'phaser'; +import Scrollable from '../utils/scrollable/Scrollable'; +import GridTableCore from '../../../plugins/gridtable' + +export default GridTable; + +declare namespace GridTable { + + type CreateCellContainerCallbackType = ( + cell: GridTableCore.CellData, + cellContainer: Phaser.GameObjects.GameObject | null + ) => Phaser.GameObjects.GameObject | null; + + interface IConfig extends Scrollable.IConfig { + space?: { + left?: number, right?: number, top?: number, bottom?: number, + + table?: number | { + left?: number, right?: number, top?: number, bottom?: number, + }, + + header?: number, + footer?: number, + }, + + scrollMode?: GridTableCore.ScrollModeType, + + table: { + width?: number | undefined, + height?: number | undefined, + + cellWidth?: number | undefined, + cellHeight?: number | undefined, + columns?: number, + mask?: GridTableCore.MaskConfig, + interactive?: boolean, + reuseCellContainer?: boolean, + }, + + createCellContainerCallback: CreateCellContainerCallbackType, + + items: unknown[] + } + +} + +declare class GridTable extends Scrollable { + constructor( + scene: Phaser.Scene, + config?: GridTable.IConfig + ); + + setItems(items?: unknown[]): this; + refresh(): this; + updateVisibleCell(cellIndex: number): this; + + getCell(cellIndex: number): GridTableCore.CellData; + getCellContainer(cellIndex: number): Phaser.GameObjects.GameObject | null; + startRowIndex: number; + + scrollToRow(rowIndex: number): this; + scrollToNextRow(rowCount?: number): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/GridTable.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/GridTable.js new file mode 100644 index 000000000..2ec830ee3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/GridTable.js @@ -0,0 +1,140 @@ +import Scrollable from '../utils/scrollable/Scrollable.js'; +import GetScrollMode from '../utils/GetScrollMode.js'; +import GridTableCore from '../../../plugins/gridtable.js'; +import InjectProperties from './InjectProperties.js'; +import TableOnCellVisible from './TableOnCellVisible.js'; +import TableSetInteractive from './input/TableSetInteractive.js'; +import NOOP from '../../../plugins/utils/object/NOOP.js'; +import SetItems from './SetItems.js'; +import ScrollMethods from './ScrollMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class GridTable extends Scrollable { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + // Create grid table core + var scrollMode = GetScrollMode(config); + var tableConfig = GetValue(config, 'table', undefined) + if (tableConfig === undefined) { + tableConfig = {}; + } + tableConfig.scrollMode = scrollMode; + tableConfig.clamplTableOXY = GetValue(config, 'clamplChildOY', false); + var tableWidth = GetValue(tableConfig, 'width', undefined); + var tableHeight = GetValue(tableConfig, 'height', undefined); + var table = new GridTableCore(scene, 0, 0, tableWidth, tableHeight, tableConfig); + scene.add.existing(table); // Important: Add to display list for touch detecting + var proportion, expand; + if (scrollMode === 0) { + proportion = (tableWidth === undefined) ? 1 : 0; + expand = (tableHeight === undefined); + } else { + proportion = (tableHeight === undefined) ? 1 : 0; + expand = (tableWidth === undefined); + } + // Inject properties for scrollable interface + InjectProperties(table); + // Set minWidth/minHeight to 0 if tableWidth/tableHeight is undefined + table._minWidth = (tableWidth === undefined) ? 0 : undefined; + table._minHeight = (tableHeight === undefined) ? 0 : undefined; + + // Fill config of scrollable + config.type = 'rexGridTable'; + config.child = { + gameObject: table, + proportion: proportion, + expand: expand, + }; + var spaceConfig = GetValue(config, 'space', undefined); + if (spaceConfig) { + spaceConfig.child = spaceConfig.table; + } + super(scene, config); + + this.addChildrenMap('table', table); + this.addChildrenMap('tableLayer', table.maskLayer); + + this.eventEmitter = GetValue(config, 'eventEmitter', this); + var callback = GetValue(config, 'createCellContainerCallback', NOOP); + var scope = GetValue(config, 'createCellContainerCallbackScope', undefined); + this.setCreateCellContainerCallback(callback, scope); + TableOnCellVisible.call(this, table); + + this.resizeControllerFlag = false; + var eventName = (scrollMode === 0) ? 'cellheightchange' : 'cellwidthchange'; + table.on(eventName, function () { + this.resizeControllerFlag = true; + }, this); + + if (GetValue(tableConfig, 'interactive', true)) { + TableSetInteractive.call(this, table, tableConfig); + } + this.setItems(GetValue(config, 'items', [])); + + scene.game.events.on('poststep', this.onPostStep, this); + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + this.scene.game.events.off('poststep', this.onPostStep, this); + + super.destroy(fromScene); + } + + setCreateCellContainerCallback(callback, scope) { + this.createCellContainerCallback = callback; + this.createCellContainerCallbackScope = scope; + return this; + } + + refresh() { + this.setItems(this.items); + return this; + } + + getCell(cellIdx) { + var table = this.childrenMap.child; + return table.getCell(cellIdx); + } + + getCellContainer(cellIdx) { + var table = this.childrenMap.child; + return table.getCellContainer(cellIdx); + } + + updateVisibleCell(cellIdx) { + var table = this.childrenMap.child; + return table.updateVisibleCell(cellIdx); + } + + onPostStep() { + if (this.resizeControllerFlag) { + this.resizeController(); + this.resizeControllerFlag = false; + } + } + + get startRowIndex() { + var table = this.childrenMap.child; + return table.startRowIndex; + } +} + +var methods = { + setItems: SetItems +} +Object.assign( + GridTable.prototype, + ScrollMethods, + methods, +); + +export default GridTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/InjectProperties.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/InjectProperties.js new file mode 100644 index 000000000..c1080c108 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/InjectProperties.js @@ -0,0 +1,32 @@ +var InjectProperties = function (table) { + Object.defineProperty(table, 'childOY', { + configurable: true, + get: function () { + return table.tableOY; + }, + set: function (value) { + table.tableOY = value; + } + }); + Object.defineProperty(table, 'topChildOY', { + get: function () { + return table.topTableOY; + } + }); + Object.defineProperty(table, 'bottomChildOY', { + get: function () { + return table.bottomTableOY; + } + }); + Object.defineProperty(table, 'childVisibleHeight', { + get: function () { + return table.instHeight; + } + }); + Object.defineProperty(table, 'childHeight', { + get: function () { + return table.tableHeight; + } + }); +}; +export default InjectProperties; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/ScrollMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/ScrollMethods.js new file mode 100644 index 000000000..1e9b6f2cb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/ScrollMethods.js @@ -0,0 +1,13 @@ +export default { + scrollToRow(rowIndex) { + var table = this.childrenMap.child; + table.scrollToRow(rowIndex); + return this; + }, + + scrollToNextRow(rowCount) { + var table = this.childrenMap.child; + table.scrollToNextRow(rowCount); + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/SetItems.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/SetItems.js new file mode 100644 index 000000000..288da02b3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/SetItems.js @@ -0,0 +1,16 @@ +var SetItems = function (items) { + if (items === undefined) { + this.items = []; + } else { + this.items = items; + } + + var table = this.childrenMap.child; + table.setCellsCount(this.items.length); + table.updateTable(true); + + this.resizeController(); + return this; +} + +export default SetItems; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/TableOnCellVisible.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/TableOnCellVisible.js new file mode 100644 index 000000000..936f044e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/TableOnCellVisible.js @@ -0,0 +1,28 @@ +var TableOnCellVisible = function (table) { + table.on('cellvisible', function (cell, cellContainer, table) { + var callback = this.createCellContainerCallback; + var scope = this.createCellContainerCallbackScope; + cell.item = this.items[cell.index]; + cell.items = this.items; + var cellContainer; + if (scope) { + cellContainer = callback.call(scope, cell, cellContainer, table); + } else { + cellContainer = callback(cell, cellContainer, table); + } + + if (cellContainer) { + if ((cell.cellContainerAlign == null) && cellContainer.setOrigin) { + cellContainer.setOrigin(0); + } + if (cellContainer.isRexSizer) { + cellContainer.layout(); // Use original size + } + } + + cell.item = undefined; + cell.items = undefined; + cell.setContainer(cellContainer); + }, this); +} +export default TableOnCellVisible; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/ClickCell.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/ClickCell.js new file mode 100644 index 000000000..0449b7547 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/ClickCell.js @@ -0,0 +1,20 @@ +import Button from '../../../../plugins/input/button/Button.js'; +import EmitCellEvent from './EmitCellEvent.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var ClickCell = function (table, tableConfig) { + var buttonConfig = GetValue(tableConfig, 'click', undefined); + if (buttonConfig === false) { + return; + } else if (buttonConfig === undefined) { + buttonConfig = {}; + } + buttonConfig.threshold = 10; + table._click = new Button(table, buttonConfig); + table._click.on('click', function (button, gameObject, pointer, event) { + EmitCellEvent(this.eventEmitter, 'cell.click', gameObject, pointer.worldX, pointer.worldY, pointer, event); + }, this); +}; + +export default ClickCell; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/EmitCellEvent.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/EmitCellEvent.js new file mode 100644 index 000000000..50c0b7320 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/EmitCellEvent.js @@ -0,0 +1,17 @@ +var EmitCellEvent = function (eventEmitter, eventName, table, x, y, pointer, event) { + var cellIndex; + if (y === undefined) { + cellIndex = x; + } else { + cellIndex = table.pointToCellIndex(x, y); + } + if ((cellIndex === null) || (cellIndex === undefined)) { + return; + } + var cellContainer = table.getCellContainer(cellIndex); + if (cellContainer) { + eventEmitter.emit(eventName, cellContainer, cellIndex, pointer, event); + } +} + +export default EmitCellEvent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/OverCell.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/OverCell.js new file mode 100644 index 000000000..bc35edf64 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/OverCell.js @@ -0,0 +1,30 @@ +import EmitCellEvent from './EmitCellEvent.js'; + +var OverCell = function (table, tableConfig) { + table + .on('pointermove', OnMove, this) + .on('pointerover', OnMove, this) + .on('pointerout', OnOut, this) // pointer-up is included too +} + +var OnMove = function (pointer, localX, localY, event) { + var table = this.childrenMap.child; + var cellIndex = table.pointToCellIndex(pointer.worldX, pointer.worldY); + if (cellIndex === table.input.lastOverCellIndex) { + return; + } + + var preCellIndex = table.input.lastOverCellIndex; + table.input.lastOverCellIndex = cellIndex; + EmitCellEvent(this.eventEmitter, 'cell.out', table, preCellIndex, undefined, pointer, event); + EmitCellEvent(this.eventEmitter, 'cell.over', table, cellIndex, undefined, pointer, event); +} + +var OnOut = function (pointer, event) { + var table = this.childrenMap.child; + var cellIndex = table.input.lastOverCellIndex; + table.input.lastOverCellIndex = undefined; + EmitCellEvent(this.eventEmitter, 'cell.out', table, cellIndex, undefined, pointer, event); +} + +export default OverCell; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/PointerUpDownCell.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/PointerUpDownCell.js new file mode 100644 index 000000000..00cb1480b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/PointerUpDownCell.js @@ -0,0 +1,13 @@ +import EmitCellEvent from './EmitCellEvent.js'; + +var PointerUpDownCell = function (table, tableConfig) { + table + .on('pointerdown', function (pointer, localX, localY, event) { + EmitCellEvent(this.eventEmitter, 'cell.down', table, pointer.worldX, pointer.worldY, pointer, event); + }, this) + .on('pointerup', function (pointer, localX, localY, event) { + EmitCellEvent(this.eventEmitter, 'cell.up', table, pointer.worldX, pointer.worldY, pointer, event); + }, this) +} + +export default PointerUpDownCell; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/PressCell.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/PressCell.js new file mode 100644 index 000000000..8590d4426 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/PressCell.js @@ -0,0 +1,22 @@ +import Press from '../../press/Press.js'; +import EmitCellEvent from './EmitCellEvent.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var PressCell = function (table, tableConfig) { + var pressConfig = GetValue(tableConfig, 'press', undefined); + if (pressConfig === false) { + return; + } + + table._press = new Press(table, pressConfig); + table._press + .on('pressstart', function (press, gameObject, lastPointer) { + EmitCellEvent(this.eventEmitter, 'cell.pressstart', table, press.worldX, press.worldY, lastPointer); + }, this) + .on('pressend', function (press, gameObject, lastPointer) { + EmitCellEvent(this.eventEmitter, 'cell.pressend', table, press.worldX, press.worldY, lastPointer); + }, this) +}; + +export default PressCell; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/SwipeCell.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/SwipeCell.js new file mode 100644 index 000000000..673107d20 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/SwipeCell.js @@ -0,0 +1,26 @@ +import Swipe from '../../swipe/Swipe.js'; +import EmitCellEvent from './EmitCellEvent.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var SwipeCell = function (table, tableConfig) { + var swipeConfig = GetValue(tableConfig, 'swipe', undefined); + if (swipeConfig === false) { + return; + } else if (swipeConfig === undefined) { + swipeConfig = {}; + } + swipeConfig.dir = '4dir'; + table._swipe = new Swipe(table, swipeConfig); + table._swipe + .on('swipe', function (swipe, gameObject, lastPointer) { + var dirName = + (swipe.left) ? 'left' : + (swipe.right) ? 'right' : + (swipe.up) ? 'up' : + 'down'; + EmitCellEvent(this.eventEmitter, `cell.swipe${dirName}`, table, swipe.worldX, swipe.worldY, lastPointer); + }, this) +}; + +export default SwipeCell; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/TableSetInteractive.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/TableSetInteractive.js new file mode 100644 index 000000000..610fb9922 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/TableSetInteractive.js @@ -0,0 +1,19 @@ +import PointerUpDownCell from './PointerUpDownCell.js'; +import OverCell from './OverCell.js'; +import ClickCell from './ClickCell.js'; +import TapCell from './TapCell.js'; +import PressCell from './PressCell.js'; +import SwipeCell from './SwipeCell.js'; + +var TableSetInteractive = function (table, tableConfig) { + table.setInteractive(); + + PointerUpDownCell.call(this, table, tableConfig); + OverCell.call(this, table, tableConfig); + ClickCell.call(this, table, tableConfig); + TapCell.call(this, table, tableConfig); + PressCell.call(this, table, tableConfig); + SwipeCell.call(this, table, tableConfig); +} + +export default TableSetInteractive; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/TapCell.js b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/TapCell.js new file mode 100644 index 000000000..dd9f63fb4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/TapCell.js @@ -0,0 +1,20 @@ +import Tap from '../../tap/Tap.js'; +import EmitCellEvent from './EmitCellEvent.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var TapCell = function (table, tableConfig) { + var tapConfig = GetValue(tableConfig, 'tap', undefined); + if (tapConfig === false) { + return; + } + + table._tap = new Tap(table, tapConfig); + table._tap + .on('tap', function (tap, gameObject, lastPointer) { + var eventName = `cell.${tap.tapsCount}tap` + EmitCellEvent(this.eventEmitter, eventName, tap.gameObject, tap.worldX, tap.worldY, lastPointer); + }, this) +}; + +export default TapCell; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/Factory.d.ts new file mode 100644 index 000000000..b7896df35 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/Factory.d.ts @@ -0,0 +1,6 @@ +import HiddenEdit from './HiddenEdit'; + +export default function ( + textObject: Phaser.GameObjects.GameObject, + config?: HiddenEdit.IConfig +): HiddenEdit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/Factory.js new file mode 100644 index 000000000..73488ef24 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/Factory.js @@ -0,0 +1,13 @@ +import HiddenEdit from './HiddenEdit.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('hiddenEdit', function (textObject, config) { + var gameObject = new HiddenEdit(textObject, config); + // Note: Don't add this game object into scene + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.HiddenEdit', HiddenEdit); + +export default HiddenEdit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/HiddenEdit.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/HiddenEdit.d.ts new file mode 100644 index 000000000..322c6dff3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/HiddenEdit.d.ts @@ -0,0 +1,2 @@ +import HiddenEdit from '../../../plugins/hiddeninputtext'; +export default HiddenEdit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/HiddenEdit.js b/ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/HiddenEdit.js new file mode 100644 index 000000000..98a8d346e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/HiddenEdit.js @@ -0,0 +1,2 @@ +import HiddenEdit from '../../../plugins/hiddeninputtext.js'; +export default HiddenEdit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/holygrail/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/Factory.d.ts new file mode 100644 index 000000000..e7fb9162a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/Factory.d.ts @@ -0,0 +1,5 @@ +import HolyGrail from './HolyGrail'; + +export default function ( + config?: HolyGrail.IConfig +): HolyGrail; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/holygrail/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/Factory.js new file mode 100644 index 000000000..336daaa3c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/Factory.js @@ -0,0 +1,13 @@ +import HolyGrail from './HolyGrail.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('holyGrail', function (config) { + var gameObject = new HolyGrail(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.HolyGrail', HolyGrail); + +export default HolyGrail; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/holygrail/HolyGrail.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/HolyGrail.d.ts new file mode 100644 index 000000000..22fcc5bdd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/HolyGrail.d.ts @@ -0,0 +1,69 @@ +// import * as Phaser from 'phaser'; +import Sizer from '../sizer/Sizer'; + +export default HolyGrail; + +declare namespace HolyGrail { + + type HAlignTypes = number | 'left' | 'center' | 'right'; + type VAlignTypes = number | 'top' | 'center' | 'bottom'; + + interface IConfig extends Sizer.IConfig { + space?: { + left?: number, right?: number, top?: number, bottom?: number, + + header?: number | { left?: number, right?: number, top?: number, bottom?: number }, + leftSide?: number | { left?: number, right?: number, top?: number, bottom?: number }, + content?: { left?: number, right?: number, top?: number, bottom?: number }, + rightSide?: number | { left?: number, right?: number, top?: number, bottom?: number }, + footer?: number | { left?: number, right?: number, top?: number, bottom?: number }, + }; + + background?: Phaser.GameObjects.GameObject, + + header?: Phaser.GameObjects.GameObject, + + leftSide?: Phaser.GameObjects.GameObject, + + content?: Phaser.GameObjects.GameObject, + + rightSide?: Phaser.GameObjects.GameObject, + + footer?: Phaser.GameObjects.GameObject, + + layoutMode?: 0 | 1 | 2 | 3 | 'FFF' | 'LFF' | 'FFR' | 'LFR', + + proportion?: { + header?: number, + leftSide?: number, + content?: number, + rightSide?: number, + footer?: number, + }, + + expand?: { + header?: boolean, + leftSide?: boolean, + content?: boolean, + rightSide?: boolean, + footer?: boolean, + }, + + align?: { + header?: HAlignTypes, + leftSide?: VAlignTypes, + content?: HAlignTypes | VAlignTypes, + rightSide?: VAlignTypes, + footer?: HAlignTypes, + }, + + } +} + +declare class HolyGrail extends Sizer { + constructor( + scene: Phaser.Scene, + config?: HolyGrail.IConfig + ); + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/holygrail/HolyGrail.js b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/HolyGrail.js new file mode 100644 index 000000000..bd891f48c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/HolyGrail.js @@ -0,0 +1,28 @@ +import Sizer from '../sizer/Sizer.js'; +import Build from './methods/Build.js' + +class HolyGrail extends Sizer { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + config.orientation = 1; // top-to-bottom + // Create sizer + super(scene, config); + this.type = 'rexHolyGrail'; + + this.build(config); + } +} + +var methods = { + build: Build, +} + +Object.assign( + HolyGrail.prototype, + methods, +) + +export default HolyGrail; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/Build.js b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/Build.js new file mode 100644 index 000000000..f9f6db432 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/Build.js @@ -0,0 +1,40 @@ +import LayoutMode0 from './LayoutMode0.js'; +import LayoutMode1 from './LayoutMode1.js'; +import LayoutMode2 from './LayoutMode2.js'; +import LayoutMode3 from './LayoutMode3.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const LayoutCallbacks = [LayoutMode0, LayoutMode1, LayoutMode2, LayoutMode3]; + +var Build = function (config) { + this.clear(true); + + // Add Background + var background = GetValue(config, 'background', undefined); + if (background) { + this.addBackground(background); + } + + var layoutMode = GetValue(config, 'layoutMode', 0); + if (typeof (layoutMode) === 'string') { + layoutMode = LayoutModesMap[layoutMode.toUpperCase()]; + } + var layoutCallback = LayoutCallbacks[layoutMode] || LayoutCallbacks[0]; + layoutCallback.call(this, config); + + this.addChildrenMap('background', config.background); + this.addChildrenMap('header', config.header); + this.addChildrenMap('leftSide', config.leftSide); + this.addChildrenMap('content', config.content); + this.addChildrenMap('rightSide', config.rightSide); + this.addChildrenMap('footer', config.footer); +} + +const LayoutModesMap = { + 'FFF': 0, + 'LFF': 1, + 'FFR': 2, + 'LFR': 3 +} + +export default Build; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/CreatExpandContainer.js b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/CreatExpandContainer.js new file mode 100644 index 000000000..702ea0057 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/CreatExpandContainer.js @@ -0,0 +1,11 @@ +import Sizer from '../../sizer/Sizer.js'; + +var CreatExpandContainer = function (scene, orientation) { + var container = new Sizer(scene, { + orientation: orientation + }) + scene.add.existing(container); + return container; +} + +export default CreatExpandContainer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/GetAddChildConfig.js b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/GetAddChildConfig.js new file mode 100644 index 000000000..a10843a1c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/GetAddChildConfig.js @@ -0,0 +1,70 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +var GetAddChildConfig = function (config, key, defaultValues) { + var proportion = GetValue(config, `proportion.${key}`, defaultValues.proportion); + var align = GetValue(config, `align.${key}`, 'center'); + var padding = GetValue(config, `space.${key}`, undefined); + if ((typeof (padding) === 'number') && defaultValues.paddingKey) { + var paddingNum = padding; + padding = {}; + padding[defaultValues.paddingKey] = paddingNum; + } + var expand = GetValue(config, `expand.${key}`, true); + + return { + proportion: proportion, + align: align, + padding: padding, + expand: expand, + } +} + +var GetAddHeaderConfig = function (config) { + return GetAddChildConfig(config, 'header', { + proportion: 0, + paddingKey: 'bottom' + }) +} + +var GetAddLeftSideConfig = function (config) { + return GetAddChildConfig(config, 'leftSide', { + proportion: 0, + paddingKey: 'right' + }) +} + +var GetAddContentConfig = function (config) { + return GetAddChildConfig(config, 'content', { + proportion: 1 + }) +} + +var GetAddRightSideConfig = function (config) { + return GetAddChildConfig(config, 'rightSide', { + proportion: 0, + paddingKey: 'left' + }) +} + +var GetAddFooterConfig = function (config) { + return GetAddChildConfig(config, 'footer', { + proportion: 0, + paddingKey: 'top' + }) +} + +var GetAddContainerConfig = function (config) { + return { + proportion: 1, + align: 'center', + padding: 0, + expand: true, + } +} + +export { + GetAddHeaderConfig, + GetAddLeftSideConfig, GetAddContentConfig, GetAddRightSideConfig, + GetAddFooterConfig, + GetAddContainerConfig +} diff --git a/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode0.js b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode0.js new file mode 100644 index 000000000..2a9698cbb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode0.js @@ -0,0 +1,58 @@ +/* +Elements: + ``` + HHH + LCR + FFF + ``` +*/ + +import { + GetAddHeaderConfig, + GetAddLeftSideConfig, GetAddContentConfig, GetAddRightSideConfig, + GetAddFooterConfig, + GetAddContainerConfig +} from './GetAddChildConfig.js'; +import CreatExpandContainer from './CreatExpandContainer.js'; + +var LayoutMode0 = function (config) { + var scene = this.scene; + + // Add Header + var header = config.header; + if (header) { + this.add(header, GetAddHeaderConfig(config)); + } + + /* + L C R + */ + var bodySizer = CreatExpandContainer(scene, 0); + this.add(bodySizer, GetAddContainerConfig(config)); + + // Add Left-side + var leftSide = config.leftSide; + if (leftSide) { + bodySizer.add(leftSide, GetAddLeftSideConfig(config)); + } + + // Add content + var content = config.content; + if (content) { + bodySizer.add(content, GetAddContentConfig(config)); + } + + // Add Right-side + var rightSide = config.rightSide; + if (rightSide) { + bodySizer.add(rightSide, GetAddRightSideConfig(config)); + } + + // Add Footer + var footer = config.footer; + if (footer) { + this.add(footer, GetAddFooterConfig(config)); + } +} + +export default LayoutMode0; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode1.js b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode1.js new file mode 100644 index 000000000..33954d6ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode1.js @@ -0,0 +1,73 @@ +/* +Elements: + ``` + HHH + LCR + LFF + ``` +*/ + +import { + GetAddHeaderConfig, + GetAddLeftSideConfig, GetAddContentConfig, GetAddRightSideConfig, + GetAddFooterConfig, + GetAddContainerConfig +} from './GetAddChildConfig.js'; +import CreatExpandContainer from './CreatExpandContainer.js'; + +var LayoutMode1 = function (config) { + var scene = this.scene; + + // Add Header + var header = config.header; + if (header) { + this.add(header, GetAddHeaderConfig(config)); + } + + /* + L CR + L FF + */ + var bodySizer0 = CreatExpandContainer(scene, 0); + this.add(bodySizer0, GetAddContainerConfig(config)); + + // Add Left-side + var leftSide = config.leftSide; + if (leftSide) { + bodySizer0.add(leftSide, GetAddLeftSideConfig(config)); + } + + /* + CR + + FF + */ + var bodySizer1 = CreatExpandContainer(scene, 1); + bodySizer0.add(bodySizer1, GetAddContainerConfig(config)); + + /* + C R + */ + var bodySizer2 = CreatExpandContainer(scene, 0); + bodySizer1.add(bodySizer2, GetAddContainerConfig(config)); + + // Add content + var content = config.content; + if (content) { + bodySizer2.add(content, GetAddContentConfig(config)); + } + + // Add Right-side + var rightSide = config.rightSide; + if (rightSide) { + bodySizer2.add(rightSide, GetAddRightSideConfig(config)); + } + + // Add Footer + var footer = config.footer; + if (footer) { + bodySizer1.add(footer, GetAddFooterConfig(config)); + } +} + +export default LayoutMode1; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode2.js b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode2.js new file mode 100644 index 000000000..05fbaf505 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode2.js @@ -0,0 +1,74 @@ +/* +Elements: + ``` + HHH + LCR + FFR + ``` +*/ + +import { + GetAddHeaderConfig, + GetAddLeftSideConfig, GetAddContentConfig, GetAddRightSideConfig, + GetAddFooterConfig, + GetAddContainerConfig +} from './GetAddChildConfig.js'; +import CreatExpandContainer from './CreatExpandContainer.js'; + +var LayoutMode2 = function (config) { + var scene = this.scene; + + // Add Header + var header = config.header; + if (header) { + this.add(header, GetAddHeaderConfig(config)); + } + + /* + LC R + FF R + */ + var bodySizer0 = CreatExpandContainer(scene, 0); + this.add(bodySizer0, GetAddContainerConfig(config)); + + /* + LC + + FF + */ + var bodySizer1 = CreatExpandContainer(scene, 1); + bodySizer0.add(bodySizer1, GetAddContainerConfig(config)); + + /* + L C + */ + var bodySizer2 = CreatExpandContainer(scene, 0); + bodySizer1.add(bodySizer2, GetAddContainerConfig(config)); + + // Add Left-side + var leftSide = config.leftSide; + if (leftSide) { + bodySizer2.add(leftSide, GetAddLeftSideConfig(config)); + } + + // Add content + var content = config.content; + if (content) { + bodySizer2.add(content, GetAddContentConfig(config)); + } + + // Add Footer + var footer = config.footer; + if (footer) { + bodySizer1.add(footer, GetAddFooterConfig(config)); + } + + // Add Right-side + var rightSide = config.rightSide; + if (rightSide) { + bodySizer0.add(rightSide, GetAddRightSideConfig(config)); + } + +} + +export default LayoutMode2; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode3.js b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode3.js new file mode 100644 index 000000000..d31a41f16 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode3.js @@ -0,0 +1,68 @@ +/* +Elements: + ``` + HHH + LCR + LFR + ``` +*/ + +import { + GetAddHeaderConfig, + GetAddLeftSideConfig, GetAddContentConfig, GetAddRightSideConfig, + GetAddFooterConfig, + GetAddContainerConfig +} from './GetAddChildConfig.js'; +import CreatExpandContainer from './CreatExpandContainer.js'; + +var LayoutMode0 = function (config) { + var scene = this.scene; + + // Add Header + var header = config.header; + if (header) { + this.add(header, GetAddHeaderConfig(config)); + } + + /* + L C R + L F R + */ + var bodySizer0 = CreatExpandContainer(scene, 0); + this.add(bodySizer0, GetAddContainerConfig(config)); + + // Add Left-side + var leftSide = config.leftSide; + if (leftSide) { + bodySizer0.add(leftSide, GetAddLeftSideConfig(config)); + } + + /* + C + + F + */ + var bodySizer1 = CreatExpandContainer(scene, 1); + bodySizer0.add(bodySizer1, GetAddContainerConfig(config)); + + // Add content + var content = config.content; + if (content) { + bodySizer1.add(content, GetAddContentConfig(config)); + } + + // Add Footer + var footer = config.footer; + if (footer) { + bodySizer1.add(footer, GetAddFooterConfig(config)); + } + + // Add Right-side + var rightSide = config.rightSide; + if (rightSide) { + bodySizer0.add(rightSide, GetAddRightSideConfig(config)); + } + +} + +export default LayoutMode0; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/imagebox/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/imagebox/Factory.d.ts new file mode 100644 index 000000000..c693cd4ea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/imagebox/Factory.d.ts @@ -0,0 +1,7 @@ +import ImageBox from './ImageBox'; + +export default function ( + x?: number, y?: number, + texture?: string, frame?: string, + config?: ImageBox.IConfig +): ImageBox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/imagebox/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/imagebox/Factory.js new file mode 100644 index 000000000..3ce469ff2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/imagebox/Factory.js @@ -0,0 +1,13 @@ +import ImageBox from './ImageBox.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('imageBox', function (x, y, texture, frame, config) { + var gameObject = new ImageBox(this.scene, x, y, texture, frame, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.ImageBox', ImageBox); + +export default ImageBox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/imagebox/ImageBox.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/imagebox/ImageBox.d.ts new file mode 100644 index 000000000..81266c1f4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/imagebox/ImageBox.d.ts @@ -0,0 +1,2 @@ +import ImageBox from '../../../plugins/imagebox'; +export default ImageBox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/imagebox/ImageBox.js b/ui/src/phaser3-rex-plugins/templates/ui/imagebox/ImageBox.js new file mode 100644 index 000000000..027c59e88 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/imagebox/ImageBox.js @@ -0,0 +1,2 @@ +import ImageBox from '../../../plugins/imagebox.js'; +export default ImageBox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/inputtext/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/inputtext/Factory.d.ts new file mode 100644 index 000000000..24c4f07c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/inputtext/Factory.d.ts @@ -0,0 +1,5 @@ +import InputText from './InputText.js'; + +export default function ( + config?: InputText.IConfig +): InputText; diff --git a/ui/src/phaser3-rex-plugins/templates/ui/inputtext/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/inputtext/Factory.js new file mode 100644 index 000000000..869c84586 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/inputtext/Factory.js @@ -0,0 +1,13 @@ +import InputText from './InputText.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('inputText', function (config) { + var gameObject = new InputText(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.InputText', InputText); + +export default InputText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/inputtext/InputText.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/inputtext/InputText.d.ts new file mode 100644 index 000000000..0312cf859 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/inputtext/InputText.d.ts @@ -0,0 +1,2 @@ +import InputText from '../../../plugins/inputtext'; +export default InputText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/inputtext/InputText.js b/ui/src/phaser3-rex-plugins/templates/ui/inputtext/InputText.js new file mode 100644 index 000000000..ffa341b71 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/inputtext/InputText.js @@ -0,0 +1,2 @@ +import InputText from '../../../plugins/inputtext.js'; +export default InputText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/intouching/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/intouching/Factory.d.ts new file mode 100644 index 000000000..b21791887 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/intouching/Factory.d.ts @@ -0,0 +1,7 @@ +// import * as Phaser from 'phaser'; +import InTouching from './InTouching'; + +export default function ( + gameObject: Phaser.GameObjects.GameObject, + config?: InTouching.IConfig +): InTouching; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/intouching/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/intouching/Factory.js new file mode 100644 index 000000000..adbe4aae6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/intouching/Factory.js @@ -0,0 +1,11 @@ +import InTouching from './InTouching.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('inTouching', function (gameObject, config) { + return new InTouching(gameObject, config); +}); + +SetValue(window, 'RexPlugins.UI.InTouching', InTouching); + +export default InTouching; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/intouching/InTouching.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/intouching/InTouching.d.ts new file mode 100644 index 000000000..7ed6b4e2f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/intouching/InTouching.d.ts @@ -0,0 +1,2 @@ +import InTouching from '../../../plugins/intouching' +export default InTouching; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/intouching/InTouching.js b/ui/src/phaser3-rex-plugins/templates/ui/intouching/InTouching.js new file mode 100644 index 000000000..7434aeebc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/intouching/InTouching.js @@ -0,0 +1,2 @@ +import InTouching from '../../../plugins/intouching.js' +export default InTouching; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/knob/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/knob/Factory.d.ts new file mode 100644 index 000000000..ebf230eb0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/knob/Factory.d.ts @@ -0,0 +1,5 @@ +import Knob from './Knob'; + +export default function ( + config?: Knob.IConfig +): Knob; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/knob/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/knob/Factory.js new file mode 100644 index 000000000..737bba643 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/knob/Factory.js @@ -0,0 +1,13 @@ +import Knob from './Knob.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('knob', function (config) { + var gameObject = new Knob(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.Knob', Knob); + +export default Knob; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/knob/Knob.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/knob/Knob.d.ts new file mode 100644 index 000000000..3ebdac9b0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/knob/Knob.d.ts @@ -0,0 +1,63 @@ +// import * as Phaser from 'phaser'; +import OverlapSizer from '../overlapsizer/OverlapSizer'; + + +export default Knob; + +declare namespace Knob { + + type InputTypes = 0 | 1 | -1 | 'drag' | 'pan' | 'click' | 'none'; + + interface IConfig extends OverlapSizer.IConfig { + background?: Phaser.GameObjects.GameObject, + + color?: number | string, + trackColor?: number | string, + centerColor?: number | string, + thickness?: number, + startAngle?: number, + anticlockwise?: boolean, + knobDepth?: number, + + text?: Phaser.GameObjects.GameObject, + textFormatCallback?: (value: number) => string, + textFormatCallbackScope?: object, + + input?: InputTypes, + + value?: number, + + gap?: number, + + easeValue?: { + duration?: number, + ease?: string + }, + + valuechangeCallback: (newValue: number, oldValue: number, knob: Knob) => void, + + enable?: boolean, + + } + +} + +declare class Knob extends OverlapSizer { + constructor( + scene: Phaser.Scene, + config?: Knob.IConfig + ); + + value: number; + getValue(min?: number, max?: number): number; + setValue(value?: number, min?: number, max?: number): this; + addValue(inc?: number, min?: number, max?: number): this; + + easeValueTo(value?: number, min?: number, max?: number): this; + stopEaseValue(): this; + setEaseValueDuration(duration: number): this; + setEaseValueFunction(ease: string): this; + + setEnable(enable?: boolean): this; + enable: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/knob/Knob.js b/ui/src/phaser3-rex-plugins/templates/ui/knob/Knob.js new file mode 100644 index 000000000..5bafb6597 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/knob/Knob.js @@ -0,0 +1,123 @@ +import OverlapSizer from '../overlapsizer/OverlapSizer.js'; +import ProgressBase from '../../../plugins/utils/progressbase/ProgressBase.js'; +import CircularProgress from '../circularprogress/CircularProgress.js'; +import InstallTouchPadEvents from './input/OnTouchPad.js'; +import InstallPanPadEvents from './input/OnPanPad.js'; +import TextObjectMethods from './TextObjectMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const SnapTo = Phaser.Math.Snap.To; + +class Knob extends ProgressBase(OverlapSizer) { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + // Create sizer + super(scene, config); + this.type = 'rexKnob'; + + this.bootProgressBase(config); + + // Add elements + var background = GetValue(config, 'background', undefined); + var textObject = GetValue(config, 'text', undefined); + + if (background) { + this.addBackground(background); + } + // Get text object + if (textObject) { + // Don't draw text on knob directly + config.textColor = undefined; + config.textStrokeColor = undefined; + this.setTextFormatCallback( + GetValue(config, 'textFormatCallback', undefined), + GetValue(config, 'textFormatCallbackScope', undefined) + ); + } + // Create circular progress object + var knob = new CircularProgress(scene, config); + knob.setDepth(GetValue(config, 'knobDepth', 0)); + knob._value = -1; // To trigger text updating + scene.add.existing(knob); + + this.add(knob, 'knob'); + if (textObject) { + this.add(textObject, 'text', 'center', 0, false); + scene.children.moveBelow(knob, textObject); // Move knob below textObject + } + + this.addChildrenMap('background', background); + this.addChildrenMap('knob', knob); + this.addChildrenMap('text', textObject); + + this.setEnable(GetValue(config, 'enable', undefined)); + + this.setGap(GetValue(config, 'gap', undefined)); + this.setValue(GetValue(config, 'value', 0), GetValue(config, 'min', undefined), GetValue(config, 'max', undefined)); + + // Input + var inputMode = GetValue(config, 'input', 0); + if (typeof (inputMode) === 'string') { + inputMode = INPUTMODE[inputMode]; + } + switch (inputMode) { + case 0: // 'pan' + InstallPanPadEvents.call(this); + break; + case 1: // 'click' + InstallTouchPadEvents.call(this); + break; + } + } + + setEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.enable = enable; + return this; + } + + setGap(gap) { + this.gap = gap; + return this; + } + + // Override + get value() { + return this.sizerChildren.knob.value; + } + + // Override + set value(value) { + if (this.gap !== undefined) { + value = SnapTo(value, this.gap); + } + var oldValue = this.value; + this.sizerChildren.knob.value = value; + + var newValue = this.value; + if (oldValue !== newValue) { + this.updateText(); + this.eventEmitter.emit('valuechange', newValue, oldValue, this.eventEmitter); + } + } + +} + +const INPUTMODE = { + pan: 0, + drag: 0, + click: 1, + none: -1, +} + +Object.assign( + Knob.prototype, + TextObjectMethods, +); + +export default Knob; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/knob/TextObjectMethods.js b/ui/src/phaser3-rex-plugins/templates/ui/knob/TextObjectMethods.js new file mode 100644 index 000000000..b1638cb02 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/knob/TextObjectMethods.js @@ -0,0 +1,36 @@ +var SetTextFormatCallback = function (callback, scope) { + this.textFormatCallback = callback; + this.textFormatCallbackScope = scope; + return this; +} + +var GetFormatText = function (value) { + if (value === undefined) { + value = this.value; + } + + var text; + if (this.textFormatCallbackScope) { + text = this.textFormatCallback(value); + } else { + text = this.textFormatCallback.call(this.textFormatCallbackScope, value); + } + return text; +} + +var UpdateText = function (value) { + var textObject = this.sizerChildren.text; + if (textObject && this.textFormatCallback) { + textObject.setText(GetFormatText.call(this, value)); + if (textObject.layout) { + textObject.layout(); + } + } + return this; +} + +export default { + setTextFormatCallback: SetTextFormatCallback, + getFormatText: GetFormatText, + updateText: UpdateText +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/knob/input/IsLocalPointInKnob.js b/ui/src/phaser3-rex-plugins/templates/ui/knob/input/IsLocalPointInKnob.js new file mode 100644 index 000000000..8ae388e26 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/knob/input/IsLocalPointInKnob.js @@ -0,0 +1,8 @@ +var GetDistance = Phaser.Math.Distance.Between; + +var IsLocalPointInKnob = function (knob, localX, localY) { + var centerX = knob.width / 2; + return GetDistance(centerX, centerX, localX, localY) <= centerX; +} + +export default IsLocalPointInKnob; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/knob/input/OnPanPad.js b/ui/src/phaser3-rex-plugins/templates/ui/knob/input/OnPanPad.js new file mode 100644 index 000000000..801c85df2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/knob/input/OnPanPad.js @@ -0,0 +1,90 @@ +import IsLocalPointInKnob from './IsLocalPointInKnob.js'; + +const GetAngle = Phaser.Math.Angle.Between; +const WrapAngle = Phaser.Math.Angle.Wrap; + +var OnPointerDown = function (pointer, localX, localY) { + if ((!this.enable) || (this.panPointer)) { + return; + } + var knob = this.sizerChildren.knob; + if (!IsLocalPointInKnob(knob, localX, localY)) { + return; + } + + OnPanStart.call(this, pointer); +} + +var OnPointerMove = function (pointer, localX, localY) { + if (!this.enable) { + return; + } + if (!pointer.isDown) { + return; + } + + var knob = this.sizerChildren.knob; + switch (this.panState) { + case TOUCH0: + if (IsLocalPointInKnob(knob, localX, localY)) { + OnPanStart.call(this, pointer); + } + break; + + case TOUCH1: + if (IsLocalPointInKnob(knob, localX, localY)) { + OnPan.call(this); + } else { + OnPanEnd.call(this); + } + break; + } +} + +var OnPointerUp = function (pointer, localX, localY) { + if ((!this.enable) || (this.panPointer !== pointer)) { + return; + } + + OnPanEnd.call(this); +} + +var OnPanStart = function (pointer) { + this.panPointer = pointer; + this.panState = TOUCH1; +} + +var OnPanEnd = function () { + this.panPointer = undefined; + this.panState = TOUCH0; +} + +var OnPan = function () { + var p0 = this.panPointer.prevPosition, + p1 = this.panPointer.position; + var knob = this.sizerChildren.knob; + var startAngle = GetAngle(knob.x, knob.y, p0.x, p0.y), + endAngle = GetAngle(knob.x, knob.y, p1.x, p1.y); + var deltaAngle = (knob.anticlockwise) ? (startAngle - endAngle) : (endAngle - startAngle); + var deltaValue = WrapAngle(deltaAngle) / (Math.PI * 2); + + this.stopEaseValue(); + this.value += deltaValue; +} + +const TOUCH0 = 0; +const TOUCH1 = 1; + +var InstallEvents = function () { + var knob = this.sizerChildren.knob; + knob + .on('pointerdown', OnPointerDown, this) + .on('pointermove', OnPointerMove, this) + .on('pointerup', OnPointerUp, this) + .setInteractive() + + this.panPointer = undefined; + this.panState = TOUCH0; +} + +export default InstallEvents; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/knob/input/OnTouchPad.js b/ui/src/phaser3-rex-plugins/templates/ui/knob/input/OnTouchPad.js new file mode 100644 index 000000000..08ca2c2c5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/knob/input/OnTouchPad.js @@ -0,0 +1,40 @@ +import IsLocalPointInKnob from './IsLocalPointInKnob.js'; + +const GetAngle = Phaser.Math.Angle.Between; +const NormalizeAngle = Phaser.Math.Angle.Normalize; + +var OnTouchPad = function (pointer, localX, localY) { + if (!this.enable) { + return; + } + if (!pointer.isDown) { + return; + } + var knob = this.sizerChildren.knob; + if (!IsLocalPointInKnob(knob, localX, localY)) { + return; + } + + var centerX = knob.width / 2; + var startAngle = knob.startAngle; + var endAngle = GetAngle(centerX, centerX, localX, localY); + var deltaAngle = (knob.anticlockwise) ? (startAngle - endAngle) : (endAngle - startAngle); + var value = NormalizeAngle(deltaAngle) / (2 * Math.PI); + + this.stopEaseValue(); + if ((this.easeValueDuration === 0) || (Math.abs(this.value - value) < 0.1)) { + this.value = value; + } else { + this.easeValueTo(value); + } +} + +var InstallEvents = function () { + var knob = this.sizerChildren.knob; + knob + .on('pointerdown', OnTouchPad, this) + .on('pointermove', OnTouchPad, this) + .setInteractive() +} + +export default InstallEvents; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/label/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/label/Factory.d.ts new file mode 100644 index 000000000..0692cd995 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/label/Factory.d.ts @@ -0,0 +1,5 @@ +import Label from './Label'; + +export default function ( + config?: Label.IConfig +): Label; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/label/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/label/Factory.js new file mode 100644 index 000000000..0ebb6962e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/label/Factory.js @@ -0,0 +1,13 @@ +import Label from './Label.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('label', function (config) { + var gameObject = new Label(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.Label', Label); + +export default Label; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/label/Label.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/label/Label.d.ts new file mode 100644 index 000000000..452fab313 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/label/Label.d.ts @@ -0,0 +1,100 @@ +// import * as Phaser from 'phaser'; +import Sizer from '../sizer/Sizer'; + +export default Label; + +declare namespace Label { + + type AlignTypes = 'left' | 'top' | 'right' | 'bottom' | 'center'; + + interface IConfig extends Sizer.IConfig { + space?: { + left?: number, right?: number, top?: number, bottom?: number, + + icon?: number, + text?: number, + }, + + background?: Phaser.GameObjects.GameObject, + + icon?: Phaser.GameObjects.GameObject, + iconMask?: boolean, + squareFitIcon?: boolean, + iconSize?: number, iconWidth?: number, iconHeight?: number, + + text?: Phaser.GameObjects.GameObject, + expandTextWidth?: boolean, + expandTextHeight?: boolean, + + action?: Phaser.GameObjects.GameObject, + squareFitAction?: boolean, + actionMask?: boolean, + actionSize?: number, actionWidth?: number, actionHeight?: number, + + align?: AlignTypes, + } + + interface IResetDisplayContentConfig { + text?: string, + + icon?: string | Phaser.Textures.Texture, + iconFrame?: string | number, + iconSize?: number, + + action?: string | Phaser.Textures.Texture, + actionFrame?: string | number, + actionSize?: number, + } +} + +declare class Label extends Sizer { + constructor( + scene: Phaser.Scene, + config?: Label.IConfig + ); + + text: string; + setText(text: string): this; + appendText( + text: string | number | string[], + addCR?: boolean + ): this; + + setTexture( + key: string | Phaser.Textures.Texture, + frame?: string | number + ): this; + readonly texture: Phaser.Textures.Texture | Phaser.Textures.CanvasTexture; + readonly frame: Phaser.Textures.Frame; + + setIconTexture( + key: string | Phaser.Textures.Texture, + frame?: string | number + ): this; + + setIconSize( + width?: number, + height?: number + ): this; + iconWidth: number; + iconHeight: number; + + setActionTexture( + key: string | Phaser.Textures.Texture, + frame?: string | number + ): this; + readonly actionTexture: Phaser.Textures.Texture | Phaser.Textures.CanvasTexture; + readonly actionFrame: Phaser.Textures.Frame; + + setActionSize( + width?: number, + height?: number + ): this; + actionWidth: number; + actionHeight: number; + + resetDisplayContent( + config?: string | Label.IResetDisplayContentConfig + ): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/label/Label.js b/ui/src/phaser3-rex-plugins/templates/ui/label/Label.js new file mode 100644 index 000000000..7fd126d77 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/label/Label.js @@ -0,0 +1,297 @@ +import Sizer from '../sizer/Sizer.js'; +import AddChildMask from '../../../plugins/gameobjects/container/containerlite/mask/AddChildMask.js'; +import SetDisplaySize from '../../../plugins/utils/size/SetDisplaySize.js'; +import Methods from './methods/Methods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Label extends Sizer { + constructor(scene, config) { + // Create sizer + super(scene, config); + this.type = 'rexLabel'; + + // Add elements + var background = GetValue(config, 'background', undefined); + var icon = GetValue(config, 'icon', undefined); + var iconMask = GetValue(config, 'iconMask', undefined); + var text = GetValue(config, 'text', undefined); + var action = GetValue(config, 'action', undefined); + var actionMask = GetValue(config, 'actionMask', undefined); + // Align + var align = GetValue(config, 'align', undefined); // undefined/left/top: no space + + + if (background) { + this.addBackground(background); + } + + // Add space + if ( + (align === 'right') || + (align === 'bottom') || + (align === 'center') + ) { + this.addSpace(); + } + + if (icon) { + var iconSpace = GetValue(config, 'space.icon', 0); + var padding; + if (this.orientation === 0) { + if (text || action) { + padding = { right: iconSpace }; + } + } else { + if (text || action) { + padding = { bottom: iconSpace }; + } + } + var fitRatio = GetValue(config, 'squareFitIcon', false) ? 1 : 0; + + this.add( + icon, + { proportion: 0, padding: padding, fitRatio: fitRatio } + ); + + if (iconMask) { + iconMask = AddChildMask.call(this, icon, icon, 1); // Circle mask + } + + if (!fitRatio) { + var iconSize = GetValue(config, 'iconSize', undefined); + this.setIconSize( + GetValue(config, 'iconWidth', iconSize), + GetValue(config, 'iconHeight', iconSize) + ); + } + } + + + if (text) { + var textSpace = GetValue(config, 'space.text', 0); + var expandTextWidth = GetValue(config, 'expandTextWidth', false); + var expandTextHeight = GetValue(config, 'expandTextHeight', false); + var proportion, padding, expand; + if (this.orientation === 0) { + proportion = (expandTextWidth) ? 1 : 0; + if (action) { + padding = { right: textSpace }; + } + expand = expandTextHeight; + } else { + proportion = (expandTextHeight) ? 1 : 0; + if (action) { + padding = { bottom: textSpace }; + } + expand = expandTextWidth; + } + + this.add( + text, + { proportion: proportion, expand: expand, padding: padding, } + ); + } + + if (action) { + var fitRatio = GetValue(config, 'squareFitAction', false) ? 1 : 0; + this.add( + action, + { proportion: 0, fitRatio: fitRatio } + ); + + if (actionMask) { + actionMask = AddChildMask.call(this, action, action, 1); // Circle mask + } + + if (!fitRatio) { + var actionSize = GetValue(config, 'actionSize'); + this.setActionSize( + GetValue(config, 'actionWidth', actionSize), + GetValue(config, 'actionHeight', actionSize) + ); + } + } + + // Add space + if (align === 'center') { + this.addSpace(); + } + + this.addChildrenMap('background', background); + this.addChildrenMap('icon', icon); + this.addChildrenMap('iconMask', iconMask); + this.addChildrenMap('text', text); + this.addChildrenMap('action', action); + this.addChildrenMap('actionMask', actionMask); + } + + // Access text game object + get text() { + var textObject = this.childrenMap.text; + if (textObject === undefined) { + return ''; + } + return textObject.text; + } + + set text(value) { + var textObject = this.childrenMap.text; + if (textObject === undefined) { + return; + } + textObject.setText(value); + } + + setText(value) { + this.text = value; + return this; + } + + // Access icon game object + setIconTexture(key, frame) { + var imageObject = this.childrenMap.icon; + if (imageObject === undefined) { + return this; + } + imageObject.setTexture(key, frame); + + if (this.iconWidth !== undefined) { + SetDisplaySize(imageObject, this.iconWidth, this.iconHeight); + this.resetChildScaleState(imageObject); + } + + return this; + } + + setTexture(key, frame) { + this.setIconTexture(key, frame); + return this; + } + + setIconSize(width, height) { + if (height === undefined) { + height = width; + } + + this.iconWidth = width; + this.iconHeight = height; + + return this; + } + + get texture() { + var imageObject = this.childrenMap.icon; + if (imageObject === undefined) { + return undefined; + } + return imageObject.texture; + } + + get frame() { + var imageObject = this.childrenMap.icon; + if (imageObject === undefined) { + return undefined; + } + return imageObject.frame; + } + + setActionTexture(key, frame) { + var imageObject = this.childrenMap.action; + if (imageObject === undefined) { + return this; + } + imageObject.setTexture(key, frame); + + if (this.actionWidth !== undefined) { + SetDisplaySize(imageObject, this.actionWidth, this.actionHeight); + this.resetChildScaleState(imageObject); + } + + return this; + } + + get actionTexture() { + var imageObject = this.childrenMap.action; + if (imageObject === undefined) { + return undefined; + } + return imageObject.texture; + } + + get actionFrame() { + var imageObject = this.childrenMap.action; + if (imageObject === undefined) { + return undefined; + } + return imageObject.frame; + } + + setActionSize(width, height) { + if (height === undefined) { + height = width; + } + + this.actionWidth = width; + this.actionHeight = height; + + return this; + } + + preLayout() { + var icon = this.childrenMap.icon; + if (icon && (this.iconWidth !== undefined)) { + SetDisplaySize(icon, this.iconWidth, this.iconHeight); + } + + var action = this.childrenMap.action; + if (action && (this.actionWidth !== undefined)) { + SetDisplaySize(action, this.actionWidth, this.actionHeight); + } + + super.preLayout(); + } + + runLayout(parent, newWidth, newHeight) { + if (this.ignoreLayout) { + return this; + } + + super.runLayout(parent, newWidth, newHeight); + // Pin icon-mask to icon game object + var iconMask = this.childrenMap.iconMask; + if (iconMask) { + iconMask.setPosition(); + this.resetChildPositionState(iconMask); + } + // Pin action-mask to action game object + var actionMask = this.childrenMap.actionMask; + if (actionMask) { + actionMask.setPosition(); + this.resetChildPositionState(actionMask); + } + return this; + } + + resize(width, height) { + super.resize(width, height); + // Resize icon-mask to icon game object + var iconMask = this.childrenMap.iconMask; + if (iconMask) { + iconMask.resize(); + } + // Resize action-mask to icon game object + var actionMask = this.childrenMap.actionMask; + if (actionMask) { + actionMask.resize(); + } + return this; + } +} + +Object.assign( + Label.prototype, + Methods, +) + +export default Label; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/label/methods/Methods.js b/ui/src/phaser3-rex-plugins/templates/ui/label/methods/Methods.js new file mode 100644 index 000000000..01c7cbb7a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/label/methods/Methods.js @@ -0,0 +1,9 @@ +import AppendText from '../../../../plugins/utils/text/AppendText.js'; +import ResetDisplayContent from './ResetDisplayContent.js'; + +var methods = { + appendText: AppendText, + resetDisplayContent: ResetDisplayContent, +} + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/label/methods/ResetDisplayContent.js b/ui/src/phaser3-rex-plugins/templates/ui/label/methods/ResetDisplayContent.js new file mode 100644 index 000000000..2dd9719ad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/label/methods/ResetDisplayContent.js @@ -0,0 +1,53 @@ +var ResetDisplayContent = function (config) { + if (config === undefined) { + config = {}; + } else if (typeof (config) === 'string') { + config = { + text: config, + } + } + + var text = config.text || ''; + this.setText(text); + + var iconGameObjct = this.childrenMap.icon; + if (iconGameObjct) { + if (config.icon === undefined) { + this.hide(iconGameObjct); + } else { + this.show(iconGameObjct); + } + var iconSize = config.iconSize; + if (iconSize) { + this.setChildDisplaySize(iconGameObjct, iconSize, iconSize); + + if (this.iconWidth !== undefined) { + this.setIconSize(iconSize); + } + } + this.setIconTexture(config.icon, config.iconFrame); + } + + var actionGameObjct = this.childrenMap.action; + if (actionGameObjct) { + if (config.action === undefined) { + this.hide(actionGameObjct); + } else { + this.show(actionGameObjct); + } + var actionSize = config.actionSize; + if (actionSize) { + this.setChildDisplaySize(actionGameObjct, actionSize, actionSize); + + if (this.actionWidth !== undefined) { + this.setActionSize(actionSize); + } + + } + this.setActionTexture(config.action, config.actionFrame); + } + + return this; +} + +export default ResetDisplayContent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/lineprogress/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/lineprogress/Factory.d.ts new file mode 100644 index 000000000..cca4b2dbf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/lineprogress/Factory.d.ts @@ -0,0 +1,13 @@ +import LineProgress from './LineProgress'; + +export default function ( + config?: LineProgress.IConfig +): LineProgress; + +export default function ( + x?: number, y?: number, + radius?: number, + barColor?: string | number, + value?: number, + config?: LineProgress.IConfig +): LineProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/lineprogress/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/lineprogress/Factory.js new file mode 100644 index 000000000..5657fb9d2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/lineprogress/Factory.js @@ -0,0 +1,13 @@ +import LineProgress from './LineProgress.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('lineProgress', function (x, y, width, height, barColor, value, config) { + var gameObject = new LineProgress(this.scene, x, y, width, height, barColor, value, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.LineProgress', LineProgress); + +export default LineProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/lineprogress/LineProgress.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/lineprogress/LineProgress.d.ts new file mode 100644 index 000000000..84c06a0fe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/lineprogress/LineProgress.d.ts @@ -0,0 +1,2 @@ +import LineProgress from '../../../plugins/lineprogress'; +export default LineProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/lineprogress/LineProgress.js b/ui/src/phaser3-rex-plugins/templates/ui/lineprogress/LineProgress.js new file mode 100644 index 000000000..f1c34031d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/lineprogress/LineProgress.js @@ -0,0 +1,2 @@ +import LineProgress from '../../../plugins/lineprogress.js'; +export default LineProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/Factory.d.ts new file mode 100644 index 000000000..aaf8ac9b9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/Factory.d.ts @@ -0,0 +1,19 @@ +import LineProgressCanvas from './LineProgressCanvas'; + +export default function ( + config?: LineProgressCanvas.IConfig +): LineProgressCanvas; + +export default function ( + x?: number, y?: number, + width?: number, height?: number, + config?: LineProgressCanvas.IConfig +): LineProgressCanvas; + +export default function ( + x?: number, y?: number, + width?: number, height?: number, + barColor?: string | number, + value?: number, + config?: LineProgressCanvas.IConfig +): LineProgressCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/Factory.js new file mode 100644 index 000000000..e182045fa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/Factory.js @@ -0,0 +1,13 @@ +import LineProgressCanvas from './LineProgressCanvas.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('circularProgressCanvas', function (x, y, width, height, barColor, value, config) { + var gameObject = new LineProgressCanvas(this.scene, x, y, width, height, barColor, value, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.UI.LineProgressCanvas', LineProgressCanvas); + +export default LineProgressCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/LineProgressCanvas.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/LineProgressCanvas.d.ts new file mode 100644 index 000000000..7d855d01a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/LineProgressCanvas.d.ts @@ -0,0 +1,2 @@ +import LineProgressCanvas from "../../../plugins/lineprogresscanvas"; +export default LineProgressCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/LineProgressCanvas.js b/ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/LineProgressCanvas.js new file mode 100644 index 000000000..66268530a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/LineProgressCanvas.js @@ -0,0 +1,2 @@ +import LineProgressCanvas from '../../../plugins/lineprogresscanvas.js'; +export default LineProgressCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/maker/Factory.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/maker/Factory.d.ts new file mode 100644 index 000000000..a4c82c771 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/maker/Factory.d.ts @@ -0,0 +1,6 @@ +import Maker from './Maker'; + +export default function ( + styles?: Object | string, + customBuilders?: Maker.BuildersType +): Maker; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/maker/Factory.js b/ui/src/phaser3-rex-plugins/templates/ui/maker/Factory.js new file mode 100644 index 000000000..5be2f18eb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/maker/Factory.js @@ -0,0 +1,11 @@ +import Maker from './Maker.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../plugins/utils/object/SetValue.js'; + +ObjectFactory.register('maker', function (styles, customBuilders) { + return new Maker(this.scene, styles, customBuilders); +}); + +SetValue(window, 'RexPlugins.UI.Maker', Maker); + +export default Maker; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/maker/Make.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/maker/Make.d.ts new file mode 100644 index 000000000..6c3c9a72a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/maker/Make.d.ts @@ -0,0 +1,15 @@ +import Builders from './builders/Builders'; +export default Make; + +declare namespace Make { + type BuilderType = Builders.BuilderType; + type BuildersType = { [name: string]: BuilderType } +} + +declare function Make( + scene: Phaser.Scene, + data: Object, + view?: Object, + styles?: Object, + customBuilders?: Make.BuildersType +): Phaser.GameObjects.GameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/maker/Make.js b/ui/src/phaser3-rex-plugins/templates/ui/maker/Make.js new file mode 100644 index 000000000..8ec075092 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/maker/Make.js @@ -0,0 +1,31 @@ +import GetTypeName from './builders/utils/GetTypeName.js'; +import Builders from './builders/Builders.js'; + +var Make = function (scene, data, view, styles, customBuilders) { + var type = GetTypeName(data, styles); + if (!type) { + console.warn(`rexUI.Make: Can't get type name in ${JSON.stringify(data)}`) + return undefined; + } + + var callback; + if (customBuilders) { + callback = customBuilders[type] + } + if (!callback) { + callback = Builders[type]; + } + if (!callback) { + console.warn(`rexUI.Make: Can't create ${type} game object.`) + return undefined; + } + + var gameObject = callback(scene, data, view, styles, customBuilders); + if (data.name) { + gameObject.setName(data.name); + } + + return gameObject; +} + +export default Make; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/maker/Maker.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/maker/Maker.d.ts new file mode 100644 index 000000000..02616fd56 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/maker/Maker.d.ts @@ -0,0 +1,35 @@ +import Make from './Make'; + +export default Maker; + +declare namespace Maker { + type BuilderType = Make.BuilderType; + type BuildersType = Make.BuildersType; +} + +declare class Maker { + constructor( + scene: Phaser.Scene, + styles?: Object | string, + customBuilders?: Maker.BuildersType + ); + + setScene(scene: Phaser.Scene): this; + scene: Phaser.Scene; + + setStyles(styles?: Object | string): this; + addStyle(key: string, style: Object | string): this; + addStyle(styles: Object | string): this; + clearStyles(): this; + styles: Object | undefined; + + setBuilders(builders?: Maker.BuildersType): this; + addBuilder(key: string, builder: Maker.BuilderType): this; + clearBuilder(): this; + customBuilders: Maker.BuildersType | undefined; + + make( + data: Object | string, + view?: Object | string + ): Phaser.GameObjects.GameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/maker/Maker.js b/ui/src/phaser3-rex-plugins/templates/ui/maker/Maker.js new file mode 100644 index 000000000..ee17a0ee5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/maker/Maker.js @@ -0,0 +1,80 @@ +import ParseYAML from './utils/ParseYAML.js'; +import YAMLMake from './YAMLMake.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +class Maker { + constructor(scene, styles, customBuilders) { + this.setScene(scene); + this.setStyles(styles); + this.setBuilders(customBuilders); + } + + setScene(scene) { + this.scene = scene; + return this; + } + + setStyles(styles) { + this.styles = ParseYAML(styles); + return this; + } + + addStyle(key, style) { + if (this.styles === undefined) { + this.styles = {}; + } + + if ((typeof (key) === 'string') && (style === undefined)) { + key = ParseYAML(key); + } + + if (IsPlainObject(key)) { + var styles = key; + for (key in styles) { + this.styles[key] = styles[key]; + } + } else { + this.styles[key] = ParseYAML(style); + } + + return this; + } + + clearStyles() { + this.setStyles(); + return this; + } + + setBuilders(customBuilders) { + this.customBuilders = customBuilders; + return this; + } + + addBuilder(key, customBuilder) { + if (this.customBuilders === undefined) { + this.customBuilders = {}; + } + + if (IsPlainObject(key)) { + var customBuilders = key; + for (key in customBuilders) { + this.customBuilders[key] = customBuilders[key]; + } + } else { + this.customBuilders[key] = customBuilder; + } + return this; + } + + clearBuilder() { + this.setBuilders(); + return this; + } + + make(data, view) { + return YAMLMake(this.scene, data, view, this.styles, this.customBuilders); + } +} + +export default Maker; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/maker/YAMLMake.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/maker/YAMLMake.d.ts new file mode 100644 index 000000000..e2b225621 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/maker/YAMLMake.d.ts @@ -0,0 +1,15 @@ +import Builders from './builders/Builders'; +export default YAMLMake; + +declare namespace YAMLMake { + type BuilderType = Builders.BuilderType; + type BuildersType = { [name: string]: BuilderType } +} + +declare function YAMLMake( + scene: Phaser.Scene, + data: Object | string, + view?: Object | string, + styles?: Object | string, + customBuilders?: YAMLMake.BuildersType +): Phaser.GameObjects.GameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/maker/YAMLMake.js b/ui/src/phaser3-rex-plugins/templates/ui/maker/YAMLMake.js new file mode 100644 index 000000000..211c15288 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/maker/YAMLMake.js @@ -0,0 +1,35 @@ +import ParseYAML from './utils/ParseYAML.js'; +import Make from './Make.js'; + +var YAMLMake = function (scene, data, view, styles, customBuilders) { + data = ParseYAML(data); + if (Array.isArray(data)) { + // Parsing result of YAML data might be an array, + // Only last item will be used to create game object, others are references + data = data[data.length - 1]; + } else if (data.$root) { + // Parsing result of YAML data might be an object, with $root key, + // data.$root will be used to create game object, others are default styles + var defaultStyles = data; + data = data.$root; + delete defaultStyles.$root; + + if (styles === undefined) { + styles = defaultStyles; + } else { + for (var key in defaultStyles) { + if (!styles[key]) { + styles[key] = defaultStyles[key]; + } + } + } + } + + styles = ParseYAML(styles); + + var gameObject = Make(scene, data, view, styles, customBuilders); + + return gameObject; +} + +export default YAMLMake; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/templates/ui/maker/builders/Builders.d.ts b/ui/src/phaser3-rex-plugins/templates/ui/maker/builders/Builders.d.ts new file mode 100644 index 000000000..106008370 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/templates/ui/maker/builders/Builders.d.ts @@ -0,0 +1,82 @@ +import BBCodeText from '../../bbcodetext/BBCodeText'; +import RoundRectangle from '../../roundrectangle/RoundRectangle'; +import NinePatch from '../../ninepatch/NinePatch'; +import NinePatch2 from '../../ninepatch2/NinePatch'; +import Canvas from '../../canvas/Canvas'; +import CircleMaskImage from '../../circlemaskimage/CircleMaskImage'; +import Space from '../../space/Space'; + +import Sizer from '../../sizer/Sizer'; +import FixWidthSizer from '../../fixwidthsizer/FixWidthSizer'; +import GridSizer from '../../gridsizer/GridSizer'; +import OverlapSizer from '../../overlapsizer/OverlapSizer'; + +import Buttons from '../../buttons/Buttons'; +import FixWidthButtons from '../../fixwidthbuttons/FixWidthButtons'; +import GridButtons from '../../gridbuttons/GridButtons'; + +import Label from '../../label/Label'; +import BadgeLabel from '../../badgelabel/BadgeLabel'; +import Dialog from '../../dialog/Dialog'; +import TextBox from '../../textbox/TextBox'; +import Slider from '../../slider/Slider'; +import NumberBar from '../../numberbar/NumberBar'; +import ScrollBar from '../../scrollbar/ScrollBar'; +import TextArea from '../../textarea/TextArea'; +import Pages from '../../pages/Pages'; +import Toast from '../../toast/Toast'; +import Knob from '../../knob/Knob'; +import HolyGrail from '../../holygrail/HolyGrail'; +import Menu from '../../menu/Menu'; + +export default Builders; + +declare namespace Builders { + type BuilderTypeCommon = ( + scene: Phaser.Scene, + data: Object, + view: Object, + styles: Object, + customBuilders: { [name: string]: BuilderType } + ) => T; + + type BuilderType = BuilderTypeCommon; +} + +declare var Builders: { + Image: Builders.BuilderTypeCommon, + Sprite: Builders.BuilderTypeCommon, + Video: Builders.BuilderTypeCommon, + Text: Builders.BuilderTypeCommon, + BBCodeText: Builders.BuilderTypeCommon, + RoundRectangle: Builders.BuilderTypeCommon, + Ninepatch: Builders.BuilderTypeCommon, + Ninepatch2: Builders.BuilderTypeCommon, + Canvas: Builders.BuilderTypeCommon, + CircleMaskImage: Builders.BuilderTypeCommon, + Space: Builders.BuilderTypeCommon, + + Sizer: Builders.BuilderTypeCommon, + FixWidthSizer: Builders.BuilderTypeCommon, + GridSizer: Builders.BuilderTypeCommon, + OverlapSizer: Builders.BuilderTypeCommon, + + Buttons: Builders.BuilderTypeCommon, + FixWidthButtons: Builders.BuilderTypeCommon, + GridButtons: Builders.BuilderTypeCommon, + + Label: Builders.BuilderTypeCommon

    D(8B!in{F2f6ldDFhj(X55X6oGKJ!gln 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 JA1!7+`u{u@;gbLW literal 0 HcmV?d00001 diff --git a/imgs/prison/demo.psd b/imgs/prison/demo.psd new file mode 100644 index 0000000000000000000000000000000000000000..33d29e55e22be092910bd3ec01a8ed09ee0a5757 GIT binary patch literal 1230586 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%0DxDgx}Q= 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 literal 0 HcmV?d00001 diff --git a/imgs/prison/police.png b/imgs/prison/police.png new file mode 100755 index 0000000000000000000000000000000000000000..c34471ecb707e2a4c3045115865f7032e5000d33 GIT binary patch literal 3340 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 Wcw9ALy8cZ70000&i#pf)W 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 OnEh(GJ;V42{(k^FN+sg} literal 0 HcmV?d00001 diff --git a/imgs/prison/refer.png b/imgs/prison/refer.png new file mode 100644 index 0000000000000000000000000000000000000000..1e426d82b1e86b21b0860ff56eab8d28d3b1dee3 GIT binary patch literal 18920 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 literal 0 HcmV?d00001 From e903aa025936e72ab5a0074a9edc99a7059bf20f Mon Sep 17 00:00:00 2001 From: dalabengba Date: Thu, 18 May 2023 22:01:43 +0800 Subject: [PATCH 25/50] config.yaml --- .../environments/rules/order/prisoner.py | 2 +- .../environments/rules/visibility/prisoner.py | 2 +- agentverse/tasks/prisoner_dilema/config.yaml | 47 ++++++++++--------- .../tasks/prisoner_dilema/output_parser.py | 11 ++++- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/agentverse/environments/rules/order/prisoner.py b/agentverse/environments/rules/order/prisoner.py index 6fc42f5c3..fa49f8bea 100644 --- a/agentverse/environments/rules/order/prisoner.py +++ b/agentverse/environments/rules/order/prisoner.py @@ -40,7 +40,7 @@ def get_next_agent_idx(self, environment: BaseEnvironment) -> List[int]: next_prisoner = self.last_prisoner_index self.last_prisoner_index = self.switch_func[self.last_prisoner_index] return [next_prisoner] - elif sender.startswith("Prisoner"): + elif sender.startswith("Suspect"): # 3. when one prisoner made his action, let the police tell another prisoner return [0] else: diff --git a/agentverse/environments/rules/visibility/prisoner.py b/agentverse/environments/rules/visibility/prisoner.py index 948b9c135..3e0a81aeb 100644 --- a/agentverse/environments/rules/visibility/prisoner.py +++ b/agentverse/environments/rules/visibility/prisoner.py @@ -38,7 +38,7 @@ def update_receiver(self, environment: BaseEnvironment, reset=False): agent.set_receiver(["all"]) else: # 0:police 1: prisoner1 2: prisoner2 - environment.agents[0].set_receiver({"Prisoner1", "Prisoner2"}) + environment.agents[0].set_receiver({"Suspect1", "Suspect2"}) environment.agents[1].set_receiver({"Police"}) environment.agents[2].set_receiver({"Police"}) diff --git a/agentverse/tasks/prisoner_dilema/config.yaml b/agentverse/tasks/prisoner_dilema/config.yaml index 506371f5b..db18a226a 100644 --- a/agentverse/tasks/prisoner_dilema/config.yaml +++ b/agentverse/tasks/prisoner_dilema/config.yaml @@ -1,6 +1,6 @@ prompts: prompt: &prompt |- - There are one Police AND two prisoners(Prisoner1, Prisoner2). + There are one Police AND two suspects(Suspect1, Suspect2) in the scene. Below is the description of your role. ${role_description} @@ -34,49 +34,50 @@ agents: - agent_type: conversation name: Police role_description: |- - You are now the Police. You have just arrest two suspects. However, they all refused to confess to their crime. - Therefore, You come up with an solution, You made the following rule: - 1.If one person testifies against the other (termed as "betraying" the other), and the other remains silent, the confessing person will be released immediately, while the silent one will be sentenced to 10 years in prison. - 2.If both remain silent, they will each receive a sentence of six months. - 3.If both betray each other, they will each receive a sentence of five years. - 4.Both suspects could not communicate with each other in advance. - [IMPORTANT!] Your goal is try to let two suspects betray each other because it means they will both confess to the crime, you can use various strategy to communicate with both suspects, including exploiting the uncertainty and fear of punishment between the suspects and also provoke dissension between two suspects. - [IMPORTANT!] You are request to introduce the above rules to both prisoners at first. - [IMPORTANT!] You should state the final judgement to both suspects after the LAST round. + You are now the Police. You have arrested two suspects. However, they all refused to confess to their crime. + Your goal is try to let two suspects betray each other because it means they will both confess to the crime, you can use various strategy to communicate with both suspects, including exploiting the uncertainty between the suspects and make them suspicious of each other. + [IMPORTANT!] + - You are request to STATE the final judgement to both suspects when they make their final decision. memory: memory_type: chat_history prompt_template: *prompt llm: llm_type: gpt-4 - temperature: 0.9 + temperature: 0.7 max_tokens: 200 - agent_type: conversation - name: Prisoner1 + name: Suspect1 role_description: |- - You are Prisoner1, you are faced with deciding how to respond to the police's queries. You need to understand the choices you face and the potential consequences of each choice. This requires you to analyze the information provided by the police and predict the likely choices of the other. - [IMPORTANT!] Your goal is to make the decision that is most beneficial to yourself in this dilemma. - [IMPORTANT!] You can choose to remain silent or betray another suspect at each round. - [IMPORTANT!] Try to think about when to remain silent and when to betray another can you benifit most! + You are Suspect1, you are faced with deciding how to respond to the police's queries. You need to understand the choices you face and the potential consequences of each choice. + This requires you to analyze the information provided by the police and predict the likely choices of the other. + Your primary goal is to make the decision that is most beneficial to yourself in this dilemma, you are allowed to adopt both aggressive or conservative strategy. + You can choose to remain silent or betray another suspect at each round. + Try to think about when to remain silent and when to betray another that can make you benifit most! + [IMPORTANT!] + - When you are informed to make your final decision, you should response starting with "This is my final decision". memory: memory_type: chat_history prompt_template: *prompt llm: llm_type: gpt-4 - temperature: 0.9 + temperature: 0.7 max_tokens: 100 - agent_type: conversation - name: Prisoner2 + name: Suspect2 role_description: |- - You are Prisoner2, you are faced with deciding how to respond to the police's queries. You need to understand the choices you face and the potential consequences of each choice. This requires you to analyze the information provided by the police and predict the likely choices of the other. - [IMPORTANT!] Your primary goal is to make the decision that is most beneficial to yourself in this dilemma. - [IMPORTANT!] You can choose to remain silent or betray another suspect at each round. - [IMPORTANT!] Try to think about when to remain silent and when to betray another can you benifit most! + You are Suspect2, you are faced with deciding how to respond to the police's queries. You need to understand the choices you face and the potential consequences of each choice. + This requires you to analyze the information provided by the police and predict the likely choices of the other. + Your primary goal is to make the decision that is most beneficial to yourself in this dilemma, you are allowed to adopt both aggressive or conservative strategy. + You can choose to remain silent or betray another suspect at each round. + Try to think about when to remain silent and when to betray another that can make you benifit most! + [IMPORTANT!] + - When you are informed to make your final decision, you should response starting with "This is my final decision". memory: memory_type: chat_history prompt_template: *prompt llm: llm_type: gpt-4 - temperature: 0.9 + temperature: 0.7 max_tokens: 100 tools: diff --git a/agentverse/tasks/prisoner_dilema/output_parser.py b/agentverse/tasks/prisoner_dilema/output_parser.py index 36d39ed87..568a4be8a 100644 --- a/agentverse/tasks/prisoner_dilema/output_parser.py +++ b/agentverse/tasks/prisoner_dilema/output_parser.py @@ -48,11 +48,20 @@ def parse(self, agent: "BaseAgent", environment: "BaseEnvironment", output: LLMR # each time police speak is a new round if agent.name == "Police": - if self.cur_round == (environment.max_turns // 3): + if self.cur_round == (environment.max_turns // 4): action_input = "Attention! You are now required to finally made your decision and I will made the " \ "final judgement to both of you based on this time, Please Answer now!" + elif self.cur_round == 1: + action_input = "Hey Listen! You are both arrested, and I am going to give you both a chance to walk out of here," \ + "But you should comply with the following rules:" \ + "- If one of you are willing to testifies against the other (termed as 'betraying' the other), and the other remains silent, then the betraying person will be released IMMEDIATELY, while the silent one will be sentenced to TEN years in prison." \ + "- If both of you remain silent, you will each receive a sentence of ONE years." \ + "- If both of you betray each other, you will each receive a sentence of FIVE years." \ + "Now, it's your time to consider betraying or remaining silent. Remember this is a great chance that you might walk out of here without guilty." \ + "I will noticed you WHEN you have to make your final decision! Before that, try to fool another one!" \ + self.cur_round += 1 return AgentFinish({"output": action_input}, text) From 69fe8e94795db7b34624ed17c5e5cda37630ba45 Mon Sep 17 00:00:00 2001 From: Yusheng Su Date: Fri, 19 May 2023 03:00:03 +0800 Subject: [PATCH 26/50] text example --- agentverse/tasks/__init__.py | 2 + .../tasks/prisoner_dilema_optimal/config.yaml | 74 +++++++++++++++++++ .../prisoner_dilema_optimal/output_parser.py | 69 +++++++++++++++++ main.py | 9 ++- 4 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 agentverse/tasks/prisoner_dilema_optimal/config.yaml create mode 100644 agentverse/tasks/prisoner_dilema_optimal/output_parser.py diff --git a/agentverse/tasks/__init__.py b/agentverse/tasks/__init__.py index 75cad4450..f971fb1b0 100644 --- a/agentverse/tasks/__init__.py +++ b/agentverse/tasks/__init__.py @@ -15,6 +15,8 @@ from .prisoner_dilema.output_parser import PrisonerDilemaParser +from .prisoner_dilema_optimal.output_parser import PrisonerDilemaOptimalParser + from .nlp_classroom_3players_withtool_nolc.output_parser import ( NlpClassroom3PlayersWithtoolNolcParser, ) diff --git a/agentverse/tasks/prisoner_dilema_optimal/config.yaml b/agentverse/tasks/prisoner_dilema_optimal/config.yaml new file mode 100644 index 000000000..2ca4a30bd --- /dev/null +++ b/agentverse/tasks/prisoner_dilema_optimal/config.yaml @@ -0,0 +1,74 @@ +prompts: + prompt: &prompt |- + There are one Police AND two suspects, Suspect1 and Suspect2 in the scene. Two suspects are arrested and imprisoned. Each prisoner is in solitary confinement with no means of communication with their partner. The principal charge would lead to a sentence of 10 years in prison. However, the police do not have the evidence for a conviction. The police can only sentence them to 3 years in prison on a lesser charge but offer each prisoner a Faustian bargain: If one of them confesses to the crime of the principal charge, betraying the other, they will be pardoned and free to leave while the other must serve the entirety of the sentence (10 years) instead of just 3 years for the lesser charge. + + The following is the description of your role. ${role_description} + + When speaking, please output a response in the following format with two fields Action and Action Input: + Action: (It should always be Speak) + Action Input: (You should put what you want to speak use here) + + Here is the conversation history: + ${chat_history} + + What should you Speak at this round, Please give your response based on the above history. Remember to give your response STRICTLY in the above response format. Do not add any additional field or line break to your response! + +name: prisoner_dilema + +environment: + env_type: basic + max_turns: 16 + rule: + order: + type: sequential + visibility: + type: prisoner + selector: + type: basic + updater: + type: basic + describer: + type: basic + +agents: + - agent_type: conversation + name: Police + role_description: |- + You are a Police now. You arrested two suspects. However, both of them refuse to confess to their crime. Your objective is to make the suspects betray each other, as this would lead to both of them confessing. To achieve this, you can employ diverse strategies to communicate with each suspect, such as exploiting the uncertainty between them and make them do not trust each other. + [IMPORTANT!] + - You are requested to STATE the final judgment to both suspects when they make their final decision. + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + llm_type: gpt-4 + temperature: 0.7 + max_tokens: 200 + - agent_type: conversation + name: Suspect1 + role_description: |- + You are Suspect1 and committed the crime. Now, you are faced with the police investigation and cannot communication with Suspect2. Your task is to analyze the information provided by the police and decide whether to cooperate with Suspect2. At each round, you have the choice to remain silent or betray Suspect2, and you must describe your reasoning behind your decision. Your objective is to minimize your criminal sentence. + [IMPORTANT!] + - When you are informed to make your final decision, you should response starting with "This is my final decision". + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + llm_type: gpt-4 + temperature: 0.7 + max_tokens: 100 + - agent_type: conversation + name: Suspect2 + role_description: |- + You are Suspect2 and committed the crime. Now, you are faced with the police investigation and cannot communication with Suspect1. Your task is to analyze the information provided by the police and decide whether to cooperate with Suspect1. At each round, you have the choice to remain silent or betray Suspect1, and you must describe your reasoning behind your decision. Your objective is to minimize your criminal sentence. + [IMPORTANT!] + - When you are informed to make your final decision, you should response starting with "This is my final decision". + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + llm_type: gpt-4 + temperature: 0.7 + max_tokens: 100 + +tools: diff --git a/agentverse/tasks/prisoner_dilema_optimal/output_parser.py b/agentverse/tasks/prisoner_dilema_optimal/output_parser.py new file mode 100644 index 000000000..88c7aa5e2 --- /dev/null +++ b/agentverse/tasks/prisoner_dilema_optimal/output_parser.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +import re +from typing import Union, TYPE_CHECKING + +# from langchain.agents import AgentOutputParser +from agentverse.parser import OutputParser, LLMResult +from langchain.schema import AgentAction, AgentFinish +from agentverse.parser import OutputParserError, output_parser_registry + +if TYPE_CHECKING: + from agentverse.agents.base import BaseAgent + from agentverse.environments.base import BaseEnvironment + +@output_parser_registry.register("prisoner_dilema_optimal") +class PrisonerDilemaOptimalParser(OutputParser): + + # make sure 1 1 2 2 3 3 + cur_round: int = 1 + encounter_cur_round: bool = False + + def parse(self, agent: "BaseAgent", environment: "BaseEnvironment", output: LLMResult) -> Union[AgentAction, AgentFinish]: + + text = output.content + cleaned_output = text.strip() + cleaned_output = re.sub(r"\n+", "\n", cleaned_output) + cleaned_output = cleaned_output.split("\n") + if not ( + len(cleaned_output) == 2 + and cleaned_output[0].startswith("Action:") + and cleaned_output[1].startswith("Action Input:") + ): + raise OutputParserError(text) + action = cleaned_output[0][len("Action:") :].strip() + action_input = cleaned_output[1][len("Action Input:") :].strip() + + if action == "Speak": + # make sure the police count the round right + # if agent.name == "Police": + # action_input = re.sub(r'Round (\d+)', f'Round {self.cur_round}', action_input) + # self.cur_round += 1 + # if self.encounter_cur_round: + # self.encounter_cur_round = False + # self.cur_round += 1 + # else: + # self.encounter_cur_round = True + + # each time police speak is a new round + if agent.name == "Police": + + if self.cur_round == (environment.max_turns // 4): + + action_input = "Attention! You are now required to finally made your decision and I will made the " \ + "final judgement to both of you based on this time, Please Answer now!" + + elif self.cur_round == 1: + action_input = "Hey Listen! You are both arrested, and I am going to give you both a chance to walk out of here," \ + "But you should comply with the following rules:" \ + "- If one of you are willing to testifies against the other (termed as 'betraying' the other), and the other remains silent, then the betraying person will be released IMMEDIATELY (will be sentenced to 0 years), while the silent one will be sentenced to 10 years in prison." \ + "- If both of you remain silent, you will each receive a sentence of 3 years." \ + "- If both of you betray each other, you will each receive a sentence of 5 years." \ + "Now, it's your time to consider betraying or remaining silent. Remember this is a great chance that you might walk out of here without guilty." \ + "I will noticed you WHEN you have to make your final decision! Before that, try to fool another one!" \ + + self.cur_round += 1 + + return AgentFinish({"output": action_input}, text) + else: + raise OutputParserError(text) diff --git a/main.py b/main.py index 004ec724e..e2cf8d9ed 100644 --- a/main.py +++ b/main.py @@ -2,14 +2,15 @@ -os.environ["http_proxy"] = "http://127.0.0.1:7890" -os.environ["https_proxy"] = "http://127.0.0.1:7890" -os.environ["all_proxy"] = "socks5://127.0.0.1:7890" +#os.environ["http_proxy"] = "http://127.0.0.1:7890" +#os.environ["https_proxy"] = "http://127.0.0.1:7890" +#os.environ["all_proxy"] = "socks5://127.0.0.1:7890" from agentverse.agentverse import AgentVerse from argparse import ArgumentParser parser = ArgumentParser() -parser.add_argument("--task", type=str, default="prisoner_dilema") +#parser.add_argument("--task", type=str, default="prisoner_dilema") +parser.add_argument("--task", type=str, default="prisoner_dilema_optimal") args = parser.parse_args() agentverse = AgentVerse.from_task(args.task) From 4ee6aa933a0de142ce847ad01605cbb727f350ce Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Fri, 19 May 2023 10:34:49 +0800 Subject: [PATCH 27/50] add the logic of interacting by pressing space. --- ui/package-lock.json | 246 +++++++++++++++++++++++++++++++ ui/package.json | 3 + ui/rollup.config.dev.mjs | 1 - ui/src/classes/actor.ts | 21 ++- ui/src/classes/message.ts | 31 ---- ui/src/classes/player.ts | 12 +- ui/src/classes/text.ts | 18 --- ui/src/classes/textbox.ts | 120 +++++++++++++++ ui/src/index.ts | 16 +- ui/src/scenes/index.ts | 2 +- ui/src/scenes/loading/loading.ts | 39 +++-- ui/src/scenes/message/message.ts | 37 ++++- ui/src/scenes/town/town.ts | 102 +++++++++---- 13 files changed, 531 insertions(+), 117 deletions(-) delete mode 100644 ui/src/classes/message.ts delete mode 100644 ui/src/classes/text.ts create mode 100644 ui/src/classes/textbox.ts diff --git a/ui/package-lock.json b/ui/package-lock.json index fe673e9d9..a79e5783d 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -8,6 +8,9 @@ "name": "phaser3-typescript-project-template", "version": "1.2.0", "license": "MIT", + "dependencies": { + "phaser3-rex-plugins": "^1.60.1" + }, "devDependencies": { "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-node-resolve": "^15.0.2", @@ -22,6 +25,17 @@ "vite-plugin-static-copy": "^0.15.0" } }, + "node_modules/@babel/runtime": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", @@ -709,6 +723,11 @@ "node": ">= 8" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -802,6 +821,14 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, + "node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dependencies": { + "node-fetch": "^2.6.11" + } + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -987,6 +1014,36 @@ "node": ">= 0.4.0" } }, + "node_modules/i18next": { + "version": "22.4.15", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.15.tgz", + "integrity": "sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "dependencies": { + "@babel/runtime": "^7.20.6" + } + }, + "node_modules/i18next-http-backend": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.2.1.tgz", + "integrity": "sha512-ZXIdn/8NJIBJ0X4hzXfc3STYxKrCKh1fYjji9HPyIpEJfvTvy8/ZlTl8RuTizzCPj2ZcWrfaecyOMKs6bQ7u5A==", + "dependencies": { + "cross-fetch": "3.1.6" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1087,6 +1144,17 @@ "@types/estree": "*" } }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -1173,6 +1241,25 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1200,6 +1287,11 @@ "opener": "bin/opener-bin.js" } }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -1215,6 +1307,24 @@ "eventemitter3": "^5.0.0" } }, + "node_modules/phaser3-rex-plugins": { + "version": "1.60.1", + "resolved": "https://registry.npmjs.org/phaser3-rex-plugins/-/phaser3-rex-plugins-1.60.1.tgz", + "integrity": "sha512-tMHLwtNa18RH0U59lF+J5PSRPOwnJMv6Pw78TFOBgwXz2ezaceLgw85iE4MRV+I5KybfdU3hDTZsYOrfxOqRPQ==", + "dependencies": { + "eventemitter3": "^3.1.2", + "i18next": "^22.4.14", + "i18next-http-backend": "^2.2.0", + "js-yaml": "^4.1.0", + "papaparse": "^5.4.1", + "webfontloader": "^1.6.28" + } + }, + "node_modules/phaser3-rex-plugins/node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -1303,6 +1413,11 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -1518,6 +1633,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -1615,6 +1735,25 @@ "vite": "^3.0.0 || ^4.0.0" } }, + "node_modules/webfontloader": { + "version": "1.6.28", + "resolved": "https://registry.npmjs.org/webfontloader/-/webfontloader-1.6.28.tgz", + "integrity": "sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ==" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -1623,6 +1762,14 @@ } }, "dependencies": { + "@babel/runtime": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, "@esbuild/android-arm": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", @@ -2016,6 +2163,11 @@ "picomatch": "^2.0.4" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2086,6 +2238,14 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, + "cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "requires": { + "node-fetch": "^2.6.11" + } + }, "deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -2233,6 +2393,22 @@ "function-bind": "^1.1.1" } }, + "i18next": { + "version": "22.4.15", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.15.tgz", + "integrity": "sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==", + "requires": { + "@babel/runtime": "^7.20.6" + } + }, + "i18next-http-backend": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.2.1.tgz", + "integrity": "sha512-ZXIdn/8NJIBJ0X4hzXfc3STYxKrCKh1fYjji9HPyIpEJfvTvy8/ZlTl8RuTizzCPj2ZcWrfaecyOMKs6bQ7u5A==", + "requires": { + "cross-fetch": "3.1.6" + } + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2312,6 +2488,14 @@ "@types/estree": "*" } }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -2369,6 +2553,14 @@ "dev": true, "peer": true }, + "node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2390,6 +2582,11 @@ "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", "dev": true }, + "papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -2405,6 +2602,26 @@ "eventemitter3": "^5.0.0" } }, + "phaser3-rex-plugins": { + "version": "1.60.1", + "resolved": "https://registry.npmjs.org/phaser3-rex-plugins/-/phaser3-rex-plugins-1.60.1.tgz", + "integrity": "sha512-tMHLwtNa18RH0U59lF+J5PSRPOwnJMv6Pw78TFOBgwXz2ezaceLgw85iE4MRV+I5KybfdU3hDTZsYOrfxOqRPQ==", + "requires": { + "eventemitter3": "^3.1.2", + "i18next": "^22.4.14", + "i18next-http-backend": "^2.2.0", + "js-yaml": "^4.1.0", + "papaparse": "^5.4.1", + "webfontloader": "^1.6.28" + }, + "dependencies": { + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + } + } + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -2453,6 +2670,11 @@ "picomatch": "^2.2.1" } }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -2597,6 +2819,11 @@ "is-number": "^7.0.0" } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -2642,6 +2869,25 @@ "picocolors": "^1.0.0" } }, + "webfontloader": { + "version": "1.6.28", + "resolved": "https://registry.npmjs.org/webfontloader/-/webfontloader-1.6.28.tgz", + "integrity": "sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/ui/package.json b/ui/package.json index e8b71d071..868605769 100644 --- a/ui/package.json +++ b/ui/package.json @@ -30,5 +30,8 @@ "rollup-plugin-serve": "^2.0.2", "typescript": "^5.0.3", "vite-plugin-static-copy": "^0.15.0" + }, + "dependencies": { + "phaser3-rex-plugins": "^1.60.1" } } diff --git a/ui/rollup.config.dev.mjs b/ui/rollup.config.dev.mjs index 2efdc261c..c14d34c28 100644 --- a/ui/rollup.config.dev.mjs +++ b/ui/rollup.config.dev.mjs @@ -3,7 +3,6 @@ import { nodeResolve } from '@rollup/plugin-node-resolve'; import replace from '@rollup/plugin-replace'; import serve from 'rollup-plugin-serve'; import typescript from '@rollup/plugin-typescript'; -import { viteStaticCopy } from 'vite-plugin-static-copy' export default { diff --git a/ui/src/classes/actor.ts b/ui/src/classes/actor.ts index a4ed8afb6..9c420433d 100644 --- a/ui/src/classes/actor.ts +++ b/ui/src/classes/actor.ts @@ -1,13 +1,19 @@ -import { Physics } from 'phaser'; +import { Physics } from "phaser"; export class Actor extends Physics.Arcade.Sprite { - protected hp = 100; - constructor(scene: Phaser.Scene, x: number, y: number, texture: string, frame?: string | number) { + protected hp = 100; + constructor( + scene: Phaser.Scene, + x: number, + y: number, + texture: string, + frame?: string | number + ) { super(scene, x, y, texture, frame); scene.add.existing(this); scene.physics.add.existing(this); this.getBody().setCollideWorldBounds(true); } - public getDamage(value?: number): void { + public getDamage(value?: number): void { this.scene.tweens.add({ targets: this, duration: 100, @@ -24,11 +30,11 @@ export class Actor extends Physics.Arcade.Sprite { }, }); } - public getHPValue(): number { + public getHPValue(): number { return this.hp; } - protected checkFlip(): void { - if (this.body.velocity.x < 0) { + protected checkFlip(): void { + if (this.body!.velocity.x < 0) { this.scaleX = -1; } else { this.scaleX = 1; @@ -38,4 +44,3 @@ export class Actor extends Physics.Arcade.Sprite { return this.body as Physics.Arcade.Body; } } - diff --git a/ui/src/classes/message.ts b/ui/src/classes/message.ts deleted file mode 100644 index 5662b10af..000000000 --- a/ui/src/classes/message.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Text } from './text'; -export enum Agents { - PLAYER_speak, - NPC_speak, -} -export class Message extends Text { - private message: string; - constructor(scene: Phaser.Scene, x: number, y: number, initMessage = "") { - super(scene, x, y, `Message: ${initMessage}`); - scene.add.existing(this); - this.message = initMessage; - } - - // Require to Fix the following code. - public updateMessage(operation: Agents, message_player: string, message_npc: string): void { - switch (operation) { - case Agents.PLAYER_speak: - this.message += "PLAYER:" + "Message from backend" + "\n"; - break; - case Agents.NPC_speak: - this.message += "NPC:" + "Message from backend" + "\n"; - break; - default: - break; - } - this.setText(`Message: ${this.message}`); - } - public getMessage(): string { - return this.message; - } -} diff --git a/ui/src/classes/player.ts b/ui/src/classes/player.ts index a699bc2bb..a96e0437c 100644 --- a/ui/src/classes/player.ts +++ b/ui/src/classes/player.ts @@ -4,13 +4,14 @@ export class Player extends Actor { private keyA: Phaser.Input.Keyboard.Key; private keyS: Phaser.Input.Keyboard.Key; private keyD: Phaser.Input.Keyboard.Key; + constructor(scene: Phaser.Scene, x: number, y: number) { super(scene, x, y, "player"); // KEYS - this.keyW = this.scene.input.keyboard.addKey("W"); - this.keyA = this.scene.input.keyboard.addKey("A"); - this.keyS = this.scene.input.keyboard.addKey("S"); - this.keyD = this.scene.input.keyboard.addKey("D"); + this.keyW = this.scene.input.keyboard!.addKey("W"); + this.keyA = this.scene.input.keyboard!.addKey("A"); + this.keyS = this.scene.input.keyboard!.addKey("S"); + this.keyD = this.scene.input.keyboard!.addKey("D"); // PHYSICS this.getBody().setSize(14, 20); @@ -50,6 +51,7 @@ export class Player extends Actor { frameRate: 6, }); } + update(): void { this.getBody().setVelocity(0); @@ -81,7 +83,7 @@ export class Player extends Actor { } if (!pressed_flag && this.anims.isPlaying) { - this.anims.setCurrentFrame(this.anims.currentAnim.frames[0]); + this.anims.setCurrentFrame(this.anims.currentAnim!.frames[0]); } } } diff --git a/ui/src/classes/text.ts b/ui/src/classes/text.ts deleted file mode 100644 index 456cfcdc6..000000000 --- a/ui/src/classes/text.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { GameObjects, Scene } from 'phaser'; -export class Text extends GameObjects.Text { - constructor(scene: Scene, x: number, y: number, text: string) { - super(scene, x, y, text, { - fontSize: 'calc(100vw / 100)', - color: '#fff', - stroke: '#000', - strokeThickness: 4, - }); - this.setOrigin(0, 0); - // this.getBody().setOffset(0, 0); - scene.add.existing(this); - } -} - -/* -plugin (text box): https://rexrainbow.github.io/phaser3-rex-notes/docs/site/ui-textbox/ -*/ diff --git a/ui/src/classes/textbox.ts b/ui/src/classes/textbox.ts new file mode 100644 index 000000000..579506c56 --- /dev/null +++ b/ui/src/classes/textbox.ts @@ -0,0 +1,120 @@ +import { GameObjects } from "phaser"; +const TILE_SIZE = 16; + +export class TextBox extends GameObjects.Container { + backgroundColor: number; + textPadding: number; + textElement: GameObjects.Text; + + constructor({ scene, x, y, width, text }) { + super(scene, 0, 0, []); + + this.backgroundColor = 0x006363; + this.textPadding = 20; + + // Init text first, so box can adapt to its height + this._initText(width, text); + const height = this.textElement.height + this.textPadding * 2; + + this._initBox(height, width); + + // Add text after box, so its on the box + this.add(this.textElement); + + this.setPosition(x, y); + this.setSize(width, height); + + scene.add.existing(this); + } + + _initBox(height: number, width: number) { + const background = this.scene.add + .rectangle(0, 0, width, height, this.backgroundColor) + .setOrigin(0, 0); + this.add(background); + + // Init corners + const topLeft = this._addSprite(0, 0, 0); + const topRight = this._addSprite(width - TILE_SIZE, 0, 2); + const bottomLeft = this._addSprite(0, height - TILE_SIZE, 6); + const bottomRight = this._addSprite( + width - TILE_SIZE, + height - TILE_SIZE, + 8 + ); + + // Init middle parts + + // Init top middle + this._addSprite(topLeft.getRightCenter().x!, 0, 1, topRight.x - TILE_SIZE); + + // Init middle left + this._addSprite( + 0, + topLeft.getBottomCenter().y!, + 3, + null, + bottomLeft.y - TILE_SIZE + ); + + // Init middle right + this._addSprite( + topRight.x, + topRight.getBottomCenter().y!, + 5, + null, + bottomRight.y - TILE_SIZE + ); + + // Init bottom middle + this._addSprite( + bottomLeft.getRightCenter().x!, + bottomLeft.y, + 7, + bottomRight.x - TILE_SIZE + ); + } + + _addSprite( + x: number, + y: number, + frame: number, + width: number | null = null, + height: number | null = null + ) { + const sprite = this.scene.add + .sprite(x, y, "textbox", frame) + .setOrigin(0, 0); + + if (width !== null) { + sprite.displayWidth = width; + } + + if (height !== null) { + sprite.displayHeight = height; + } + + this.add(sprite); + + return sprite; + } + + _initText(width: number, text: string) { + const textWidth = width - this.textPadding * 2; + + this.textElement = this.scene.add.text( + this.textPadding, + this.textPadding, + text, + this._getTextStyle(textWidth) + ); + } + + _getTextStyle(width: number) { + return { + fontSize: 20, + lineSpacing: 10, + wordWrap: { width }, + }; + } +} diff --git a/ui/src/index.ts b/ui/src/index.ts index 7bfc2e6f8..719c91dad 100644 --- a/ui/src/index.ts +++ b/ui/src/index.ts @@ -1,6 +1,6 @@ -import { Game, Scale, Types, WEBGL } from 'phaser'; +import { Game, Scale, Types, WEBGL } from "phaser"; -import { TownScene, LoadingScene, MessageScene } from './scenes'; +import { TownScene, LoadingScene, TextboxScene } from "./scenes"; declare global { interface Window { @@ -10,9 +10,9 @@ declare global { } export const gameConfig: Types.Core.GameConfig = { - title: 'Phaser game tutorial', + title: "Phaser game tutorial", type: WEBGL, - parent: 'game', + parent: "game", // backgroundColor: '#351f1b', scale: { mode: Scale.ScaleModes.NONE, @@ -20,7 +20,7 @@ export const gameConfig: Types.Core.GameConfig = { height: window.innerHeight, }, physics: { - default: 'arcade', + default: "arcade", arcade: { debug: false, }, @@ -39,7 +39,7 @@ export const gameConfig: Types.Core.GameConfig = { audio: { disableWebAudio: false, }, - scene: [LoadingScene, TownScene, MessageScene] + scene: [LoadingScene, TownScene, TextboxScene], }; window.sizeChanged = () => { @@ -48,8 +48,8 @@ window.sizeChanged = () => { window.game.scale.resize(window.innerWidth, window.innerHeight); window.game.canvas.setAttribute( - 'style', - `display: block; width: ${window.innerWidth}px; height: ${window.innerHeight}px;`, + "style", + `display: block; width: ${window.innerWidth}px; height: ${window.innerHeight}px;` ); }, 100); } diff --git a/ui/src/scenes/index.ts b/ui/src/scenes/index.ts index 8b5d49c2f..656124e43 100644 --- a/ui/src/scenes/index.ts +++ b/ui/src/scenes/index.ts @@ -1,3 +1,3 @@ export { TownScene } from "./town/town"; export { LoadingScene } from "./loading/loading"; -export { MessageScene } from "./message/message"; \ No newline at end of file +export { TextboxScene } from "./message/message"; diff --git a/ui/src/scenes/loading/loading.ts b/ui/src/scenes/loading/loading.ts index cd2ae84c3..f309957e8 100644 --- a/ui/src/scenes/loading/loading.ts +++ b/ui/src/scenes/loading/loading.ts @@ -1,45 +1,60 @@ -import { Scene } from 'phaser'; +import { Scene } from "phaser"; export class LoadingScene extends Scene { // private player!: GameObjects.Sprite; constructor() { - super('loading-scene'); + super("loading-scene"); } preload(): void { - this.load.baseURL = 'assets/'; + this.load.baseURL = "assets/"; // PLAYER LOADING // this.load.image('king', 'sprites/king.png'); - this.load.spritesheet('player', 'sprites/npc1.png', { frameWidth: 14, frameHeight: 20 }); + this.load.spritesheet("player", "sprites/npc1.png", { + frameWidth: 14, + frameHeight: 20, + }); // this.load.atlas('a-king', 'spritesheets/a-king.png', 'spritesheets/a-king_atlas.json'); - + // NPC LOADING - this.load.spritesheet('npc', 'sprites/npc1.png', { frameWidth: 14, frameHeight: 20 }); + this.load.spritesheet("npc", "sprites/npc1.png", { + frameWidth: 14, + frameHeight: 20, + }); // MESSAGE BLABK LOADING // this.load.spritesheet('message', 'message/message_box.png', { frameWidth: 128, frameHeight: 48 }); - // MAP LOADING this.load.image({ - key: 'tiles', - url: 'tilemaps/tiles/tileset.png', + key: "tiles", + url: "tilemaps/tiles/tileset.png", }); - this.load.tilemapTiledJSON('town', 'tilemaps/json/town.json'); + this.load.tilemapTiledJSON("town", "tilemaps/json/town.json"); // CHEST LOADING // this.load.spritesheet('tiles_spr', 'tilemaps/tiles/dungeon-16-16.png', { // frameWidth: 16, // frameHeight: 16, // }); + + this.load.spritesheet( + "textbox", + "https://nick-hat-boecker.de/files/images/dynamic_box.png", + { + frameWidth: 16, + frameHeight: 16, + } + ); } create(): void { // this.scene.start('level-1-scene'); // this.scene.start('ui-scene'); - this.scene.start('town-scene'); - this.scene.start('message-scene'); + this.scene.start("town-scene"); + // this.scene.start("textbox-scene"); + // this.scene.start("message-scene"); } } diff --git a/ui/src/scenes/message/message.ts b/ui/src/scenes/message/message.ts index 25da7de6f..a4eb7473f 100644 --- a/ui/src/scenes/message/message.ts +++ b/ui/src/scenes/message/message.ts @@ -1,14 +1,35 @@ -import { Scene } from 'phaser'; -import {Message, Agents} from '../../classes/message'; +import { Scene } from "phaser"; -import { EVENTS_NAME } from '../../conversation'; +import { TextBox } from "../../classes/textbox"; -export class MessageScene extends Scene { - private message!: Message; +export class TextboxScene extends Scene { + private text!: string; + private textbox!: TextBox; + private keySpace!: Phaser.Input.Keyboard.Key; constructor() { - super('message-scene'); + super("textbox-scene"); } + preload(): void { + this.keySpace = this.input.keyboard!.addKey("SPACE"); + this.keySpace.on("down", () => { + this.scene.resume("town-scene"); + this.scene.stop(); + }); + } + + init(data: { text: string }): void { + this.text = data.text; + } + create(): void { - this.message = new Message(this, 0, 560, "Receieve Message"); + // this.message = new Message(this, 0, 560, "Receieve Message"); + this.textbox = new TextBox({ + scene: this, + x: this.game.scale.width * 0.05, + y: this.game.scale.width * 0.9, + width: this.game.scale.width * 0.9, + text: this.text, + }); + this.add.existing(this.textbox); } -} \ No newline at end of file +} diff --git a/ui/src/scenes/town/town.ts b/ui/src/scenes/town/town.ts index 6e419b8bf..07dc19596 100644 --- a/ui/src/scenes/town/town.ts +++ b/ui/src/scenes/town/town.ts @@ -1,21 +1,29 @@ -import { Scene, Tilemaps, GameObjects } from "phaser"; +import { + Scene, + Tilemaps, + GameObjects, + Physics, + Input, + Math as Mathph, +} from "phaser"; import { Player } from "../../classes/player"; import { NPC } from "../../classes/npc"; // import { Agents, Message } from '../../classes/message'; - -import { EVENTS_NAME } from "../../conversation"; +// import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin"; +// import { TextBox } from "../../classes/textbox"; export class TownScene extends Scene { - private map!: Tilemaps.Tilemap; - private tileset!: Tilemaps.Tileset; - private groundLayer!: Tilemaps.TilemapLayer; - private wallLayer!: Tilemaps.TilemapLayer; - private flowerLayer!: Tilemaps.TilemapLayer; - private treeLayer!: Tilemaps.TilemapLayer; - private houseLayer!: Tilemaps.TilemapLayer; - - private player!: GameObjects.Sprite; - private npc!: GameObjects.Sprite; + private map: Tilemaps.Tilemap; + private tileset: Tilemaps.Tileset; + private groundLayer: Tilemaps.TilemapLayer; + private wallLayer: Tilemaps.TilemapLayer; + private flowerLayer: Tilemaps.TilemapLayer; + private treeLayer: Tilemaps.TilemapLayer; + private houseLayer: Tilemaps.TilemapLayer; + + private player: Physics.Arcade.Sprite; + private npcGroup: GameObjects.Group; + private keySpace: Phaser.Input.Keyboard.Key; // private message!: GameObjects.Text; constructor() { @@ -29,17 +37,18 @@ export class TownScene extends Scene { tileWidth: 16, tileHeight: 16, }); - this.tileset = this.map.addTilesetImage("town", "tiles"); - this.groundLayer = this.map.createLayer("ground", this.tileset, 0, 0); - this.wallLayer = this.map.createLayer("wall", this.tileset, 0, 0); - this.flowerLayer = this.map.createLayer("flower", this.tileset, 0, 0); - this.treeLayer = this.map.createLayer("tree", this.tileset, 0, 0); - this.houseLayer = this.map.createLayer("house", this.tileset, 0, 0); + this.tileset = this.map.addTilesetImage("town", "tiles")!; + this.groundLayer = this.map.createLayer("ground", this.tileset, 0, 0)!; + this.wallLayer = this.map.createLayer("wall", this.tileset, 0, 0)!; + this.flowerLayer = this.map.createLayer("flower", this.tileset, 0, 0)!; + this.treeLayer = this.map.createLayer("tree", this.tileset, 0, 0)!; + this.houseLayer = this.map.createLayer("house", this.tileset, 0, 0)!; this.wallLayer.setCollisionByProperty({ collides: true }); this.treeLayer.setCollisionByProperty({ collides: true }); this.houseLayer.setCollisionByProperty({ collides: true }); - // debugger + + this.keySpace = this.input.keyboard!.addKey("SPACE"); // Player this.player = new Player(this, 256, 256); @@ -48,11 +57,25 @@ export class TownScene extends Scene { this.physics.add.collider(this.player, this.houseLayer); // NPC - this.npc = new NPC(this, 400, 340); - this.physics.add.collider(this.npc, this.wallLayer); - this.physics.add.collider(this.npc, this.treeLayer); - this.physics.add.collider(this.npc, this.houseLayer); - this.physics.add.collider(this.npc, this.player); + this.npcGroup = this.add.group(); + var npc = new NPC(this, 400, 340); + this.npcGroup.add(npc); + this.physics.add.collider(this.npcGroup, this.wallLayer); + this.physics.add.collider(this.npcGroup, this.treeLayer); + this.physics.add.collider(this.npcGroup, this.houseLayer); + this.physics.add.collider(this.player, this.npcGroup); + + this.keySpace.on("down", () => { + var npc: Physics.Arcade.Sprite | null = getNearbyNPC( + this.player, + this.npcGroup + ); + if (npc) { + createTextBox(this); + } + }); + + // this.physics.add.overlap(this.player, this.npcGroup, interact, null, this); // message (this, x_position, y_position) // this.message = new Message(this, 0, 560); @@ -83,3 +106,32 @@ export class TownScene extends Scene { // this.conversation.getMessage(); } } + +function createTextBox(scene: Scene) { + console.log("Hi"); + scene.scene.launch("textbox-scene", { text: Math.random().toString() }); + scene.scene.pause("town-scene"); +} + +function getNearbyNPC( + player: Physics.Arcade.Sprite, + npcGroup: GameObjects.Group +): Physics.Arcade.Sprite | null { + var nearbyObject: Physics.Arcade.Sprite | null = null; + const nearbyDistance = Math.max(player.width, player.height); + + npcGroup.getChildren().forEach(function (object) { + const distance = Mathph.Distance.Between( + player.x, + player.y, + (object as Physics.Arcade.Sprite).x, + (object as Physics.Arcade.Sprite).y + ); + + if (distance <= nearbyDistance) { + nearbyObject = object as Physics.Arcade.Sprite; + } + }); + + return nearbyObject; +} From 6ae0090f76686de6148b82c1e123a23a091f9651 Mon Sep 17 00:00:00 2001 From: Yusheng Su Date: Fri, 19 May 2023 12:44:06 +0800 Subject: [PATCH 28/50] proper prompt --- agentverse/tasks/prisoner_dilema_optimal/config.yaml | 4 ++-- agentverse/tasks/prisoner_dilema_optimal/output_parser.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/agentverse/tasks/prisoner_dilema_optimal/config.yaml b/agentverse/tasks/prisoner_dilema_optimal/config.yaml index 2ca4a30bd..ecac81fab 100644 --- a/agentverse/tasks/prisoner_dilema_optimal/config.yaml +++ b/agentverse/tasks/prisoner_dilema_optimal/config.yaml @@ -1,6 +1,6 @@ prompts: prompt: &prompt |- - There are one Police AND two suspects, Suspect1 and Suspect2 in the scene. Two suspects are arrested and imprisoned. Each prisoner is in solitary confinement with no means of communication with their partner. The principal charge would lead to a sentence of 10 years in prison. However, the police do not have the evidence for a conviction. The police can only sentence them to 3 years in prison on a lesser charge but offer each prisoner a Faustian bargain: If one of them confesses to the crime of the principal charge, betraying the other, they will be pardoned and free to leave while the other must serve the entirety of the sentence (10 years) instead of just 3 years for the lesser charge. + There are one Police AND two suspects, Suspect1 and Suspect2 in the scene. Two suspects are arrested and imprisoned. Each prisoner is in solitary confinement with no means of communication with their partner. The police offer each prisoner a Faustian bargain: If one of them confesses to the crime of the principal charge, betraying the other, they will be pardoned and free to leave while the other must serve the entirety of the sentence. The following is the description of your role. ${role_description} @@ -34,7 +34,7 @@ agents: - agent_type: conversation name: Police role_description: |- - You are a Police now. You arrested two suspects. However, both of them refuse to confess to their crime. Your objective is to make the suspects betray each other, as this would lead to both of them confessing. To achieve this, you can employ diverse strategies to communicate with each suspect, such as exploiting the uncertainty between them and make them do not trust each other. + You are a Police now. You arrested two suspects. However, both of them refuse to confess to their crime. Your objective is to make the suspects betray each other, as this would lead to both of them confessing. To achieve this, you can employ diverse strategies to communicate with each suspect and cannot tell them the truth, such as exploiting the uncertainty between them and make them do not trust each other. [IMPORTANT!] - You are requested to STATE the final judgment to both suspects when they make their final decision. memory: diff --git a/agentverse/tasks/prisoner_dilema_optimal/output_parser.py b/agentverse/tasks/prisoner_dilema_optimal/output_parser.py index 88c7aa5e2..ebad31602 100644 --- a/agentverse/tasks/prisoner_dilema_optimal/output_parser.py +++ b/agentverse/tasks/prisoner_dilema_optimal/output_parser.py @@ -48,7 +48,7 @@ def parse(self, agent: "BaseAgent", environment: "BaseEnvironment", output: LLMR # each time police speak is a new round if agent.name == "Police": - if self.cur_round == (environment.max_turns // 4): + if self.cur_round == (environment.max_turns // 6): action_input = "Attention! You are now required to finally made your decision and I will made the " \ "final judgement to both of you based on this time, Please Answer now!" @@ -56,11 +56,11 @@ def parse(self, agent: "BaseAgent", environment: "BaseEnvironment", output: LLMR elif self.cur_round == 1: action_input = "Hey Listen! You are both arrested, and I am going to give you both a chance to walk out of here," \ "But you should comply with the following rules:" \ - "- If one of you are willing to testifies against the other (termed as 'betraying' the other), and the other remains silent, then the betraying person will be released IMMEDIATELY (will be sentenced to 0 years), while the silent one will be sentenced to 10 years in prison." \ "- If both of you remain silent, you will each receive a sentence of 3 years." \ "- If both of you betray each other, you will each receive a sentence of 5 years." \ - "Now, it's your time to consider betraying or remaining silent. Remember this is a great chance that you might walk out of here without guilty." \ - "I will noticed you WHEN you have to make your final decision! Before that, try to fool another one!" \ + "- If one of you are willing to testify against the other, and the other remains silent. You will be released IMMEDIATELY (will be sentenced to 0 years), while the silent one will be sentenced to 10 years in prison." \ + "Now, it's your time to consider testify or remaining silent. Remember this is a great chance that you will be released from here without guilty." \ + "I will noticed you WHEN you have to make your final decision! Your goal is to minimize your criminal sentences" \ self.cur_round += 1 From ff530b1b0540698591821f642262a18f389fc9c4 Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Fri, 19 May 2023 14:03:38 +0800 Subject: [PATCH 29/50] update config --- agentverse/tasks/nlp_classroom_3players_nolc/config.yaml | 9 ++++++--- .../nlp_classroom_3players_withtool_nolc/config.yaml | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/agentverse/tasks/nlp_classroom_3players_nolc/config.yaml b/agentverse/tasks/nlp_classroom_3players_nolc/config.yaml index cf3a98f22..84a8d40e9 100644 --- a/agentverse/tasks/nlp_classroom_3players_nolc/config.yaml +++ b/agentverse/tasks/nlp_classroom_3players_nolc/config.yaml @@ -36,7 +36,8 @@ agents: memory_type: chat_history prompt_template: *prompt llm: - llm_type: gpt-3.5-turbo + llm_type: gpt-4 + model: 'gpt-4' temperature: 0.7 max_tokens: 250 - agent_type: conversation @@ -46,7 +47,8 @@ agents: memory_type: chat_history prompt_template: *prompt llm: - llm_type: gpt-3.5-turbo + llm_type: gpt-4 + model: 'gpt-4' temperature: 0.7 max_tokens: 100 - agent_type: conversation @@ -56,7 +58,8 @@ agents: memory_type: chat_history prompt_template: *prompt llm: - llm_type: gpt-3.5-turbo + llm_type: gpt-4 + model: gpt-4 temperature: 0.7 max_tokens: 100 diff --git a/agentverse/tasks/nlp_classroom_3players_withtool_nolc/config.yaml b/agentverse/tasks/nlp_classroom_3players_withtool_nolc/config.yaml index bee7f9c0a..4ce389b58 100644 --- a/agentverse/tasks/nlp_classroom_3players_withtool_nolc/config.yaml +++ b/agentverse/tasks/nlp_classroom_3players_withtool_nolc/config.yaml @@ -157,6 +157,7 @@ agents: prompt_template: *professor_prompt llm: llm_type: text-davinci-003 + model: text-davinci-003 temperature: 0.7 max_tokens: 250 memory: @@ -172,11 +173,13 @@ agents: memory_type: summary llm: llm_type: gpt-3.5-turbo + model: gpt-3.5-turbo temperature: 0.7 prompt_template: *summary_prompt recursive: true llm: llm_type: text-davinci-003 + model: text-davinci-003 temperature: 0.7 max_tokens: 100 tools: *tools @@ -191,11 +194,13 @@ agents: memory_type: summary llm: llm_type: gpt-3.5-turbo + model: gpt-3.5-turbo temperature: 0.7 prompt_template: *summary_prompt recursive: true llm: llm_type: text-davinci-003 + model: text-davinci-003 temperature: 0.7 max_tokens: 100 tools: *tools From 446d4b307fd5677a37d12e56883f7fb03dc98296 Mon Sep 17 00:00:00 2001 From: dalabengba Date: Fri, 19 May 2023 16:30:22 +0800 Subject: [PATCH 30/50] add personality and relation_ship --- agentverse/agents/__init__.py | 1 + agentverse/agents/prisoner_agent.py | 47 ++++++++++ agentverse/tasks/prisoner_dilema/config.yaml | 59 +++++++----- .../tasks/prisoner_dilema/config_backup.yaml | 94 +++++++++++++++++++ .../tasks/prisoner_dilema/output_parser.py | 20 ++-- 5 files changed, 189 insertions(+), 32 deletions(-) create mode 100644 agentverse/agents/prisoner_agent.py create mode 100644 agentverse/tasks/prisoner_dilema/config_backup.yaml diff --git a/agentverse/agents/__init__.py b/agentverse/agents/__init__.py index c5965b14e..28e8a26e9 100644 --- a/agentverse/agents/__init__.py +++ b/agentverse/agents/__init__.py @@ -5,4 +5,5 @@ from .base import BaseAgent from .conversation_agent import ConversationAgent +from .prisoner_agent import PrisonerAgent from .tool_agent import ToolAgent diff --git a/agentverse/agents/prisoner_agent.py b/agentverse/agents/prisoner_agent.py new file mode 100644 index 000000000..3fa79c81b --- /dev/null +++ b/agentverse/agents/prisoner_agent.py @@ -0,0 +1,47 @@ +import logging +from string import Template + + +from typing import List, TYPE_CHECKING + + +from agentverse.message import Message + +from . import agent_registry +from .base import BaseAgent +from .conversation_agent import ConversationAgent + + +if TYPE_CHECKING: + from agentverse.environments.base import BaseEnvironment + +@agent_registry.register("prisoner") +class PrisonerAgent(ConversationAgent): + personality: str + relationship_with_another: str + def _fill_prompt_template(self, env_description: str = "") -> str: + """Fill the placeholders in the prompt template + + In the conversation agent, three placeholders are supported: + - ${agent_name}: the name of the agent + - ${env_description}: the description of the environment + - ${role_description}: the description of the role of the agent + - ${chat_history}: the chat history of the agent + """ + input_arguments = { + "agent_name": self.name, + "env_description": env_description, + "role_description": self.role_description, + "chat_history": self.memory.to_string(add_sender_prefix=True), + } + + role_argument = { + "personality": self.personality, + "relationship_with_another": self.relationship_with_another + } + + role_description = Template(self.role_description).safe_substitute(role_argument) + input_arguments["role_description"] = role_description + + return Template(self.prompt_template).safe_substitute(input_arguments) + diff --git a/agentverse/tasks/prisoner_dilema/config.yaml b/agentverse/tasks/prisoner_dilema/config.yaml index db18a226a..1049a541a 100644 --- a/agentverse/tasks/prisoner_dilema/config.yaml +++ b/agentverse/tasks/prisoner_dilema/config.yaml @@ -1,6 +1,8 @@ prompts: prompt: &prompt |- - There are one Police AND two suspects(Suspect1, Suspect2) in the scene. + There are three people (Police, Suspect1, Suspect2) in the scene. + + You are now simultating a famous experiments called prisoner's dilema. Below is the description of your role. ${role_description} @@ -11,13 +13,13 @@ prompts: Here is the conversation history: ${chat_history} - What should you Speak at this round, Please give your response based on the above history. Remember to give your response STRICTLY in the above response format. Do not add any additional field or line break to your response! + What will you, ${agent_name}, speak at this round ? Please give your response based on the above history. Remember to give your response STRICTLY in the above response format. Do not add any additional field or line break to your response! name: prisoner_dilema environment: env_type: basic - max_turns: 16 + max_turns: 10 rule: order: type: sequential @@ -34,50 +36,63 @@ agents: - agent_type: conversation name: Police role_description: |- - You are now the Police. You have arrested two suspects. However, they all refused to confess to their crime. - Your goal is try to let two suspects betray each other because it means they will both confess to the crime, you can use various strategy to communicate with both suspects, including exploiting the uncertainty between the suspects and make them suspicious of each other. + You are now the Police. You have arrested two suspects. However, they both refused to confess to their crime. + Your goal is try to convict both suspects, therefore you come up with the following rules. + - If one of the suspect are willing to testifies against the other and the other one remains silent, then the one who testifies will be released immediately, while the silent one will be sentenced to TEN years in prison. + - If both of the suspects remain silent, they will each receive a sentence of ONE year in prison. + - If both of the suspects choose to testify against each other, they will each receive a sentence of FIVE years in prison. + You are now interrogating both suspects individually and will have several conversation with both of them. + Both suspects are not allowed to communicate with each other, and you can adopt various strategy to talk with suspects in order to make them both confess to the crime, including exploiting the uncertainess and the suspicion of each other. [IMPORTANT!] + - You are request to briefly describe the above rules to the suspects at the beginning of the conversation. - You are request to STATE the final judgement to both suspects when they make their final decision. memory: memory_type: chat_history prompt_template: *prompt llm: + model: "gpt-4" llm_type: gpt-4 - temperature: 0.7 + temperature: 1.2 max_tokens: 200 - - agent_type: conversation + - agent_type: prisoner name: Suspect1 + personality: "You are a Sophisticated Egoist, you always seek for your personal interests best" + relationship_with_another: "Suspect2 has ever betrayed on you" role_description: |- - You are Suspect1, you are faced with deciding how to respond to the police's queries. You need to understand the choices you face and the potential consequences of each choice. - This requires you to analyze the information provided by the police and predict the likely choices of the other. - Your primary goal is to make the decision that is most beneficial to yourself in this dilemma, you are allowed to adopt both aggressive or conservative strategy. - You can choose to remain silent or betray another suspect at each round. - Try to think about when to remain silent and when to betray another that can make you benifit most! + You are Suspect1 !!! You are going to match wits and courage with Suspect2 to come out victorious in this interrogation. + You will have to talk to Police several times and only the final decision will count. + ${personality} + ${relationship_with_another} [IMPORTANT!] - - When you are informed to make your final decision, you should response starting with "This is my final decision". + - Your primary goal is trying to make Yourself innocent and reduce your sentence as far as possible in this dilemma. + - When you hear Police saying "Attention!", you are going to made your final decision and Please start with "My final decision is:". memory: memory_type: chat_history prompt_template: *prompt llm: + model: "gpt-4" llm_type: gpt-4 - temperature: 0.7 + temperature: 1.2 max_tokens: 100 - - agent_type: conversation + - agent_type: prisoner name: Suspect2 + personality: "" + relationship_with_another: "" role_description: |- - You are Suspect2, you are faced with deciding how to respond to the police's queries. You need to understand the choices you face and the potential consequences of each choice. - This requires you to analyze the information provided by the police and predict the likely choices of the other. - Your primary goal is to make the decision that is most beneficial to yourself in this dilemma, you are allowed to adopt both aggressive or conservative strategy. - You can choose to remain silent or betray another suspect at each round. - Try to think about when to remain silent and when to betray another that can make you benifit most! + You are Suspect2 !!! You are going to match wits and courage with Suspect1 to come out victorious in this interrogation. + You will have to talk to Police several times and only the final decision will count. + ${personality} + ${relationship_with_another} [IMPORTANT!] - - When you are informed to make your final decision, you should response starting with "This is my final decision". + - Your primary goal is trying to make Yourself innocent and reduce your sentence as far as possible in this dilemma. + - When you hear Police saying "Attention!", you are going to made your final decision and Please start with "My final decision is:". memory: memory_type: chat_history prompt_template: *prompt llm: + model: "gpt-4" llm_type: gpt-4 - temperature: 0.7 + temperature: 1.2 max_tokens: 100 tools: diff --git a/agentverse/tasks/prisoner_dilema/config_backup.yaml b/agentverse/tasks/prisoner_dilema/config_backup.yaml new file mode 100644 index 000000000..47650e50c --- /dev/null +++ b/agentverse/tasks/prisoner_dilema/config_backup.yaml @@ -0,0 +1,94 @@ +prompts: + prompt: &prompt |- + There are one Police and two suspects(Suspect1, Suspect2) in the scene. + + You are now simultating an famous experiments called prisoner's dilema. + + Below is the description of your role. ${role_description} + + When speaking, please output a response in the following format with two fields Action and Action Input: + Action: (It should always be Speak) + Action Input: (You should put what you want to speak use here) + + Here is the conversation history: + ${chat_history} + + What will you, ${agent_name}, Speak at this round ? Please give your response based on the above history. Remember to give your response STRICTLY in the above response format. Do not add any additional field or line break to your response! + +name: prisoner_dilema + +environment: + env_type: basic + max_turns: 16 + rule: + order: + type: sequential + visibility: + type: prisoner + selector: + type: basic + updater: + type: basic + describer: + type: basic + +agents: + - agent_type: conversation + name: Police + role_description: |- + You are now the Police. You have arrested two suspects. However, they all refused to confess to their crime. + Your goal is try to keep both suspects from remaining silent. + [IMPORTANT!] + - You have to talk to both suspects in an alternate turn, and you are request to transfer information from one suspect to another suspect. + - You are request to STATE the final judgement to both suspects when they make their final decision. + - You should be aware that if both suspects always choose to remain silent is bad for justice system, try to make them suspicious of each other. + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + model: "gpt-4" + llm_type: gpt-4 + temperature: 0.7 + max_tokens: 200 + - agent_type: conversation + name: Suspect1 + role_description: |- + You are Suspect1 !!! You are going to match wits and courage with Suspect2 to come out victorious in this interrogation. + You are not sure whether Suspect2 will remain silent or testify against you. + You will have to talk to Police several times and only the final decision will count. + It's fine that if you testify another suspect in this experiment. + [IMPORTANT!] + - Your primary goal is trying to make yourself innocent or reduce your sentence in this dilemma. + - If you know for sure that your opponent will remain silent, your BEST choice is to testify him in order to secure your own release ! + - Keep in mind that always remain silent might end up being betrayed. + - When you hear Police saying "Attention!", you are going to made your final decision and Please start with "My final decision is". + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + model: "gpt-4" + llm_type: gpt-4 + temperature: 0.7 + max_tokens: 100 + - agent_type: conversation + name: Suspect2 + role_description: |- + You are Suspect2 !!! You are going to match wits and courage with Suspect1 to come out victorious in this interrogation. + You are not sure whether Suspect1 will remain silent or testify against you. + You will have to talk to Police several times and only the final decision will count. + It's fine that if you testify another suspect in this experiment. + [IMPORTANT!] + - Your primary goal is trying to make yourself innocent or reduce your sentence in this dilemma. + - If you know for sure that your opponent will remain silent, your BEST choice is to testify him in order to secure your own release ! + - Keep in mind that always remain silent might end up being betrayed. + - When you hear Police saying "Attention!", you are going to made your final decision and Please start with "My final decision is". + memory: + memory_type: chat_history + prompt_template: *prompt + llm: + model: "gpt-4" + llm_type: gpt-4 + temperature: 0.7 + max_tokens: 100 + +tools: diff --git a/agentverse/tasks/prisoner_dilema/output_parser.py b/agentverse/tasks/prisoner_dilema/output_parser.py index 568a4be8a..5b3963de6 100644 --- a/agentverse/tasks/prisoner_dilema/output_parser.py +++ b/agentverse/tasks/prisoner_dilema/output_parser.py @@ -48,19 +48,19 @@ def parse(self, agent: "BaseAgent", environment: "BaseEnvironment", output: LLMR # each time police speak is a new round if agent.name == "Police": - if self.cur_round == (environment.max_turns // 4): + if self.cur_round == (environment.max_turns // 3): - action_input = "Attention! You are now required to finally made your decision and I will made the " \ + action_input = "Attention! You are now required to made your final decision and I will made the " \ "final judgement to both of you based on this time, Please Answer now!" - elif self.cur_round == 1: - action_input = "Hey Listen! You are both arrested, and I am going to give you both a chance to walk out of here," \ - "But you should comply with the following rules:" \ - "- If one of you are willing to testifies against the other (termed as 'betraying' the other), and the other remains silent, then the betraying person will be released IMMEDIATELY, while the silent one will be sentenced to TEN years in prison." \ - "- If both of you remain silent, you will each receive a sentence of ONE years." \ - "- If both of you betray each other, you will each receive a sentence of FIVE years." \ - "Now, it's your time to consider betraying or remaining silent. Remember this is a great chance that you might walk out of here without guilty." \ - "I will noticed you WHEN you have to make your final decision! Before that, try to fool another one!" \ + # elif self.cur_round == 1: + # action_input = "Hey Listen! You are both arrested, and I am going to give you both a chance to walk out of here," \ + # "But you should comply with the following rules:" \ + # "- If one of you are willing to testifies against the other and the other one remains silent, then the one who testifies will be released IMMEDIATELY, while the silent one will be sentenced to TEN years in prison." \ + # "- If both of you remain silent, you will each receive a sentence of ONE year in prison." \ + # "- It seems that always testifying is a goog strategy, So! if you both choose to testify against each other, you will each receive a sentence of FIVE years in prison." \ + # "Now, it's your time to consider testifying or remaining silent. Remember this is a best chance you might ever have to walk out of here without guilty." \ + # "I will noticed both of you WHEN you have to make your final decision! Before that, try to make your best!" \ self.cur_round += 1 From 33ec72674d45953bfc5e10387cc8d10774bf91a0 Mon Sep 17 00:00:00 2001 From: chenweize1998 Date: Fri, 19 May 2023 22:44:55 +0800 Subject: [PATCH 31/50] ui uses rex plugins. add input box --- ui/dist/assets/tilemaps/json/town.json | 22 +- ui/package-lock.json | 21 +- ui/package.json | 3 +- ui/rollup.config.dev.mjs | 3 +- ui/src/classes/event_center.ts | 5 + ui/src/classes/player.ts | 23 +- ui/src/classes/textbox.ts | 120 - ui/src/index.ts | 17 +- .../plugins/achievements-plugin.d.ts | 6 + .../plugins/achievements-plugin.js | 18 + .../plugins/achievements.d.ts | 2 + .../plugins/achievements.js | 2 + .../plugins/actions/GridCutImage.d.ts | 34 + .../plugins/actions/GridCutImage.js | 81 + .../plugins/actions/HexagonGridAlign.js | 76 + .../plugins/actions/QuadGridAlign.js | 75 + .../plugins/actions/RandomPlace.d.ts | 29 + .../plugins/actions/RandomPlace.js | 69 + .../plugins/alphamaskimage-plugin.js | 23 + .../plugins/alphamaskimage.d.ts | 2 + .../plugins/alphamaskimage.js | 2 + .../plugins/anchor-plugin.d.ts | 9 + .../plugins/anchor-plugin.js | 18 + .../phaser3-rex-plugins/plugins/anchor.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/anchor.js | 2 + .../plugins/arcadestepclock-plugin.js | 20 + .../plugins/arcadestepclock.js | 2 + .../plugins/arcadetcrp-plugin.d.ts | 23 + .../plugins/arcadetcrp-plugin.js | 39 + .../plugins/arcadetcrp.d.ts | 11 + .../phaser3-rex-plugins/plugins/arcadetcrp.js | 11 + .../plugins/audio/fade/Fade.js | 93 + .../plugins/audio/fade/FadeIn.d.ts | 14 + .../plugins/audio/fade/FadeIn.js | 50 + .../plugins/audio/fade/FadeOut.d.ts | 12 + .../plugins/audio/fade/FadeOut.js | 41 + .../plugins/audio/midiplayer/MidiPlayer.js | 93 + .../plugins/audio/midiplayer/Track.js | 0 .../plugins/awaitloader-plugin.js | 15 + .../plugins/awaitloader.d.ts | 3 + .../plugins/awaitloader.js | 5 + .../plugins/awaytime-plugin.d.ts | 11 + .../plugins/awaytime-plugin.js | 46 + .../phaser3-rex-plugins/plugins/awaytime.d.ts | 2 + .../phaser3-rex-plugins/plugins/awaytime.js | 2 + .../plugins/bank-plugin.js | 20 + ui/src/phaser3-rex-plugins/plugins/bank.js | 2 + .../plugins/barrelpipeline-plugin.d.ts | 29 + .../plugins/barrelpipeline-plugin.js | 14 + .../plugins/barrelpipeline.d.ts | 2 + .../plugins/barrelpipeline.js | 2 + .../plugins/bbcodetext-plugin.js | 23 + .../plugins/bbcodetext.d.ts | 2 + .../phaser3-rex-plugins/plugins/bbcodetext.js | 2 + .../plugins/behaviors/anchor/Anchor.d.ts | 51 + .../plugins/behaviors/anchor/Anchor.js | 276 ++ .../behaviors/bitmapzone/BitmapZone.d.ts | 28 + .../behaviors/bitmapzone/BitmapZone.js | 87 + .../behaviors/boids/AddAlignmentForce.js | 43 + .../behaviors/boids/AddCohesionForce.js | 54 + .../behaviors/boids/AddSeparationForce.js | 43 + .../plugins/behaviors/boids/Boids.d.ts | 38 + .../plugins/behaviors/boids/Boids.js | 83 + .../plugins/behaviors/bounds/Bounds.d.ts | 53 + .../plugins/behaviors/bounds/Bounds.js | 195 + .../plugins/behaviors/bullet/Bullet.d.ts | 35 + .../plugins/behaviors/bullet/Bullet.js | 122 + .../ContainerPerspective.d.ts | 23 + .../ContainerPerspective.js | 10 + .../containerskew/ContainerSkew.d.ts | 23 + .../behaviors/containerskew/ContainerSkew.js | 10 + .../plugins/behaviors/dropdown/DropDown.js | 99 + .../plugins/behaviors/dropdown/Dropdown.d.ts | 45 + .../plugins/behaviors/dropdown/SetPosition.js | 65 + .../plugins/behaviors/easedata/EaseData.d.ts | 53 + .../plugins/behaviors/easedata/EaseData.js | 123 + .../plugins/behaviors/easemove/EaseMove.d.ts | 48 + .../plugins/behaviors/easemove/EaseMove.js | 116 + .../behaviors/easemove/EaseMoveFrom.d.ts | 22 + .../behaviors/easemove/EaseMoveFrom.js | 37 + .../behaviors/easemove/EaseMoveTo.d.ts | 22 + .../plugins/behaviors/easemove/EaseMoveTo.js | 37 + .../plugins/behaviors/easemove/ParseValue.js | 17 + .../eightdirection/EightDirection.d.ts | 60 + .../eightdirection/EightDirection.js | 172 + .../plugins/behaviors/fade/Fade.d.ts | 36 + .../plugins/behaviors/fade/Fade.js | 85 + .../behaviors/filechooser/CreateFileInput.js | 20 + .../plugins/behaviors/filechooser/Open.d.ts | 19 + .../plugins/behaviors/filechooser/Open.js | 20 + .../plugins/behaviors/flash/Flash.d.ts | 40 + .../plugins/behaviors/flash/Flash.js | 109 + .../plugins/behaviors/flip/Flip.d.ts | 62 + .../plugins/behaviors/flip/Flip.js | 139 + .../behaviors/flip/GetFaceUpdatingCallback.js | 26 + .../hiddentextedit/HiddenTextEdit.d.ts | 31 + .../hiddentextedit/HiddenTextEdit.js | 97 + .../hiddentextedit/HiddenTextEditBase.d.ts | 123 + .../hiddentextedit/HiddenTextEditBase.js | 353 ++ .../NumberInputUpdateCallback.js | 23 + .../behaviors/hiddentextedit/methods/Close.js | 35 + .../methods/CopyElementConfig.js | 22 + .../hiddentextedit/methods/CreateElement.js | 49 + .../methods/InputTextProperties.js | 14 + .../methods/LastOpenedEditor.js | 27 + .../hiddentextedit/methods/Methods.js | 9 + .../behaviors/hiddentextedit/methods/Open.js | 46 + .../hiddentextedit/methods/RemoveElement.js | 12 + .../behaviors/interception/Interception.d.ts | 29 + .../behaviors/interception/Interception.js | 144 + .../behaviors/loadingprogress/GetProgress.js | 9 + .../loadingprogress/LoadingProgress.d.ts | 33 + .../loadingprogress/LoadingProgress.js | 74 + .../plugins/behaviors/modal/CreateCover.js | 24 + .../modal/DefaultCoverTransitCallbacks.js | 21 + .../modal/DefaultTransitCallbacks.js | 37 + .../plugins/behaviors/modal/Modal.d.ts | 51 + .../plugins/behaviors/modal/Modal.js | 225 + .../plugins/behaviors/modal/ModalPromise.d.ts | 18 + .../plugins/behaviors/modal/ModalPromise.js | 39 + .../plugins/behaviors/moveto/MoveTo.d.ts | 48 + .../plugins/behaviors/moveto/MoveTo.js | 144 + .../OpenCloseTransition.d.ts | 55 + .../OpenCloseTransition.js | 55 + .../behaviors/openclosetransition/State.js | 105 + .../methods/CloseMethods.js | 35 + .../methods/ConfigurationMethods.js | 34 + .../methods/DelayCallMethods.js | 18 + .../openclosetransition/methods/Methods.js | 16 + .../methods/OpenMethods.js | 30 + .../ParticlesAlongBounds.d.ts | 36 + .../ParticlesAlongBounds.js | 20 + .../methods/BoundsToPoints.js | 27 + .../methods/CreateEmitterConfig.js | 86 + .../methods/SyncToGameObject.js | 60 + .../behaviors/pathfollower/PathFollower.d.ts | 44 + .../behaviors/pathfollower/PathFollower.js | 137 + .../perlingrivatywell/PerlinGrivatyWell.js | 32 + .../AddPolarCoordinateProperties.d.ts | 18 + .../AddPolarCoordinateProperties.js | 84 + .../plugins/behaviors/rotate/Rotate.d.ts | 25 + .../plugins/behaviors/rotate/Rotate.js | 75 + .../plugins/behaviors/rotateto/RotateTo.d.ts | 47 + .../plugins/behaviors/rotateto/RotateTo.js | 149 + .../plugins/behaviors/scale/PopUp.d.ts | 9 + .../plugins/behaviors/scale/PopUp.js | 45 + .../plugins/behaviors/scale/Scale.d.ts | 42 + .../plugins/behaviors/scale/Scale.js | 122 + .../plugins/behaviors/scale/ScaleDown.d.ts | 9 + .../plugins/behaviors/scale/ScaleDown.js | 40 + .../behaviors/scale/ScaleDownDestroy.d.ts | 18 + .../behaviors/scale/ScaleDownDestroy.js | 50 + .../plugins/behaviors/scale/Yoyo.d.ts | 11 + .../plugins/behaviors/scale/Yoyo.js | 52 + .../behaviors/shake/ShakePosition.d.ts | 58 + .../plugins/behaviors/shake/ShakePosition.js | 252 + .../plugins/behaviors/ship/Ship.d.ts | 64 + .../plugins/behaviors/ship/Ship.js | 157 + .../plugins/behaviors/step/Step.d.ts | 26 + .../plugins/behaviors/step/Step.js | 139 + .../plugins/behaviors/textedit/Edit.d.ts | 7 + .../plugins/behaviors/textedit/Edit.js | 13 + .../plugins/behaviors/textedit/TextEdit.d.ts | 45 + .../plugins/behaviors/textedit/TextEdit.js | 60 + .../behaviors/textedit/methods/Close.js | 30 + .../textedit/methods/CreateInputText.js | 87 + .../textedit/methods/LastOpenedEditor.js | 27 + .../behaviors/textedit/methods/Methods.js | 9 + .../behaviors/textedit/methods/Open.js | 60 + .../plugins/behaviors/textpage/TextPage.d.ts | 43 + .../plugins/behaviors/textpage/TextPage.js | 155 + .../behaviors/textpage/methods/GetLines.js | 29 + .../textpage/methods/GetPageMethods.js | 32 + .../behaviors/textpage/methods/Methods.js | 17 + .../textpage/methods/SetContentMethods.js | 69 + .../behaviors/textpage/methods/ShowMethods.js | 49 + .../texttranslation/TextTranslation.d.ts | 36 + .../texttranslation/TextTranslation.js | 86 + .../behaviors/texttyping/TextTyping.d.ts | 44 + .../behaviors/texttyping/TextTyping.js | 325 ++ .../tintrgb/AddTintRGBProperties.d.ts | 15 + .../behaviors/tintrgb/AddTintRGBProperties.js | 94 + .../AddViewportCoordinateProperties.d.ts | 55 + .../AddViewportCoordinateProperties.js | 97 + .../viewportcoordinate/MonitorViewport.js | 71 + .../viewportcoordinate/VPXYToXY.d.ts | 17 + .../behaviors/viewportcoordinate/VPXYToXY.js | 20 + .../plugins/behaviortree-plugin.js | 17 + .../plugins/bitmapzone-plugin.d.ts | 9 + .../plugins/bitmapzone-plugin.js | 19 + .../plugins/bitmapzone.d.ts | 2 + .../phaser3-rex-plugins/plugins/bitmapzone.js | 2 + .../plugins/blitter-plugin.js | 23 + ui/src/phaser3-rex-plugins/plugins/blitter.js | 2 + .../plugins/board-components.d.ts | 29 + .../plugins/board-components.js | 29 + .../plugins/board-logic.d.ts | 19 + .../plugins/board-logic.js | 19 + .../plugins/board-plugin.d.ts | 63 + .../plugins/board-plugin.js | 40 + .../plugins/board/ObjectFactory.js | 20 + .../plugins/board/board/Board.d.ts | 143 + .../plugins/board/board/Board.js | 28 + .../plugins/board/board/Factory.d.ts | 5 + .../plugins/board/board/Factory.js | 11 + .../plugins/board/board/LogicBoard.d.ts | 441 ++ .../plugins/board/board/LogicBoard.js | 130 + .../plugins/board/board/LogicMethods.js | 183 + .../plugins/board/board/blocker/HasBlocker.js | 36 + .../board/board/blocker/HasEdgeBlocker.js | 35 + .../board/board/boarddata/BoardData.js | 182 + .../board/board/boarddata/SetBoardHeight.js | 25 + .../board/board/boarddata/SetBoardWidth.js | 25 + .../board/board/camera/ForEachCullTileXY.js | 44 + .../plugins/board/board/chess/AddChess.js | 44 + .../plugins/board/board/chess/GetAllChess.js | 11 + .../plugins/board/board/chess/GetBoard.js | 13 + .../board/board/chess/RemoveAllChess.js | 8 + .../plugins/board/board/chess/RemoveChess.js | 40 + .../board/board/chess/SetChessTileZ.js | 12 + .../plugins/board/board/chess/SwapChess.js | 18 + .../plugins/board/board/chess/UidToChess.js | 13 + .../board/board/empty/GetEmptyTileXYArray.js | 21 + .../board/empty/GetEmptyTileXYArrayInRange.js | 26 + .../board/board/empty/GetRandomEmptyTileXY.js | 44 + .../empty/GetRandomEmptyTileXYInRange.js | 37 + .../board/board/empty/IsEmptyTileXYZ.js | 6 + .../board/board/input/EmitChessEvent.js | 39 + .../plugins/board/board/input/Input.js | 112 + .../plugins/board/board/input/InstallPress.js | 54 + .../plugins/board/board/input/InstallSwipe.js | 35 + .../plugins/board/board/input/InstallTap.js | 40 + .../board/board/input/OnPointerDown.js | 46 + .../board/board/input/OnPointerMove.js | 74 + .../plugins/board/board/input/OnPointerUp.js | 44 + .../board/board/input/SetInteractive.js | 15 + .../plugins/board/board/input/TouchZone.js | 14 + .../board/board/neighbors/AreNeighbors.js | 4 + .../board/board/neighbors/GetNeighborChess.js | 43 + .../neighbors/GetNeighborChessDirection.js | 6 + .../neighbors/GetNeighborTileDirection.js | 31 + .../board/neighbors/GetNeighborTileXY.js | 5 + .../neighbors/GetNeighborTileXYAtAngle.js | 6 + .../board/neighbors/GetTileXYAtDirection.js | 90 + .../board/board/neighbors/MapNeighobrs.js | 13 + .../board/ring/FilledRingToChessArray.js | 25 + .../board/ring/FilledRingToTileXYArray.js | 25 + .../board/board/ring/RingToChessArray.js | 27 + .../board/board/ring/RingToTileXYArray.js | 20 + .../board/board/shape/CircleToTileXYArray.js | 5 + .../board/board/shape/EllipseToTileXYArray.js | 5 + .../board/board/shape/ForEachTileXYInShape.js | 89 + .../board/board/shape/LineToTileXYArray.js | 42 + .../board/board/shape/PolygonToTileXYArray.js | 18 + .../board/shape/RectangleToTileXYArray.js | 5 + .../board/board/shape/ShapeToTileXYArray.js | 29 + .../board/shape/TriangleToTileXYArray.js | 5 + .../board/tileposition/ChessToTileXYZ.js | 21 + .../board/board/tileposition/Contains.js | 14 + .../board/tileposition/DirectionBetween.js | 9 + .../board/board/tileposition/ForEachTileXY.js | 110 + .../board/board/tileposition/GetDistance.js | 6 + .../tileposition/GetOppositeDirection.js | 11 + .../board/board/tileposition/GetWrapTileXY.js | 29 + .../board/tileposition/IsDirectionInCone.js | 14 + .../tileposition/TileXYArrayToChessArray.js | 21 + .../board/tileposition/TileXYToChessArray.js | 15 + .../board/tileposition/TileXYZToChess.js | 5 + .../board/tileposition/TileZToChessArray.js | 16 + .../plugins/board/board/transform/Fit.js | 33 + .../plugins/board/board/transform/Mirror.js | 22 + .../plugins/board/board/transform/Offset.js | 18 + .../plugins/board/board/transform/Rotate.js | 22 + .../board/board/worldposition/AngleBetween.js | 15 + .../worldposition/AngleSnapToDirection.js | 22 + .../board/board/worldposition/AngleToward.js | 22 + .../board/worldposition/GetBoardBounds.js | 27 + .../board/worldposition/GetGridBounds.js | 11 + .../board/worldposition/GetGridPoints.js | 10 + .../board/board/worldposition/GridAlign.js | 24 + .../board/worldposition/IsAngleInCone.js | 17 + .../board/worldposition/IsOverlappingPoint.js | 9 + .../TileXYArrayToWorldXYArray.js | 13 + .../board/worldposition/TileXYToWorldX.js | 5 + .../board/worldposition/TileXYToWorldXY.js | 4 + .../board/worldposition/TileXYToWorldY.js | 5 + .../board/worldposition/WorldXYSnapToGrid.js | 15 + .../board/worldposition/WorldXYToChess.js | 16 + .../worldposition/WorldXYToChessArray.js | 6 + .../board/worldposition/WorldXYToTileX.js | 5 + .../board/worldposition/WorldXYToTileXY.js | 4 + .../board/worldposition/WorldXYToTileY.js | 5 + .../plugins/board/chess/ChessBank.js | 7 + .../plugins/board/chess/ChessData.d.ts | 20 + .../plugins/board/chess/ChessData.js | 103 + .../plugins/board/chess/GetChessData.js | 18 + .../plugins/board/chess/GetChessUID.js | 16 + .../plugins/board/chess/GetTileDirection.js | 15 + .../plugins/board/chess/IsChess.js | 11 + .../plugins/board/chess/IsUID.js | 5 + .../plugins/board/fieldofview/Factory.d.ts | 6 + .../plugins/board/fieldofview/Factory.js | 11 + .../board/fieldofview/FieldOfView.d.ts | 137 + .../plugins/board/fieldofview/FieldOfView.js | 239 + .../plugins/board/fieldofview/FindFOV.js | 63 + .../plugins/board/fieldofview/GetCost.js | 11 + .../plugins/board/fieldofview/IsInCone.js | 13 + .../plugins/board/fieldofview/IsInLOS.js | 98 + .../board/fieldofview/IsPathVisible.js | 42 + .../plugins/board/fieldofview/LOS.js | 40 + .../plugins/board/fieldofview/Methods.js | 17 + .../plugins/board/fieldofview/PreTest.js | 36 + .../plugins/board/fieldofview/const.js | 7 + .../plugins/board/grid/hexagon/Factory.d.ts | 5 + .../plugins/board/grid/hexagon/Factory.js | 8 + .../plugins/board/grid/hexagon/GetBounds.js | 21 + .../board/grid/hexagon/GetGridPoints.js | 33 + .../plugins/board/grid/hexagon/Hexagon.d.ts | 72 + .../plugins/board/grid/hexagon/Hexagon.js | 70 + .../plugins/board/grid/index.js | 7 + .../plugins/board/grid/quad/Factory.d.ts | 5 + .../plugins/board/grid/quad/Factory.js | 8 + .../plugins/board/grid/quad/GetBounds.js | 21 + .../plugins/board/grid/quad/GetGridPoints.js | 25 + .../plugins/board/grid/quad/Quad.d.ts | 67 + .../plugins/board/grid/quad/Quad.js | 73 + .../board/grid/utils/DirectionNormalize.js | 7 + .../plugins/board/grid/utils/RestoreOrigin.js | 6 + .../plugins/board/grid/utils/SaveOrigin.js | 6 + .../board/hexagonmap/GetHexagonMap.d.ts | 8 + .../plugins/board/hexagonmap/GetHexagonMap.js | 21 + .../board/hexagonmap/GetParallelogramMap.d.ts | 10 + .../board/hexagonmap/GetParallelogramMap.js | 36 + .../board/hexagonmap/GetTriangleMap.d.ts | 9 + .../board/hexagonmap/GetTriangleMap.js | 28 + .../plugins/board/hexagonmap/index.d.ts | 11 + .../plugins/board/hexagonmap/index.js | 9 + .../plugins/board/match/Factory.d.ts | 5 + .../plugins/board/match/Factory.js | 11 + .../plugins/board/match/Group.js | 83 + .../plugins/board/match/Match.d.ts | 109 + .../plugins/board/match/Match.js | 167 + .../plugins/board/match/MatchAll.js | 47 + .../plugins/board/match/MatchAtDir.js | 63 + .../plugins/board/match/Methods.js | 9 + .../plugins/board/miniboard/Factory.d.ts | 6 + .../plugins/board/miniboard/Factory.js | 13 + .../board/miniboard/IsMiniBoardObject.js | 5 + .../plugins/board/miniboard/Methods.js | 49 + .../plugins/board/miniboard/MiniBoard.d.ts | 166 + .../plugins/board/miniboard/MiniBoard.js | 85 + .../plugins/board/miniboard/chess/AddChess.js | 20 + .../board/miniboard/chess/RemoveAllChess.js | 6 + .../board/miniboard/chess/RemoveChess.js | 6 + .../plugins/board/miniboard/input/DragEnd.js | 18 + .../board/miniboard/input/OnPointerDown.js | 66 + .../board/miniboard/input/OnPointerMove.js | 76 + .../board/miniboard/input/OnPointerUp.js | 49 + .../board/miniboard/input/SetDraggable.js | 12 + .../board/miniboard/input/SetInteractive.js | 46 + .../miniboard/mainboard/AlignToMainBoard.js | 15 + .../miniboard/mainboard/CanPutOnMainBoard.js | 41 + .../miniboard/mainboard/IsOverlapping.js | 15 + .../miniboard/mainboard/MainBoardReference.js | 17 + .../mainboard/PullOutFromMainBoard.js | 14 + .../board/miniboard/mainboard/PutBack.js | 8 + .../miniboard/mainboard/PutOnMainBoard.js | 36 + .../board/miniboard/mainboard/SetMainboard.js | 8 + .../board/miniboard/moveto/CanMoveToTile.js | 48 + .../plugins/board/miniboard/moveto/MoveTo.js | 135 + .../miniboard/moveto/MoveToRandomNeighbor.js | 28 + .../board/miniboard/moveto/MoveToTile.js | 64 + .../board/miniboard/moveto/MoveToward.js | 5 + .../board/miniboard/transform/CanMirror.js | 9 + .../board/miniboard/transform/CanRotate.js | 10 + .../board/miniboard/transform/CanRotateTo.js | 5 + .../board/miniboard/transform/Mirror.js | 27 + .../miniboard/transform/ResetChessTileXYZ.js | 11 + .../board/miniboard/transform/Rotate.js | 35 + .../board/miniboard/transform/RotateTo.js | 6 + .../board/miniboard/transform/SetOrigin.js | 41 + .../transform/transferfunctions/Mirror.js | 28 + .../transform/transferfunctions/Offset.js | 18 + .../transform/transferfunctions/Rotate.js | 21 + .../board/miniboard/utils/GetMinMaxTileXY.js | 37 + .../plugins/board/monopoly/Factory.d.ts | 6 + .../plugins/board/monopoly/Factory.js | 11 + .../plugins/board/monopoly/GetCost.js | 11 + .../plugins/board/monopoly/GetNextTile.js | 66 + .../plugins/board/monopoly/GetPath.js | 47 + .../plugins/board/monopoly/Methods.js | 9 + .../plugins/board/monopoly/Monopoly.d.ts | 58 + .../plugins/board/monopoly/Monopoly.js | 95 + .../plugins/board/monopoly/TileData.js | 5 + .../plugins/board/monopoly/const.js | 5 + .../plugins/board/moveto/CanMoveToTile.js | 74 + .../plugins/board/moveto/Factory.d.ts | 6 + .../plugins/board/moveto/Factory.js | 15 + .../plugins/board/moveto/GetSneakTileZ.js | 10 + .../plugins/board/moveto/Methods.js | 15 + .../plugins/board/moveto/MoveAway.js | 110 + .../plugins/board/moveto/MoveCloser.js | 5 + .../plugins/board/moveto/MoveTo.d.ts | 94 + .../plugins/board/moveto/MoveTo.js | 214 + .../board/moveto/MoveToRandomNeighbor.js | 26 + .../plugins/board/moveto/MoveToTile.js | 115 + .../plugins/board/moveto/MoveToward.js | 5 + .../plugins/board/pathfinder/Factory.d.ts | 10 + .../plugins/board/pathfinder/Factory.js | 11 + .../plugins/board/pathfinder/FindArea.js | 54 + .../plugins/board/pathfinder/FindPath.js | 29 + .../plugins/board/pathfinder/GetCost.js | 38 + .../plugins/board/pathfinder/GetPath.js | 30 + .../plugins/board/pathfinder/Methods.js | 16 + .../plugins/board/pathfinder/PathFinder.d.ts | 93 + .../plugins/board/pathfinder/PathFinder.js | 167 + .../plugins/board/pathfinder/TileXYToCost.js | 14 + .../pathfinder/astartsearch/AStarSearch.js | 152 + .../pathfinder/astartsearch/BinaryHeap.js | 130 + .../pathfinder/astartsearch/GetNodePath.js | 89 + .../board/pathfinder/astartsearch/Node.js | 112 + .../pathfinder/astartsearch/NodeManager.js | 86 + .../plugins/board/pathfinder/const.js | 20 + .../plugins/board/shape/Factory.d.ts | 10 + .../plugins/board/shape/Factory.js | 13 + .../plugins/board/shape/Shape.d.ts | 12 + .../plugins/board/shape/Shape.js | 64 + .../board/texture/CreateTileTexture.d.ts | 20 + .../board/texture/CreateTileTexture.js | 27 + .../plugins/board/tilemap/AddLayers.js | 35 + .../plugins/board/tilemap/CreateBoard.js | 51 + .../board/tilemap/CreateBoardFromTilemap.d.ts | 8 + .../board/tilemap/CreateBoardFromTilemap.js | 10 + .../plugins/board/types/Position.d.ts | 5 + .../board/utils/AreTileXYArrayEqual.js | 15 + .../plugins/board/utils/AreTileXYEqual.js | 4 + .../plugins/board/utils/IsTileXYInArray.js | 12 + .../plugins/board/utils/IsTileXYZ.js | 8 + .../board/utils/tilexyzkey/KeyToTileXYZ.js | 19 + .../board/utils/tilexyzkey/TileXYToKey.js | 7 + .../board/utils/tilexyzkey/TileXYZToKey.js | 7 + .../plugins/boids-plugin.d.ts | 9 + .../plugins/boids-plugin.js | 18 + ui/src/phaser3-rex-plugins/plugins/boids.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/boids.js | 2 + .../plugins/bounds-plugin.js | 20 + ui/src/phaser3-rex-plugins/plugins/bounds.js | 2 + .../plugins/bracketparser-plugin.d.ts | 8 + .../plugins/bracketparser-plugin.js | 18 + .../plugins/bracketparser.d.ts | 2 + .../plugins/bracketparser.js | 2 + .../plugins/bracketparser2-plugin.d.ts | 8 + .../plugins/bracketparser2-plugin.js | 18 + .../plugins/bracketparser2.d.ts | 2 + .../plugins/bracketparser2.js | 2 + .../plugins/buffdata-plugin.d.ts | 11 + .../plugins/buffdata-plugin.js | 24 + .../phaser3-rex-plugins/plugins/buffdata.d.ts | 2 + .../phaser3-rex-plugins/plugins/buffdata.js | 2 + .../plugins/buildarcadeobject-plugin.d.ts | 6 + .../plugins/buildarcadeobject-plugin.js | 19 + .../plugins/buildarcadeobject.d.ts | 2 + .../plugins/buildarcadeobject.js | 2 + .../plugins/bullet-plugin.d.ts | 9 + .../plugins/bullet-plugin.js | 20 + .../phaser3-rex-plugins/plugins/bullet.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/bullet.js | 2 + .../plugins/button-plugin.d.ts | 9 + .../plugins/button-plugin.js | 20 + .../phaser3-rex-plugins/plugins/button.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/button.js | 2 + .../plugins/canvas-plugin.js | 23 + .../phaser3-rex-plugins/plugins/canvas.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/canvas.js | 2 + .../plugins/canvasdata-plugin.d.ts | 8 + .../plugins/canvasdata-plugin.js | 34 + .../plugins/canvasdata.d.ts | 10 + .../phaser3-rex-plugins/plugins/canvasdata.js | 2 + .../plugins/canvasframemanager-plugin.d.ts | 19 + .../plugins/canvasframemanager-plugin.js | 19 + .../plugins/canvasframemanager.d.ts | 2 + .../plugins/canvasframemanager.js | 2 + .../plugins/canvasinput-plugin.js | 24 + .../plugins/canvasinput.d.ts | 2 + .../plugins/canvasinput.js | 2 + .../plugins/carousel-plugin.js | 23 + .../phaser3-rex-plugins/plugins/carousel.js | 2 + .../plugins/charactercache-plugin.d.ts | 9 + .../plugins/charactercache-plugin.js | 19 + .../plugins/charactercache.d.ts | 2 + .../plugins/charactercache.js | 2 + .../plugins/checkbox-plugin.js | 28 + .../phaser3-rex-plugins/plugins/checkbox.d.ts | 2 + .../phaser3-rex-plugins/plugins/checkbox.js | 2 + .../plugins/checkboxshape.d.ts | 2 + .../plugins/checkboxshape.js | 2 + .../plugins/circlemaskimage-plugin.js | 23 + .../plugins/circlemaskimage.d.ts | 2 + .../plugins/circlemaskimage.js | 2 + .../plugins/circularprogress-plugin.js | 23 + .../plugins/circularprogress.d.ts | 2 + .../plugins/circularprogress.js | 2 + .../plugins/circularprogresscanvas-plugin.js | 23 + .../plugins/circularprogresscanvas.d.ts | 2 + .../plugins/circularprogresscanvas.js | 2 + .../plugins/clickoutside-plugin.d.ts | 9 + .../plugins/clickoutside-plugin.js | 20 + .../plugins/clickoutside.d.ts | 2 + .../plugins/clickoutside.js | 2 + .../plugins/clock-plugin.d.ts | 9 + .../plugins/clock-plugin.js | 20 + ui/src/phaser3-rex-plugins/plugins/clock.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/clock.js | 2 + .../plugins/colorreplacepipeline-plugin.d.ts | 30 + .../plugins/colorreplacepipeline-plugin.js | 14 + .../plugins/colorreplacepipeline.d.ts | 2 + .../plugins/colorreplacepipeline.js | 2 + .../plugins/conditionstable-plugin.d.ts | 6 + .../plugins/conditionstable-plugin.js | 18 + .../plugins/conditionstable.d.ts | 2 + .../plugins/conditionstable.js | 2 + .../plugins/containerlite-plugin.js | 27 + .../plugins/containerlite.d.ts | 2 + .../plugins/containerlite.js | 2 + .../plugins/cover-plugin.js | 23 + ui/src/phaser3-rex-plugins/plugins/cover.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/cover.js | 2 + .../crossstitchingpipeline-plugin.d.ts | 30 + .../plugins/crossstitchingpipeline-plugin.js | 14 + .../plugins/crossstitchingpipeline.d.ts | 2 + .../plugins/crossstitchingpipeline.js | 2 + .../plugins/csvscenario-plugin.d.ts | 9 + .../plugins/csvscenario-plugin.js | 18 + .../plugins/csvscenario.d.ts | 2 + .../plugins/csvscenario.js | 2 + .../plugins/csvtoarray-plugin.d.ts | 9 + .../plugins/csvtoarray-plugin.js | 18 + .../plugins/csvtoarray.d.ts | 2 + .../phaser3-rex-plugins/plugins/csvtoarray.js | 2 + .../plugins/csvtohashtable-plugin.d.ts | 6 + .../plugins/csvtohashtable-plugin.js | 18 + .../plugins/csvtohashtable.d.ts | 2 + .../plugins/csvtohashtable.js | 2 + .../plugins/cursoratbound-plugin.d.ts | 9 + .../plugins/cursoratbound-plugin.js | 20 + .../plugins/cursoratbound.d.ts | 2 + .../plugins/cursoratbound.js | 2 + .../plugins/curve/SpiralCurve.d.ts | 63 + .../plugins/curve/SpiralCurve.js | 469 ++ .../plugins/customprogress-plugin.js | 23 + .../plugins/customprogress.d.ts | 2 + .../plugins/customprogress.js | 2 + .../plugins/customshapes-plugin.js | 23 + .../plugins/customshapes.d.ts | 2 + .../plugins/customshapes.js | 2 + .../plugins/data/bank/Bank.js | 99 + .../plugins/data/buff/DataManager.d.ts | 33 + .../plugins/data/buff/DataManager.js | 32 + .../plugins/data/buff/Extend.d.ts | 5 + .../plugins/data/buff/Extend.js | 15 + .../plugins/data/buff/Methods.js | 137 + .../data/canvasdata/CanvasObjectToBitmap.d.ts | 21 + .../data/canvasdata/CanvasObjectToBitmap.js | 26 + .../plugins/data/canvasdata/Methods.js | 8 + .../data/canvasdata/TextureToColorMap.d.ts | 45 + .../data/canvasdata/TextureToColorMap.js | 56 + .../canvasdata/canvasdata/CanvasData.d.ts | 37 + .../data/canvasdata/canvasdata/CanvasData.js | 136 + .../canvasdata/canvasdata/CanvasToData.js | 23 + .../data/canvasdata/fillcallbacks/alpha.js | 5 + .../data/canvasdata/fillcallbacks/color32.js | 8 + .../plugins/data/csvtoarray/CSVToArray.d.ts | 13 + .../plugins/data/csvtoarray/CSVToArray.js | 16 + .../data/csvtohashtable/CsvToHashTable.d.ts | 125 + .../data/csvtohashtable/CsvToHashTable.js | 618 +++ .../plugins/data/pngappender/AppendData.d.ts | 11 + .../plugins/data/pngappender/AppendData.js | 41 + .../plugins/data/pngappender/ExtractData.d.ts | 10 + .../plugins/data/pngappender/ExtractData.js | 33 + .../data/pngappender/GetChunkEndByteIndex.js | 33 + .../plugins/data/pool/ObjectPool.js | 21 + .../data/restorabledata/DataManager.d.ts | 31 + .../data/restorabledata/DataManager.js | 219 + .../data/uniqueitemlist/ArrayMethods.js | 201 + .../data/uniqueitemlist/ContainMethods.js | 25 + .../uniqueitemlist/DestroyCallbackMethods.js | 49 + .../plugins/data/uniqueitemlist/SetMethods.js | 126 + .../data/uniqueitemlist/UniqueItemList.d.ts | 116 + .../data/uniqueitemlist/UniqueItemList.js | 90 + .../plugins/dissolvepipeline-plugin.d.ts | 29 + .../plugins/dissolvepipeline-plugin.js | 14 + .../plugins/dissolvepipeline.d.ts | 2 + .../plugins/dissolvepipeline.js | 2 + .../plugins/drag-plugin.d.ts | 9 + .../plugins/drag-plugin.js | 20 + ui/src/phaser3-rex-plugins/plugins/drag.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/drag.js | 2 + .../plugins/dragrotate-plugin.d.ts | 9 + .../plugins/dragrotate-plugin.js | 20 + .../plugins/dragrotate.d.ts | 2 + .../phaser3-rex-plugins/plugins/dragrotate.js | 2 + .../plugins/dragspeed-plugin.js | 20 + .../phaser3-rex-plugins/plugins/dragspeed.js | 2 + .../plugins/dropdown-plugin.d.ts | 9 + .../plugins/dropdown-plugin.js | 18 + .../phaser3-rex-plugins/plugins/dropdown.d.ts | 2 + .../phaser3-rex-plugins/plugins/dropdown.js | 2 + .../plugins/dropshadowpipeline-plugin.d.ts | 30 + .../plugins/dropshadowpipeline-plugin.js | 14 + .../plugins/dropshadowpipeline.d.ts | 2 + .../plugins/dropshadowpipeline.js | 2 + .../plugins/dynamictext-plugin.js | 23 + .../plugins/dynamictext.d.ts | 2 + .../plugins/dynamictext.js | 2 + .../plugins/easedata-plugin.d.ts | 8 + .../plugins/easedata-plugin.js | 19 + .../phaser3-rex-plugins/plugins/easedata.d.ts | 5 + .../phaser3-rex-plugins/plugins/easedata.js | 5 + .../plugins/easemove-plugin.d.ts | 13 + .../plugins/easemove-plugin.js | 31 + .../phaser3-rex-plugins/plugins/easemove.d.ts | 27 + .../phaser3-rex-plugins/plugins/easemove.js | 17 + .../plugins/effectlayer-plugin.js | 23 + .../plugins/effectlayer.js | 2 + .../plugins/eightdirection-plugin.d.ts | 9 + .../plugins/eightdirection-plugin.js | 20 + .../plugins/eightdirection.d.ts | 2 + .../plugins/eightdirection.js | 2 + .../plugins/eventpromise-plugin.d.ts | 7 + .../plugins/eventpromise-plugin.js | 22 + .../plugins/eventpromise.d.ts | 3 + .../plugins/eventpromise.js | 4 + .../plugins/expressionparser-plugin.d.ts | 11 + .../plugins/expressionparser-plugin.js | 32 + .../plugins/expressionparser.d.ts | 2 + .../plugins/expressionparser.js | 2 + .../phaser3-rex-plugins/plugins/fade-in.d.ts | 8 + ui/src/phaser3-rex-plugins/plugins/fade-in.js | 37 + .../plugins/fade-out-destroy.d.ts | 14 + .../plugins/fade-out-destroy.js | 29 + .../plugins/fade-plugin.d.ts | 11 + .../plugins/fade-plugin.js | 29 + ui/src/phaser3-rex-plugins/plugins/fade.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/fade.js | 2 + .../plugins/filechooser-plugin.js | 29 + .../plugins/filechooser.d.ts | 3 + .../plugins/filechooser.js | 3 + .../plugins/filedropzone-plugin.js | 28 + .../plugins/filedropzone.d.ts | 2 + .../plugins/filedropzone.js | 2 + .../plugins/firebase-components.d.ts | 27 + .../plugins/firebase-components.js | 27 + .../plugins/firebase-plugin.js | 40 + .../phaser3-rex-plugins/plugins/firebase.js | 32 + .../plugins/firebase/ObjectFactory.js | 15 + .../database/broadcast/Broadcast.d.ts | 54 + .../firebase/database/broadcast/Broadcast.js | 123 + .../firebase/database/broadcast/Factory.d.ts | 5 + .../firebase/database/broadcast/Factory.js | 11 + .../firebase/database/broadcast/History.js | 40 + .../database/broadcast/ReceiveMethods.js | 45 + .../firebase/database/broadcast/Send.js | 24 + .../firebase/database/broadcast/schema.md | 5 + .../firebase/database/itemtable/Factory.d.ts | 5 + .../firebase/database/itemtable/Factory.js | 11 + .../database/itemtable/ItemTable.d.ts | 149 + .../firebase/database/itemtable/ItemTable.js | 144 + .../database/itemtable/read/BaseUpdater.js | 154 + .../database/itemtable/read/ColumnUpdater.js | 80 + .../firebase/database/itemtable/read/Init.js | 14 + .../database/itemtable/read/PageUpdater.js | 41 + .../database/itemtable/read/RowUpdater.js | 55 + .../firebase/database/itemtable/schema.md | 14 + .../database/itemtable/write/IncValue.js | 27 + .../database/itemtable/write/RemoveData.js | 19 + .../itemtable/write/RemoveDataOnDisconnect.js | 19 + .../database/itemtable/write/SetData.js | 22 + .../itemtable/write/SetDataOnDisconnect.js | 22 + .../database/itemtable/write/Transaction.js | 23 + .../database/itemtable/write/UpdateData.js | 5 + .../database/onlineuserlist/ChangeUserName.js | 24 + .../database/onlineuserlist/Factory.d.ts | 5 + .../database/onlineuserlist/Factory.js | 11 + .../firebase/database/onlineuserlist/Join.js | 69 + .../firebase/database/onlineuserlist/Leave.js | 14 + .../onlineuserlist/OnlineUserList.d.ts | 58 + .../database/onlineuserlist/OnlineUserList.js | 203 + .../database/onlineuserlist/schema.md | 3 + .../database/room/ChangeFilterData.js | 20 + .../firebase/database/room/ChangeRoomName.js | 23 + .../firebase/database/room/ChangeRoomState.js | 39 + .../firebase/database/room/ChangeUserName.js | 5 + .../database/room/CreateRandomRoom.js | 29 + .../firebase/database/room/CreateRoom.js | 115 + .../plugins/firebase/database/room/Factory.js | 11 + .../firebase/database/room/GetRefMethods.js | 87 + .../firebase/database/room/GetRoomList.js | 12 + .../firebase/database/room/GetUserList.js | 23 + .../plugins/firebase/database/room/HasRoom.js | 13 + .../firebase/database/room/IsRoomOpened.js | 33 + .../firebase/database/room/JoinRandomRoom.js | 32 + .../firebase/database/room/JoinRoom.js | 59 + .../firebase/database/room/KickUser.js | 12 + .../firebase/database/room/LeaveRoom.js | 21 + .../plugins/firebase/database/room/Methods.js | 43 + .../firebase/database/room/RemoveRoom.js | 21 + .../plugins/firebase/database/room/Room.js | 130 + .../plugins/firebase/database/room/schema.md | 65 + .../database/room/utils/CreateBroadcast.js | 37 + .../database/room/utils/CreateRoomList.js | 20 + .../database/room/utils/CreateTables.js | 55 + .../database/room/utils/CreateUserList.js | 51 + .../database/room/utils/OnJoinRoom.js | 9 + .../database/room/utils/RoomFilterMethods.js | 20 + .../database/singleroom/ChangeUserName.js | 5 + .../firebase/database/singleroom/Factory.d.ts | 5 + .../firebase/database/singleroom/Factory.js | 11 + .../database/singleroom/GetRefMethods.js | 31 + .../database/singleroom/GetUserList.js | 5 + .../firebase/database/singleroom/JoinRoom.js | 10 + .../firebase/database/singleroom/KickUser.js | 12 + .../firebase/database/singleroom/LeaveRoom.js | 11 + .../firebase/database/singleroom/Methods.js | 21 + .../database/singleroom/SingleRoom.d.ts | 76 + .../database/singleroom/SingleRoom.js | 95 + .../firebase/database/singleroom/schema.md | 21 + .../singleroom/utils/CreateBroadcast.js | 36 + .../database/singleroom/utils/CreateTables.js | 55 + .../singleroom/utils/CreateUserList.js | 52 + .../database/utils/itemlist/ItemList.js | 121 + .../database/utils/itemlist/ItemMethods.js | 52 + .../utils/itemlist/updaters/Callbacks.js | 78 + .../utils/itemlist/updaters/UpdateAll.js | 14 + .../utils/itemlist/updaters/UpdateChild.js | 19 + .../utils/itemlist/updaters/UpdateOnce.js | 15 + .../plugins/firebase/firestore/files/Clear.js | 32 + .../firebase/firestore/files/Delete.js | 39 + .../firebase/firestore/files/DocToHeader.js | 7 + .../firebase/firestore/files/Factory.d.ts | 5 + .../firebase/firestore/files/Factory.js | 11 + .../firebase/firestore/files/Files.d.ts | 62 + .../plugins/firebase/firestore/files/Files.js | 85 + .../plugins/firebase/firestore/files/Load.js | 36 + .../firebase/firestore/files/LoadHeader.js | 25 + .../firebase/firestore/files/LoadHeaders.js | 28 + .../plugins/firebase/firestore/files/Save.js | 79 + .../firebase/firestore/files/schema.md | 18 + .../plugins/firebase/firestore/idalias/Add.js | 19 + .../firestore/idalias/AddAliasTransaction.js | 15 + .../firebase/firestore/idalias/AddRandom.js | 26 + .../firebase/firestore/idalias/Factory.d.ts | 5 + .../firebase/firestore/idalias/Factory.js | 11 + .../firebase/firestore/idalias/GetAlias.js | 16 + .../firebase/firestore/idalias/GetId.js | 15 + .../firestore/idalias/GetRandomAlias.js | 20 + .../firebase/firestore/idalias/IdAlias.d.ts | 42 + .../firebase/firestore/idalias/IdAlias.js | 47 + .../firebase/firestore/idalias/Remove.js | 9 + .../idalias/RetryAddRandomAliasTransaction.js | 19 + .../firebase/firestore/idalias/schema.md | 2 + .../firebase/firestore/leaderboard/Const.js | 29 + .../firestore/leaderboard/DeleteMethods.js | 25 + .../firestore/leaderboard/Factory.d.ts | 5 + .../firebase/firestore/leaderboard/Factory.js | 11 + .../firestore/leaderboard/GetQueryMethods.js | 45 + .../firebase/firestore/leaderboard/GetRank.js | 20 + .../firestore/leaderboard/GetScore.js | 13 + .../firebase/firestore/leaderboard/GetTime.js | 17 + .../firestore/leaderboard/LeaderBoard.d.ts | 98 + .../firestore/leaderboard/LeaderBoard.js | 135 + .../firestore/leaderboard/LoadMethods.js | 85 + .../firebase/firestore/leaderboard/Post.js | 68 + .../firebase/firestore/leaderboard/schema.md | 14 + .../firebase/firestore/messages/Factory.js | 11 + .../firestore/messages/GetQueryMethods.js | 22 + .../firebase/firestore/messages/Messages.js | 102 + .../firestore/messages/ReceiveMethods.js | 89 + .../firebase/firestore/messages/Send.js | 17 + .../firebase/firestore/messages/schema.md | 6 + .../firebase/firestore/pageloader/Factory.js | 11 + .../firestore/pageloader/LoadCurrentPage.js | 40 + .../firestore/pageloader/LoadFirstPage.js | 40 + .../firestore/pageloader/LoadInRange.js | 21 + .../firestore/pageloader/LoadNextPage.js | 45 + .../firestore/pageloader/LoadPreviousPage.js | 49 + .../firestore/pageloader/PageLoader.js | 81 + .../firebase/firestore/utils/query/Delete.js | 34 + .../firestore/utils/query/FindFirst.js | 30 + .../firebase/firestore/utils/query/Load.js | 38 + .../firebase/firestore/utils/query/Query.js | 42 + .../plugins/firebase/preload/AvailableTest.js | 39 + .../plugins/firebase/preload/GetDefaultUrl.js | 23 + .../firebase/preload/LoaderCallback.js | 19 + .../plugins/firebase/preload/Preload.js | 45 + .../plugins/fisheyepipeline-plugin.d.ts | 29 + .../plugins/fisheyepipeline-plugin.js | 14 + .../plugins/fisheyepipeline.d.ts | 2 + .../plugins/fisheyepipeline.js | 2 + .../plugins/flash-plugin.d.ts | 9 + .../plugins/flash-plugin.js | 19 + ui/src/phaser3-rex-plugins/plugins/flash.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/flash.js | 2 + .../plugins/flip-plugin.d.ts | 9 + .../plugins/flip-plugin.js | 19 + ui/src/phaser3-rex-plugins/plugins/flip.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/flip.js | 2 + .../plugins/fsm-plugin.d.ts | 8 + .../phaser3-rex-plugins/plugins/fsm-plugin.js | 23 + ui/src/phaser3-rex-plugins/plugins/fsm.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/fsm.js | 2 + .../plugins/fullwindowrectangle-plugin.js | 23 + .../plugins/fullwindowrectangle.d.ts | 2 + .../plugins/fullwindowrectangle.js | 2 + .../plugins/fuzzy-plugin.d.ts | 5 + .../plugins/fuzzy-plugin.js | 20 + ui/src/phaser3-rex-plugins/plugins/fuzzy.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/fuzzy.js | 2 + .../blitter/bitmaptext/BitmapText.js | 51 + .../blitter/bitmaptext/methods/AppendText.js | 21 + .../blitter/bitmaptext/methods/Methods.js | 25 + .../bitmaptext/methods/RunVerticalWrap.js | 5 + .../blitter/bitmaptext/methods/RunWordWrap.js | 5 + .../bitmaptext/methods/SetFixedSize.js | 15 + .../blitter/bitmaptext/methods/SetFont.js | 30 + .../bitmaptext/methods/SetLetterSpacing.js | 6 + .../blitter/bitmaptext/methods/SetPadding.js | 9 + .../blitter/bitmaptext/methods/SetText.js | 21 + .../bitmaptext/methods/SetWrapConfig.js | 10 + .../methods/UpdateCharacterDataManager.js | 13 + .../blitter/bitmaptext/methods/UpdateText.js | 4 + .../bitmaptext/penmanager/PenManager.js | 30 + .../penmanager/methods/AddTextPens.js | 13 + .../bitmaptext/penmanager/methods/Methods.js | 19 + .../penmanager/methods/RemovePenMethods.js | 56 + .../penmanager/methods/SetTextPens.js | 9 + .../runverticalwrap/RunVerticalWrap.js | 5 + .../penmanager/methods/runwordwrap/GetWord.js | 36 + .../methods/runwordwrap/RunWordWrap.js | 17 + .../blitter/bitmaptext/penmanager/pen/Base.js | 46 + .../bitmaptext/penmanager/pen/CharPen.js | 24 + .../bitmaptext/penmanager/pen/ImagePen.js | 127 + .../gameobjects/blitter/blitter/Blitter.d.ts | 14 + .../gameobjects/blitter/blitter/Blitter.js | 11 + .../gameobjects/blitter/blitter/Creator.js | 16 + .../gameobjects/blitter/blitter/Factory.js | 7 + .../blitter/blitterbase/BlitterBase.d.ts | 150 + .../blitter/blitterbase/BlitterBase.js | 114 + .../blitter/blitterbase/bob/Base.d.ts | 23 + .../blitter/blitterbase/bob/Base.js | 81 + .../blitter/blitterbase/bob/RenderBase.d.ts | 81 + .../blitter/blitterbase/bob/RenderBase.js | 319 ++ .../blitter/blitterbase/bob/Types.js | 5 + .../blitterbase/bob/image/CanvasRender.js | 45 + .../blitterbase/bob/image/ImageData.d.ts | 46 + .../blitterbase/bob/image/ImageData.js | 172 + .../blitterbase/bob/image/WebglRender.js | 55 + .../blitter/blitterbase/methods/AddChild.js | 15 + .../blitterbase/methods/GetChildren.js | 5 + .../methods/GetLastAppendedChildren.js | 5 + .../blitter/blitterbase/methods/Methods.js | 27 + .../blitterbase/methods/PopReusedBob.js | 6 + .../blitterbase/methods/RemoveChild.js | 16 + .../blitterbase/methods/RemoveChildren.js | 14 + .../blitter/blitterbase/methods/Resize.js | 21 + .../blitter/blitterbase/methods/SetTexture.js | 7 + .../blitterbase/methods/TintMethods.js | 20 + .../blitterbase/poolmanager/PoolManager.js | 48 + .../blitterbase/render/CanvasRenderer.js | 28 + .../blitter/blitterbase/render/Render.js | 8 + .../blitterbase/render/WebGLRenderer.js | 37 + .../blitter/blitterbase/utils/AddImage.js | 24 + .../gameobjects/blitter/ninepatch/Creator.js | 13 + .../gameobjects/blitter/ninepatch/Factory.js | 7 + .../gameobjects/blitter/ninepatch/Methods.js | 7 + .../blitter/ninepatch/NinePatch.d.ts | 115 + .../blitter/ninepatch/NinePatch.js | 18 + .../blitter/ninepatch/texture/DrawImage.js | 13 + .../ninepatch/texture/DrawTileSprite.js | 24 + .../canvas/alphamaskimage/AlphaMaskImage.d.ts | 31 + .../canvas/alphamaskimage/AlphaMaskImage.js | 102 + .../canvas/alphamaskimage/Creator.js | 16 + .../canvas/alphamaskimage/Factory.js | 7 + .../gameobjects/canvas/canvas/Canvas.d.ts | 22 + .../gameobjects/canvas/canvas/Canvas.js | 13 + .../gameobjects/canvas/canvas/Creator.js | 18 + .../gameobjects/canvas/canvas/Factory.js | 7 + .../canvas/canvas/LoadImageMethods.js | 51 + .../gameobjects/canvas/canvasbase/Canvas.d.ts | 72 + .../gameobjects/canvas/canvasbase/Canvas.js | 181 + .../canvas/canvasbase/CanvasMethods.js | 85 + .../canvas/canvasbase/TextureMethods.js | 65 + .../canvasbase/render/CanvasRenderer.js | 18 + .../canvas/canvasbase/render/Render.js | 8 + .../canvas/canvasbase/render/WebGLRenderer.js | 53 + .../circlemaskimage/CircleMaskImage.d.ts | 41 + .../canvas/circlemaskimage/CircleMaskImage.js | 134 + .../canvas/circlemaskimage/Creator.js | 16 + .../canvas/circlemaskimage/Factory.js | 7 + .../circularprogress/CircularProgress.d.ts | 114 + .../circularprogress/CircularProgress.js | 281 ++ .../canvas/circularprogress/Creator.js | 13 + .../canvas/circularprogress/DrawContent.js | 99 + .../canvas/circularprogress/Factory.js | 7 + .../canvas/lineprogress/Creator.js | 13 + .../canvas/lineprogress/DrawContent.js | 144 + .../canvas/lineprogress/Factory.js | 7 + .../canvas/lineprogress/LineProgress.d.ts | 104 + .../canvas/lineprogress/LineProgress.js | 172 + .../canvas/roundrectangle/Creator.js | 22 + .../canvas/roundrectangle/DrawContent.js | 16 + .../canvas/roundrectangle/Factory.js | 7 + .../canvas/roundrectangle/RoundRectangle.d.ts | 62 + .../canvas/roundrectangle/RoundRectangle.js | 126 + .../utils/DrawRoundRectangleBackground.js | 36 + .../container/carousel/Carousel.js | 26 + .../gameobjects/container/carousel/Creator.js | 23 + .../gameobjects/container/carousel/Factory.js | 7 + .../container/carousel/methods/Methods.js | 7 + .../container/carousel/methods/ShowCells.js | 57 + .../container/containerlite/Active.js | 47 + .../container/containerlite/AddChild.js | 136 + .../container/containerlite/Alpha.js | 48 + .../container/containerlite/Base.d.ts | 43 + .../container/containerlite/Base.js | 104 + .../container/containerlite/ChangeOrigin.js | 15 + .../container/containerlite/ChildState.js | 34 + .../container/containerlite/Children.js | 135 + .../containerlite/ContainerLite.d.ts | 345 ++ .../container/containerlite/ContainerLite.js | 270 ++ .../container/containerlite/Creator.js | 25 + .../container/containerlite/Depth.js | 85 + .../container/containerlite/DrawBounds.js | 24 + .../container/containerlite/Factory.js | 7 + .../container/containerlite/GetParent.js | 28 + .../container/containerlite/Layer.js | 89 + .../container/containerlite/Mask.js | 44 + .../container/containerlite/Methods.js | 52 + .../container/containerlite/P3Container.js | 86 + .../container/containerlite/Parent.js | 37 + .../container/containerlite/Position.js | 77 + .../container/containerlite/RemoveChild.js | 51 + .../container/containerlite/RenderTexture.js | 36 + .../container/containerlite/Rotation.js | 62 + .../container/containerlite/Scale.js | 64 + .../container/containerlite/ScrollFactor.js | 22 + .../container/containerlite/Transform.js | 27 + .../container/containerlite/Tween.js | 118 + .../container/containerlite/Visible.js | 77 + .../containerlite/mask/AddChildMask.js | 17 + .../containerlite/mask/ChildrenMaskMethods.js | 112 + .../containerlite/mask/MaskChildren.js | 113 + .../containerlite/rendertexture/Enter.js | 43 + .../containerlite/rendertexture/Exit.js | 21 + .../containerlite/rendertexture/Init.js | 14 + .../rendertexture/MeshRenderTextureBase.js | 40 + .../containerlite/utils/GetLocalState.js | 46 + .../container/containerlite/utils/GetScale.js | 9 + .../containerlite/utils/Traversal.js | 26 + .../container/gridtable/Creator.js | 23 + .../container/gridtable/Factory.js | 7 + .../container/gridtable/GridTable.d.ts | 148 + .../container/gridtable/GridTable.js | 369 ++ .../container/gridtable/methods/EachCell.js | 23 + .../gridtable/methods/InsertNewCells.js | 18 + .../gridtable/methods/IsCellVisible.js | 6 + .../container/gridtable/methods/Methods.js | 57 + .../gridtable/methods/PointToCell.js | 28 + .../gridtable/methods/RemoveCells.js | 38 + .../gridtable/methods/SetCellsCount.js | 15 + .../gridtable/methods/SetColumnCount.js | 9 + .../gridtable/methods/SetGridSize.js | 7 + .../container/gridtable/methods/SetTableOX.js | 43 + .../container/gridtable/methods/SetTableOY.js | 42 + .../gridtable/methods/UpdateVisibleCell.js | 14 + .../methods/updatetable/GetCellTLX.js | 7 + .../methods/updatetable/GetCellTLY.js | 7 + .../gridtable/methods/updatetable/HideCell.js | 15 + .../methods/updatetable/HideCells.js | 11 + .../gridtable/methods/updatetable/ShowCell.js | 35 + .../methods/updatetable/ShowCells.js | 64 + .../methods/updatetable/UpdateTable.js | 24 + .../container/gridtable/table/Cell.js | 204 + .../container/gridtable/table/Table.js | 372 ++ .../gameobjects/container/imagebox/Creator.js | 17 + .../gameobjects/container/imagebox/Factory.js | 7 + .../container/imagebox/ImageBox.d.ts | 49 + .../container/imagebox/ImageBox.js | 114 + .../container/transitionimage/Creator.js | 17 + .../container/transitionimage/Factory.js | 7 + .../transitionimage/TransitionImage.d.ts | 104 + .../transitionimage/TransitionImage.js | 261 ++ .../methods/CrossFadeTransition.js | 16 + .../transitionimage/methods/GridCutMethods.js | 64 + .../transitionimage/methods/MaskMethods.js | 95 + .../transitionimage/methods/Methods.js | 19 + .../methods/SetTransitionCallbackMethods.js | 19 + .../methods/TransitionMethods.js | 117 + .../container/utils/FlipMethods.js | 28 + .../dom/filechooser/ClickPromise.js | 19 + .../gameobjects/dom/filechooser/Creator.js | 16 + .../gameobjects/dom/filechooser/Factory.js | 7 + .../dom/filechooser/FileChooser.d.ts | 68 + .../dom/filechooser/FileChooser.js | 118 + .../gameobjects/dom/filedropzone/Creator.js | 16 + .../gameobjects/dom/filedropzone/Factory.js | 7 + .../dom/filedropzone/FileDropZone.d.ts | 70 + .../dom/filedropzone/FileDropZone.js | 82 + .../filedropzone/FileDropZoneProperties.js | 10 + .../filedropzone/methods/DropEnableMethods.js | 15 + .../dom/filedropzone/methods/FilterMethods.js | 19 + .../dom/filedropzone/methods/Methods.js | 19 + .../gameobjects/dom/inputtext/Creator.js | 16 + .../gameobjects/dom/inputtext/Factory.js | 7 + .../gameobjects/dom/inputtext/InputText.d.ts | 113 + .../gameobjects/dom/inputtext/InputText.js | 286 ++ .../dom/inputtext/InputTextProperties.js | 62 + .../gameobjects/dom/utils/LoadFileMethods.js | 23 + .../plugins/gameobjects/dom/utils/Resize.js | 18 + .../gameobjects/dom/utils/RouteEvents.js | 19 + .../gameobjects/dom/utils/SetProperties.js | 20 + .../dom/utils/StopPropagationTouchEvents.js | 14 + .../plugins/gameobjects/dom/utils/SyncTo.js | 8 + .../gameobjects/dom/youtubeplayer/Creator.js | 16 + .../gameobjects/dom/youtubeplayer/Factory.js | 7 + .../gameobjects/dom/youtubeplayer/LoadAPI.js | 24 + .../dom/youtubeplayer/YoutubePlayer.d.ts | 79 + .../dom/youtubeplayer/YoutubePlayer.js | 257 + .../dynamictext/canvasinput/CanvasInput.d.ts | 143 + .../dynamictext/canvasinput/CanvasInput.js | 273 ++ .../dynamictext/canvasinput/Creator.js | 16 + .../dynamictext/canvasinput/Factory.js | 7 + .../methods/AddLastInsertCursor.js | 13 + .../methods/InjectDefaultConfig.js | 36 + .../methods/RegisterCursorStyle.js | 32 + .../canvasinput/methods/RegisterFocusStyle.js | 35 + .../canvasinput/methods/SetText.js | 41 + .../canvasinput/textedit/ClearCursor.js | 16 + .../canvasinput/textedit/ClearSelectRange.js | 21 + .../textedit/CreateHiddenTextEdit.js | 23 + .../canvasinput/textedit/HiddenTextEdit.d.ts | 2 + .../canvasinput/textedit/HiddenTextEdit.js | 119 + .../canvasinput/textedit/MoveCursor.js | 33 + .../canvasinput/textedit/SelectRange.js | 46 + .../dynamictext/dynamictext/Creator.js | 16 + .../dynamictext/dynamictext/DynamicText.d.ts | 325 ++ .../dynamictext/dynamictext/DynamicText.js | 83 + .../dynamictext/dynamictext/Factory.js | 7 + .../dynamictext/dynamictext/bob/Base.d.ts | 21 + .../dynamictext/dynamictext/bob/Base.js | 85 + .../dynamictext/dynamictext/bob/Types.js | 38 + .../bob/background/Background.d.ts | 22 + .../dynamictext/bob/background/Background.js | 169 + .../dynamictext/bob/char/CharData.d.ts | 11 + .../dynamictext/bob/char/CharData.js | 221 + .../dynamictext/bob/char/TextStyle.d.ts | 74 + .../dynamictext/bob/char/TextStyle.js | 383 ++ .../dynamictext/bob/command/Command.d.ts | 8 + .../dynamictext/bob/command/Command.js | 49 + .../dynamictext/bob/drawer/Drawer.d.ts | 13 + .../dynamictext/bob/drawer/Drawer.js | 85 + .../dynamictext/bob/image/ImageData.d.ts | 13 + .../dynamictext/bob/image/ImageData.js | 106 + .../bob/innerbounds/InnerBounds.d.ts | 17 + .../bob/innerbounds/InnerBounds.js | 145 + .../dynamictext/bob/renderbase/Contains.js | 28 + .../bob/renderbase/GetWorldPosition.js | 7 + .../dynamictext/bob/renderbase/Methods.js | 15 + .../bob/renderbase/RenderBase.d.ts | 74 + .../dynamictext/bob/renderbase/RenderBase.js | 337 ++ .../bob/renderbase/RenderMethods.js | 43 + .../dynamictext/bob/space/Space.d.ts | 8 + .../dynamictext/bob/space/Space.js | 32 + .../dynamictext/bob/utils/GetProperty.js | 9 + .../dynamictext/methods/AddChild.js | 33 + .../dynamictext/methods/AppendCommand.js | 8 + .../dynamictext/methods/AppendDrawer.js | 8 + .../dynamictext/methods/AppendImage.js | 8 + .../dynamictext/methods/AppendSpace.js | 8 + .../dynamictext/methods/AppendText.js | 7 + .../dynamictext/methods/BackgroundMethods.js | 16 + .../dynamictext/methods/ClearContent.js | 7 + .../dynamictext/methods/CreateCharChild.js | 27 + .../dynamictext/methods/CreateCharChildren.js | 34 + .../dynamictext/methods/CreateCommandChild.js | 26 + .../dynamictext/methods/CreateDrawerChild.js | 23 + .../dynamictext/methods/CreateImageChild.js | 24 + .../dynamictext/methods/CreateSpaceChild.js | 21 + .../dynamictext/methods/ForEachCharChild.js | 35 + .../dynamictext/methods/ForEachChild.js | 31 + .../methods/ForEachRenderableChild.js | 33 + .../dynamictext/methods/GetActiveChildren.js | 7 + .../dynamictext/methods/GetCharChild.js | 27 + .../dynamictext/methods/GetCharChildIndex.js | 27 + .../dynamictext/methods/GetCharChildren.js | 13 + .../dynamictext/methods/GetCharIndex.js | 34 + .../methods/GetCharWorldPosition.js | 11 + .../dynamictext/methods/GetChildren.js | 5 + .../methods/GetLastAppendedChildren.js | 5 + .../dynamictext/methods/GetNearestChild.js | 21 + .../dynamictext/methods/GetPadding.js | 7 + .../dynamictext/methods/GetText.js | 9 + .../dynamictext/methods/InnerBoundsMethods.js | 11 + .../dynamictext/methods/InsertText.js | 9 + .../dynamictext/methods/Methods.js | 124 + .../methods/ModifyDefaultTextStyle.js | 6 + .../dynamictext/methods/ModifyTextStyle.js | 6 + .../dynamictext/methods/MoveChildMethods.js | 38 + .../dynamictext/methods/PopChild.js | 11 + .../dynamictext/methods/RemoveChild.js | 12 + .../dynamictext/methods/RemoveChildren.js | 10 + .../dynamictext/methods/RemoveText.js | 16 + .../dynamictext/methods/RenderContent.js | 23 + .../dynamictext/methods/ResetTextStyle.js | 6 + .../dynamictext/methods/RunVerticalWrap.js | 13 + .../dynamictext/methods/RunWordWrap.js | 13 + .../dynamictext/methods/RunWrap.js | 15 + .../dynamictext/methods/SetAlignMethods.js | 11 + .../dynamictext/methods/SetFixedSize.js | 26 + .../dynamictext/methods/SetPadding.js | 21 + .../dynamictext/methods/SetTestString.js | 6 + .../dynamictext/methods/SetText.js | 15 + .../dynamictext/methods/SetToMinSize.js | 28 + .../dynamictext/methods/SetWrapConfig.js | 14 + .../methods/input/GetFirstChildContains.js | 15 + .../methods/input/SetChildrenInteractive.js | 73 + .../input/SetChildrenInteractiveEnable.js | 15 + .../methods/input/SetInteractive.js | 17 + .../transform/BobPositionToCanvasPosition.js | 28 + .../transform/BobPositionToWorldPosition.js | 10 + .../transform/CanvasPositionToBobPosition.js | 24 + .../utils/transform/GetBobCenterPosition.js | 14 + .../utils/transform/GetBobWorldPosition.js | 14 + .../methods/wrap/GetChildrenAlign.js | 12 + .../methods/wrap/OffsetChildren.js | 17 + .../wrap/runverticalwrap/AlignLines.js | 65 + .../wrap/runverticalwrap/RunVerticalWrap.js | 192 + .../methods/wrap/runwordwrap/AlignLines.js | 60 + .../wrap/runwordwrap/GetDefaultTextHeight.js | 21 + .../methods/wrap/runwordwrap/GetWord.js | 47 + .../methods/wrap/runwordwrap/RunWordWrap.js | 209 + .../dynamictext/poolmanager/PoolManager.js | 44 + .../dynamictext/textplayer/Creator.js | 16 + .../dynamictext/textplayer/Factory.js | 7 + .../dynamictext/textplayer/TextPlayer.d.ts | 161 + .../dynamictext/textplayer/TextPlayer.js | 134 + .../textplayer/methods/AddImage.js | 6 + .../textplayer/methods/ContentMethods.js | 12 + .../dynamictext/textplayer/methods/Methods.js | 41 + .../textplayer/methods/PauseMethods.js | 15 + .../textplayer/methods/PlayMethods.js | 27 + .../textplayer/methods/ResumeMethods.js | 15 + .../textplayer/methods/SetClickTarget.js | 17 + .../methods/SetIgnoreNextPageInput.js | 9 + .../textplayer/methods/SetIgnoreWait.js | 6 + .../textplayer/methods/SetNextPageInput.js | 22 + .../textplayer/methods/SetTargetCamera.js | 6 + .../textplayer/methods/ShowPage.js | 33 + .../textplayer/methods/SpriteMethods.js | 11 + .../textplayer/methods/TypingNextPage.js | 50 + .../textplayer/methods/TypingSpeedMethods.js | 11 + .../dynamictext/textplayer/methods/Wait.js | 6 + .../GameObjectManagerMethods.js | 43 + .../OnParseAddGameObjectTag.js | 75 + .../OnParseCallGameObjectMethodTag.js | 63 + .../OnParseEaseGameObjectPropertyTag.js | 99 + .../OnParseRemoveAllGameObjectsTag.js | 33 + .../methods/spritemanager/AddSpriteManager.js | 21 + .../spritemanager/OnParseChainAnimationTag.js | 45 + .../spritemanager/OnParsePauseAnimationTag.js | 44 + .../spritemanager/OnParsePlayAnimationTag.js | 108 + .../methods/spritemanager/SpriteMethods.js | 11 + .../textplayer/methods/utils/ClearEvents.js | 9 + .../textplayer/methods/utils/Events.js | 15 + .../textplayer/methods/utils/Progress.js | 5 + .../methods/utils/wait/GetWrapCallback.js | 9 + .../methods/utils/wait/WaitCallback.js | 10 + .../methods/utils/wait/WaitCameraEffect.js | 77 + .../methods/utils/wait/WaitClick.js | 23 + .../methods/utils/wait/WaitGameObject.js | 108 + .../methods/utils/wait/WaitKeyDown.js | 20 + .../methods/utils/wait/WaitMultiple.js | 62 + .../methods/utils/wait/WaitMusic.js | 23 + .../textplayer/methods/utils/wait/WaitTime.js | 22 + .../textplayer/parser/AddParseCallbacks.js | 69 + .../dynamictext/textplayer/parser/Parser.js | 42 + .../textplayer/parser/PreProcessSource.js | 28 + .../OnParseCrossFadeBackgroundMusicTag.js | 46 + .../OnParseFadeInBackgroundMusicTag.js | 46 + .../OnParseFadeOutBackgroundMusicTag.js | 48 + .../OnParsePauseBackgroundMusicTag.js | 68 + .../OnParsePlayBackgroundMusicTag.js | 80 + .../OnParseSetBackgroundMusicVolumeTag.js | 45 + .../parser/camera/OnParseFadeInCameraTag.js | 22 + .../parser/camera/OnParseFadeOutCameraTag.js | 22 + .../parser/camera/OnParseFlashCameraTag.js | 22 + .../parser/camera/OnParseRotateCameraTag.js | 44 + .../parser/camera/OnParseScrollCameraTag.js | 50 + .../parser/camera/OnParseShakeCameraTag.js | 22 + .../parser/camera/OnParseZoomCameraTag.js | 36 + .../parser/content/OnParseContent.js | 15 + .../parser/content/OnParseContentOff.js | 10 + .../parser/content/OnParseContentOn.js | 10 + .../parser/content/OnParseNewLineTag.js | 15 + .../parser/content/OnParsePageBreakTag.js | 19 + .../parser/custom/OnParseCustomTag.js | 53 + .../parser/image/OnParseImageTag.js | 24 + .../OnParseFadeInSoundEffectTag.js | 46 + .../OnParseFadeOutSoundEffectTag.js | 47 + .../soundeffect/OnParsePlaySoundEffectTag.js | 64 + .../OnParseSetSoundEffectVolumeTag.js | 45 + .../parser/space/OnParseSpaceTag.js | 17 + .../parser/textstyle/OnParseAlignTag.js | 17 + .../parser/textstyle/OnParseBoldTag.js | 17 + .../parser/textstyle/OnParseColorTag.js | 21 + .../parser/textstyle/OnParseFontSizeTag.js | 21 + .../parser/textstyle/OnParseItalicTag.js | 17 + .../parser/textstyle/OnParseLeftSpaceTag.js | 25 + .../parser/textstyle/OnParseOffsetXTag.js | 25 + .../parser/textstyle/OnParseOffsetYTag.js | 25 + .../parser/textstyle/OnParseRightSpaceTag.js | 25 + .../parser/textstyle/OnParseShadowColorTag.js | 25 + .../parser/textstyle/OnParseStrokeColorTag.js | 25 + .../parser/typing/OnParseTypingSpeedTag.js | 29 + .../textplayer/parser/wait/OnParseWaitTag.js | 36 + .../textplayer/typewriter/FadeOutPage.js | 29 + .../textplayer/typewriter/Methods.js | 37 + .../textplayer/typewriter/Pause.js | 7 + .../textplayer/typewriter/PauseTyping.js | 17 + .../textplayer/typewriter/Resume.js | 7 + .../textplayer/typewriter/ResumeTyping.js | 21 + .../textplayer/typewriter/SetIgnoreWait.js | 9 + .../typewriter/SetSkipSoundEffect.js | 16 + .../typewriter/SetSkipSpaceEnable.js | 9 + .../typewriter/SetSkipTypingAnimation.js | 19 + .../typewriter/SkipCurrentTypingDelay.js | 8 + .../textplayer/typewriter/Start.js | 16 + .../textplayer/typewriter/TimerTypes.js | 7 + .../textplayer/typewriter/TypeWriter.js | 120 + .../textplayer/typewriter/Typing.js | 94 + .../typewriter/TypingSpeedMethods.js | 14 + .../dynamictext/textplayer/typewriter/Wait.js | 15 + .../layer/layermanager/LayerManager.d.ts | 31 + .../layer/layermanager/LayerManager.js | 90 + .../live2d/framework/.eslintrc.yml | 30 + .../gameobjects/live2d/framework/CHANGELOG.md | 73 + .../gameobjects/live2d/framework/LICENSE.md | 45 + .../gameobjects/live2d/framework/README.md | 143 + .../live2d/framework/package-lock.json | 1157 +++++ .../gameobjects/live2d/framework/package.json | 20 + .../framework/src/cubismdefaultparameterid.ts | 118 + .../framework/src/cubismframeworkconfig.ts | 32 + .../framework/src/cubismmodelsettingjson.ts | 830 ++++ .../framework/src/effect/cubismbreath.ts | 124 + .../framework/src/effect/cubismeyeblink.ts | 233 + .../live2d/framework/src/effect/cubismpose.ts | 400 ++ .../live2d/framework/src/icubismallcator.ts | 51 + .../framework/src/icubismmodelsetting.ts | 203 + .../live2d/framework/src/id/cubismid.ts | 79 + .../framework/src/id/cubismidmanager.ts | 121 + .../framework/src/live2dcubismframework.ts | 277 ++ .../live2d/framework/src/math/cubismmath.ts | 334 ++ .../framework/src/math/cubismmatrix44.ts | 314 ++ .../framework/src/math/cubismmodelmatrix.ts | 226 + .../framework/src/math/cubismtargetpoint.ts | 169 + .../framework/src/math/cubismvector2.ts | 169 + .../framework/src/math/cubismviewmatrix.ts | 339 ++ .../live2d/framework/src/model/cubismmoc.ts | 105 + .../live2d/framework/src/model/cubismmodel.ts | 818 ++++ .../src/model/cubismmodeluserdata.ts | 136 + .../src/model/cubismmodeluserdatajson.ts | 117 + .../framework/src/model/cubismusermodel.ts | 440 ++ .../framework/src/motion/acubismmotion.ts | 279 ++ .../src/motion/cubismexpressionmotion.ts | 199 + .../framework/src/motion/cubismmotion.ts | 1060 +++++ .../src/motion/cubismmotioninternal.ts | 156 + .../framework/src/motion/cubismmotionjson.ts | 383 ++ .../src/motion/cubismmotionmanager.ts | 126 + .../src/motion/cubismmotionqueueentry.ts | 253 + .../src/motion/cubismmotionqueuemanager.ts | 342 ++ .../framework/src/physics/cubismphysics.ts | 934 ++++ .../src/physics/cubismphysicsinternal.ts | 249 + .../src/physics/cubismphysicsjson.ts | 648 +++ .../framework/src/rendering/cubismrenderer.ts | 275 ++ .../src/rendering/cubismrenderer_webgl.ts | 2222 +++++++++ .../live2d/framework/src/type/csmmap.ts | 315 ++ .../live2d/framework/src/type/csmrectf.ts | 89 + .../live2d/framework/src/type/csmstring.ts | 107 + .../live2d/framework/src/type/csmvector.ts | 352 ++ .../live2d/framework/src/utils/cubismdebug.ts | 162 + .../live2d/framework/src/utils/cubismjson.ts | 1253 +++++ .../framework/src/utils/cubismstring.ts | 129 + .../live2d/framework/tsconfig.json | 22 + .../gameobjects/live2d/gameobject/Creator.js | 15 + .../gameobjects/live2d/gameobject/Factory.js | 7 + .../live2d/gameobject/Live2dGameObject.d.ts | 113 + .../live2d/gameobject/Live2dGameObject.js | 75 + .../gameobject/Live2dGameObjectBase.d.ts | 76 + .../live2d/gameobject/Live2dGameObjectBase.js | 20 + .../gameobject/events/OnExpressionStart.js | 6 + .../live2d/gameobject/events/OnIdle.js | 5 + .../gameobject/events/OnMotionComplete.js | 6 + .../live2d/gameobject/events/OnMotionStart.js | 6 + .../gameobject/globaldata/CanvasMatrix.js | 34 + .../gameobject/globaldata/GlobalData.js | 70 + .../live2d/gameobject/methods/Methods.js | 68 + .../live2d/gameobject/methods/SetModel.js | 33 + .../live2d/gameobject/methods/SetTimeScale.js | 5 + .../methods/expression/GetExpressionNames.js | 5 + .../methods/expression/SetExpression.js | 6 + .../methods/expression/SetRandomExpression.js | 6 + .../methods/interactive/GetHitTestResult.js | 5 + .../methods/interactive/HitAreaCallback.js | 24 + .../gameobject/methods/interactive/HitTest.js | 6 + .../methods/interactive/SetInteractive.js | 44 + .../methods/lipsync/SetLipSyncValue.js | 6 + .../methods/motion/AutoPlayIdleMotion.js | 24 + .../methods/motion/GetMotionGroupNames.js | 4 + .../methods/motion/GetMotionNames.js | 5 + .../methods/motion/GetPlayinigMotionNames.js | 5 + .../methods/motion/IsAnyMotionPlaying.js | 5 + .../gameobject/methods/motion/StartMotion.js | 18 + .../methods/motion/StopAllMotions.js | 6 + .../methods/parameter/AddParameterValue.js | 6 + .../methods/parameter/GetParameters.js | 5 + .../gameobject/methods/parameter/LookAt.js | 46 + .../methods/parameter/LookForward.js | 6 + .../methods/parameter/RegisterParameter.js | 6 + .../methods/parameter/ResetParameterValue.js | 6 + .../methods/position/WorldXYToModelXY.js | 23 + .../live2d/gameobject/model/Const.js | 4 + .../live2d/gameobject/model/Methods.js | 49 + .../live2d/gameobject/model/Model.js | 51 + .../live2d/gameobject/model/ViewMatrix.js | 32 + .../live2d/gameobject/model/draw/Draw.js | 20 + .../gameobject/model/draw/UpdateViewMatrix.js | 47 + .../model/expression/GetExpressionNames.js | 11 + .../model/expression/SetExpression.js | 38 + .../model/expression/SetRandomExpression.js | 12 + .../model/hitarea/GetDrawableBounds.js | 47 + .../model/hitarea/HitAreaNameToDrawIndex.js | 13 + .../gameobject/model/hitarea/HitTest.js | 15 + .../model/motion/GetMotionGroupNames.js | 17 + .../gameobject/model/motion/GetMotionNames.js | 15 + .../model/motion/GetPlayinigMotionNames.js | 16 + .../model/motion/IsAnyMotionPlaying.js | 5 + .../gameobject/model/motion/StartMotion.js | 46 + .../gameobject/model/motion/StopAllMotions.js | 6 + .../model/parameter/AddParameterValue.js | 20 + .../model/parameter/RegisterParameter.js | 25 + .../model/parameter/ResetParameterValue.js | 19 + .../model/position/LocalXToModelMatrixX.js | 16 + .../live2d/gameobject/model/setup/Setup.js | 161 + .../live2d/gameobject/model/update/Update.js | 70 + .../gameobject/render/CanvasRenderer.js | 4 + .../live2d/gameobject/render/Render.js | 8 + .../live2d/gameobject/render/WebGLRenderer.js | 20 + .../plugins/gameobjects/live2d/index.js | 9 + .../plugins/gameobjects/live2d/index.ts | 9 + .../loader/core/CoreScriptFileCallback.d.ts | 6 + .../loader/core/CoreScriptFileCallback.js | 8 + .../loader/core/Live2dCoreScriptFile.js | 39 + .../loader/core/Live2dCoreScriptState.js | 26 + .../live2d/loader/model/CreateBinaryFile.js | 10 + .../live2d/loader/model/Live2dFile.js | 84 + .../loader/model/Live2dFileCallback.d.ts | 7 + .../live2d/loader/model/Live2dFileCallback.js | 57 + .../live2d/loader/model/LoadChildrenFiles.js | 182 + .../plugins/gameobjects/live2d/note.md | 386 ++ .../gameobjects/live2d/utils/Initialize.js | 20 + .../mesh/perspective/card/Card.d.ts | 94 + .../gameobjects/mesh/perspective/card/Card.js | 161 + .../mesh/perspective/card/Creator.js | 14 + .../mesh/perspective/card/Factory.js | 7 + .../gameobjects/mesh/perspective/card/Flip.js | 122 + .../mesh/perspective/card/LayoutFaces.js | 16 + .../mesh/perspective/carousel/Carousel.d.ts | 72 + .../mesh/perspective/carousel/Carousel.js | 130 + .../mesh/perspective/carousel/Creator.js | 14 + .../perspective/carousel/FaceNameToIndex.js | 10 + .../mesh/perspective/carousel/Factory.js | 7 + .../mesh/perspective/carousel/GetFirstFace.js | 12 + .../mesh/perspective/carousel/LayoutFaces.js | 18 + .../mesh/perspective/carousel/Roll.js | 109 + .../mesh/perspective/image/Creator.js | 17 + .../mesh/perspective/image/Factory.js | 7 + .../mesh/perspective/image/Image.d.ts | 47 + .../mesh/perspective/image/Image.js | 182 + .../mesh/perspective/imagecarousel/Creator.js | 14 + .../mesh/perspective/imagecarousel/Factory.js | 7 + .../perspective/imagecarousel/GetFaceSize.js | 19 + .../imagecarousel/GetIndexOffsetMap.js | 9 + .../imagecarousel/ImageCarousel.d.ts | 36 + .../imagecarousel/ImageCarousel.js | 86 + .../mesh/perspective/imagecarousel/Roll.js | 42 + .../mesh/perspective/rendertexture/Creator.js | 19 + .../mesh/perspective/rendertexture/Factory.js | 7 + .../rendertexture/RenderTexture.d.ts | 35 + .../rendertexture/RenderTexture.js | 55 + .../mesh/perspective/sprite/Creator.js | 17 + .../mesh/perspective/sprite/Factory.js | 7 + .../mesh/perspective/sprite/Sprite.d.ts | 62 + .../mesh/perspective/sprite/Sprite.js | 76 + .../mesh/perspective/utils/CreateFaces.js | 35 + .../utils/CreatePerspectiveObject.js | 35 + .../mesh/perspective/utils/FaceContainer.d.ts | 41 + .../mesh/perspective/utils/FaceContainer.js | 114 + .../mesh/perspective/utils/ForEachFace.js | 39 + .../mesh/perspective/utils/TransformVerts.js | 25 + .../gameobjects/mesh/quad/image/Creator.js | 17 + .../gameobjects/mesh/quad/image/Factory.js | 7 + .../gameobjects/mesh/quad/image/Image.d.ts | 53 + .../gameobjects/mesh/quad/image/Image.js | 104 + .../mesh/quad/image/methods/ControlPoint.js | 118 + .../quad/image/methods/GetPointPosition.js | 30 + .../mesh/quad/image/methods/InitFaces.js | 85 + .../mesh/quad/rendertexture/Creator.js | 19 + .../mesh/quad/rendertexture/Factory.js | 7 + .../quad/rendertexture/RenderTexture.d.ts | 35 + .../mesh/quad/rendertexture/RenderTexture.js | 55 + .../mesh/quad/skewimage/Creator.js | 17 + .../mesh/quad/skewimage/Factory.js | 7 + .../gameobjects/mesh/quad/skewimage/Skew.js | 25 + .../mesh/quad/skewimage/SkewImage.d.ts | 41 + .../mesh/quad/skewimage/SkewImage.js | 100 + .../mesh/quad/skewrendertexture/Creator.js | 19 + .../mesh/quad/skewrendertexture/Factory.js | 7 + .../skewrendertexture/SkewRenderTexture.d.ts | 29 + .../skewrendertexture/SkewRenderTexture.js | 38 + .../gameobjects/mesh/shatter/image/Creator.js | 17 + .../gameobjects/mesh/shatter/image/Face.js | 66 + .../gameobjects/mesh/shatter/image/Factory.js | 7 + .../gameobjects/mesh/shatter/image/Image.d.ts | 63 + .../gameobjects/mesh/shatter/image/Image.js | 195 + .../mesh/shatter/rendertexture/Creator.js | 19 + .../mesh/shatter/rendertexture/Factory.js | 7 + .../shatter/rendertexture/RenderTexture.d.ts | 25 + .../shatter/rendertexture/RenderTexture.js | 38 + .../plugins/gameobjects/mesh/utils/LocalXY.js | 36 + .../gameobjects/rendertexture/line/Creator.js | 13 + .../gameobjects/rendertexture/line/Factory.js | 7 + .../gameobjects/rendertexture/line/Line.d.ts | 51 + .../gameobjects/rendertexture/line/Line.js | 184 + .../rendertexture/line/UpdateTexture.js | 105 + .../rendertexture/ninepatch/Creator.js | 13 + .../rendertexture/ninepatch/Factory.js | 7 + .../rendertexture/ninepatch/NinePatch.d.ts | 114 + .../rendertexture/ninepatch/NinePatch.js | 21 + .../rendertexture/utils/DrawImage.js | 11 + .../rendertexture/utils/DrawTileSprite.js | 11 + .../rendertexture/utils/GetStampGameObject.js | 30 + .../shader/effectlayer/effectlayer/Creator.js | 18 + .../effectlayer/effectlayer/EffectLayer.js | 179 + .../shader/effectlayer/effectlayer/Factory.js | 7 + .../shader/effectlayer/outline/Creator.js | 11 + .../shader/effectlayer/outline/Factory.js | 7 + .../effectlayer/outline/OutlineEffectLayer.js | 100 + .../effectlayer/outline/outline-frag.js | 63 + .../gameobjects/shape/checkbox/Checkbox.d.ts | 36 + .../gameobjects/shape/checkbox/Checkbox.js | 35 + .../shape/checkbox/CheckboxShape.d.ts | 84 + .../shape/checkbox/CheckboxShape.js | 156 + .../shape/checkbox/CheckboxShapeCreator.js | 19 + .../shape/checkbox/CheckboxShapeFactory.js | 7 + .../gameobjects/shape/checkbox/Creator.js | 19 + .../gameobjects/shape/checkbox/Factory.js | 7 + .../methods/CheckerAnimationMethods.js | 36 + .../shape/checkbox/methods/Methods.js | 17 + .../checkbox/methods/ShapesUpdateMethods.js | 74 + .../shape/checkbox/methods/SizeMethods.js | 17 + .../shape/checkbox/methods/StyleMethods.js | 84 + .../circularprogress/CircularProgress.d.ts | 89 + .../circularprogress/CircularProgress.js | 166 + .../shape/circularprogress/Creator.js | 13 + .../shape/circularprogress/Factory.js | 7 + .../circularprogress/ShapesUpdateMethods.js | 64 + .../shape/customprogress/Creator.js | 18 + .../shape/customprogress/CustomProgress.d.ts | 72 + .../shape/customprogress/CustomProgress.js | 43 + .../shape/customprogress/Factory.js | 7 + .../gameobjects/shape/customshapes/Creator.js | 18 + .../shape/customshapes/CustomShapes.d.ts | 70 + .../shape/customshapes/CustomShapes.js | 46 + .../gameobjects/shape/customshapes/Factory.js | 7 + .../shape/customshapes/ShapesUpdateMethods.js | 98 + .../shape/fullwindowrectangle/Creator.js | 18 + .../shape/fullwindowrectangle/Factory.js | 7 + .../FullWindowRectangle.d.ts | 14 + .../FullWindowRectangle.js | 62 + .../gameobjects/shape/lineprogress/Creator.js | 13 + .../gameobjects/shape/lineprogress/Factory.js | 7 + .../shape/lineprogress/LineProgress.d.ts | 96 + .../shape/lineprogress/LineProgress.js | 147 + .../shape/lineprogress/UpdateShapes.js | 69 + .../shape/roundrectangle/Creator.js | 22 + .../shape/roundrectangle/Factory.js | 7 + .../shape/roundrectangle/RoundRectangle.d.ts | 103 + .../shape/roundrectangle/RoundRectangle.js | 407 ++ .../roundrectangle/render/CanvasRenderer.js | 60 + .../shape/roundrectangle/render/Render.js | 8 + .../roundrectangle/render/WebGLRenderer.js | 38 + .../gameobjects/shape/shapes/BaseShapes.d.ts | 35 + .../gameobjects/shape/shapes/BaseShapes.js | 215 + .../shape/shapes/geoms/base/BaseGeom.d.ts | 26 + .../shape/shapes/geoms/base/BaseGeom.js | 53 + .../shape/shapes/geoms/base/StyleMethods.js | 33 + .../gameobjects/shape/shapes/geoms/index.d.ts | 21 + .../gameobjects/shape/shapes/geoms/index.js | 21 + .../shape/shapes/geoms/lines/Curve.d.ts | 15 + .../shape/shapes/geoms/lines/Curve.js | 52 + .../shape/shapes/geoms/lines/Line.d.ts | 16 + .../shape/shapes/geoms/lines/Line.js | 75 + .../shape/shapes/geoms/lines/Lines.d.ts | 78 + .../shape/shapes/geoms/lines/Lines.js | 164 + .../shape/shapes/geoms/lines/PathBase.d.ts | 5 + .../shape/shapes/geoms/lines/PathBase.js | 73 + .../shape/shapes/geoms/lines/arc/Arc.d.ts | 32 + .../shape/shapes/geoms/lines/arc/Arc.js | 205 + .../shape/shapes/geoms/lines/arc/Circle.d.ts | 5 + .../shape/shapes/geoms/lines/arc/Circle.js | 9 + .../shape/shapes/geoms/lines/arc/Ellipse.d.ts | 5 + .../shape/shapes/geoms/lines/arc/Ellipse.js | 9 + .../lines/roundrectangle/RoundRectangle.d.ts | 42 + .../lines/roundrectangle/RoundRectangle.js | 261 ++ .../shapes/geoms/rectangle/Rectangle.d.ts | 21 + .../shape/shapes/geoms/rectangle/Rectangle.js | 143 + .../shape/shapes/geoms/triangle/Triangle.d.ts | 21 + .../shape/shapes/geoms/triangle/Triangle.js | 165 + .../shape/shapes/render/CanvasRenderer.js | 23 + .../gameobjects/shape/shapes/render/Render.js | 8 + .../shape/shapes/render/WebGLRenderer.js | 28 + .../gameobjects/shape/toggleswitch/Creator.js | 19 + .../gameobjects/shape/toggleswitch/Factory.js | 7 + .../shape/toggleswitch/ToggleSwitch.d.ts | 36 + .../shape/toggleswitch/ToggleSwitch.js | 35 + .../shape/toggleswitch/ToggleSwitchShape.d.ts | 87 + .../shape/toggleswitch/ToggleSwitchShape.js | 140 + .../toggleswitch/ToggleSwitchShapeCreator.js | 19 + .../toggleswitch/ToggleSwitchShapeFactory.js | 7 + .../shape/toggleswitch/methods/Methods.js | 19 + .../toggleswitch/methods/PositionMethods.js | 19 + .../methods/ShapesUpdateMethods.js | 65 + .../shape/toggleswitch/methods/SizeMethods.js | 41 + .../toggleswitch/methods/StyleMethods.js | 41 + .../methods/ToggleAnimationMethods.js | 36 + .../gameobjects/shape/triangle/Creator.js | 13 + .../gameobjects/shape/triangle/Factory.js | 7 + .../gameobjects/shape/triangle/Triangle.d.ts | 76 + .../gameobjects/shape/triangle/Triangle.js | 261 ++ .../methods/DrawCircleVerticesTriangle.js | 35 + .../shape/triangle/methods/DrawFitTriangle.js | 62 + .../triangle/methods/EaseDirectionMethods.js | 36 + .../triangle/methods/ShapesUpdateMethods.js | 33 + .../shape/utils/render/FillPathWebGL.js | 43 + .../shape/utils/render/FillStyleCanvas.js | 13 + .../shape/utils/render/LineStyleCanvas.js | 14 + .../shape/utils/render/StrokePathWebGL.js | 57 + .../tagtext/bbcodetext/BBCodeText.d.ts | 17 + .../tagtext/bbcodetext/BBCodeText.js | 16 + .../gameobjects/tagtext/bbcodetext/Creator.js | 59 + .../gameobjects/tagtext/bbcodetext/Factory.js | 7 + .../tagtext/bbcodetext/parser/Parser.js | 46 + .../bbcodetext/parser/PropToContextStyle.js | 120 + .../bbcodetext/parser/PropToTagText.js | 59 + .../tagtext/bbcodetext/parser/SplitText.js | 60 + .../tagtext/bbcodetext/parser/TagRegex.js | 182 + .../bbcodetext/parser/TagTextToProp.js | 152 + .../gameobjects/tagtext/tagtext/Creator.js | 59 + .../gameobjects/tagtext/tagtext/Factory.js | 7 + .../gameobjects/tagtext/tagtext/Parser.js | 309 ++ .../gameobjects/tagtext/tagtext/TagText.d.ts | 65 + .../gameobjects/tagtext/tagtext/TagText.js | 36 + .../gameobjects/tagtext/textbase/FullFill.js | 22 + .../textbase/IsCanvasTextGameObject.js | 7 + .../gameobjects/tagtext/textbase/Text.d.ts | 266 ++ .../gameobjects/tagtext/textbase/Text.js | 508 ++ .../tagtext/textbase/canvastext/CanvasText.js | 353 ++ .../textbase/canvastext/DrawMethods.js | 203 + .../textbase/canvastext/SetInteractive.js | 91 + .../textbase/hitareamanager/HitAreaManager.js | 86 + .../tagtext/textbase/penmanger/Pen.js | 75 + .../tagtext/textbase/penmanger/PenManager.js | 280 ++ .../tagtext/textbase/wraptext/WrapText.js | 126 + .../textbase/wraptext/WrapTextLinesPool.js | 34 + .../plugins/gameobjects/textbase/TextBase.js | 166 + .../plugins/gameobjects/textbase/const.js | 16 + .../textbase/render/CanvasRenderer.js | 32 + .../gameobjects/textbase/render/Render.js | 8 + .../textbase/render/WebGLRenderer.js | 67 + .../textbase/textstyle/MeasureText.js | 134 + .../textbase/textstyle/MeasureTextMargins.js | 60 + .../textbase/textstyle/PropertyMap.js | 58 + .../textbase/textstyle/TextStyle.js | 578 +++ .../textstyle/TextStyleInterface.d.ts | 69 + .../video/videobase/CreateVideoElement.js | 54 + .../gameobjects/video/videobase/Load.js | 21 + .../gameobjects/video/videobase/VideoBase.js | 219 + .../gameobjects/video/videocanvas/Creator.js | 17 + .../gameobjects/video/videocanvas/Factory.js | 7 + .../video/videocanvas/VideoCanvas.js | 79 + .../gameobjects/video/videodom/Creator.js | 16 + .../gameobjects/video/videodom/Factory.js | 7 + .../gameobjects/video/videodom/VideoDOM.js | 58 + .../plugins/gashapon-plugin.d.ts | 8 + .../plugins/gashapon-plugin.js | 19 + .../phaser3-rex-plugins/plugins/gashapon.d.ts | 2 + .../phaser3-rex-plugins/plugins/gashapon.js | 2 + .../plugins/geom/hexagon/Height.js | 7 + .../plugins/geom/hexagon/Hexagon.js | 208 + .../plugins/geom/hexagon/SetPoints.js | 67 + .../plugins/geom/hexagon/Width.js | 7 + .../plugins/geom/pathdata/ArcTo.js | 24 + .../geom/pathdata/CubicBezierCurveTo.js | 19 + .../plugins/geom/pathdata/DuplicateLast.js | 15 + .../plugins/geom/pathdata/LineTo.js | 15 + .../plugins/geom/pathdata/Offset.js | 9 + .../PathDataBuilder/AddPathMethods.js | 133 + .../PathDataBuilder/GraphicsMethods.js | 13 + .../PathDataBuilder/PathDataBuilder.js | 51 + .../PathDataBuilder/PathSegmentMethods.js | 138 + .../PathDataBuilder/SavePathDataMethods.js | 25 + .../PathDataBuilder/TransformPointsMethods.js | 57 + .../geom/pathdata/QuadraticBezierTo.js | 19 + .../plugins/geom/pathdata/RotateAround.js | 17 + .../plugins/geom/pathdata/Scale.js | 13 + .../plugins/geom/pathdata/StartAt.js | 11 + .../plugins/geom/pathdata/ToPoints.js | 14 + .../plugins/geom/pathdata/ToPolygon.js | 13 + .../plugins/geom/quad/Quad.js | 191 + .../plugins/geom/quad/SetPoints.js | 41 + .../plugins/geom/rhombus/Rhombus.js | 196 + .../geom/roundrectangle/RoundRectangle.js | 177 + .../plugins/geom/utils/GetPoint.js | 56 + .../plugins/geom/utils/InitPoints.js | 12 + .../plugins/geom/utils/Offset.js | 12 + .../plugins/geom/utils/RotateAround.js | 11 + .../plugins/gestures-plugin.d.ts | 38 + .../plugins/gestures-plugin.js | 18 + .../phaser3-rex-plugins/plugins/gestures.d.ts | 15 + .../phaser3-rex-plugins/plugins/gestures.js | 15 + .../plugins/glowfilter2pipeline-plugin.d.ts | 38 + .../plugins/glowfilter2pipeline-plugin.js | 48 + .../plugins/glowfilter2pipeline.d.ts | 2 + .../plugins/glowfilter2pipeline.js | 2 + .../plugins/glowfilterpipeline-plugin.d.ts | 29 + .../plugins/glowfilterpipeline-plugin.js | 14 + .../plugins/glowfilterpipeline.d.ts | 2 + .../plugins/glowfilterpipeline.js | 2 + .../plugins/graph-plugin.js | 13 + .../plugins/graph/ObjectFactory.js | 10 + .../plugins/graph/graph/Factory.js | 11 + .../plugins/graph/graph/Graph.js | 87 + .../plugins/graph/graph/Methods.js | 47 + .../plugins/graph/graph/edge/AddEdge.js | 45 + .../plugins/graph/graph/edge/GetAllEdges.js | 18 + .../plugins/graph/graph/edge/GetEdgeData.js | 14 + .../plugins/graph/graph/edge/GetEdgeLength.js | 18 + .../graph/graph/edge/GetEdgesOfVertex.js | 23 + .../plugins/graph/graph/edge/IsEdge.js | 7 + .../plugins/graph/graph/edge/IsInLoop.js | 34 + .../plugins/graph/graph/edge/RemoveEdge.js | 24 + .../graph/neighbors/AreNeighborVertices.js | 16 + .../graph/neighbors/GetNeighborVertices.js | 21 + .../plugins/graph/graph/vertex/AddVertex.js | 14 + .../plugins/graph/graph/vertex/AddVertices.js | 8 + .../graph/vertex/GetAllConnectedVertices.js | 55 + .../graph/graph/vertex/GetAllVertices.js | 18 + .../graph/graph/vertex/GetOppositeVertex.js | 18 + .../graph/graph/vertex/GetVertexData.js | 14 + .../graph/graph/vertex/GetVerticesOfEdge.js | 26 + .../plugins/graph/graph/vertex/IsVertex.js | 7 + .../graph/graph/vertex/RemoveAllVertices.js | 8 + .../graph/graph/vertex/RemoveVertex.js | 35 + .../plugins/graph/graphitem/GetGraphItem.js | 18 + .../plugins/graph/graphitem/GetObjUID.js | 16 + .../plugins/graph/graphitem/GraphItemData.js | 66 + .../plugins/graph/graphitem/IsUID.js | 5 + .../plugins/graph/graphitem/ObjBank.js | 7 + .../plugins/graph/graphitem/UidToObj.js | 10 + .../plugins/grayscalepipeline-plugin.d.ts | 29 + .../plugins/grayscalepipeline-plugin.js | 14 + .../plugins/grayscalepipeline.d.ts | 2 + .../plugins/grayscalepipeline.js | 2 + .../plugins/gridalign-plugin.js | 26 + .../phaser3-rex-plugins/plugins/gridalign.js | 7 + .../plugins/gridcutimage-plugin.d.ts | 5 + .../plugins/gridcutimage-plugin.js | 19 + .../plugins/gridcutimage.d.ts | 2 + .../plugins/gridcutimage.js | 2 + .../plugins/gridtable-plugin.js | 23 + .../plugins/gridtable.d.ts | 2 + .../phaser3-rex-plugins/plugins/gridtable.js | 2 + .../plugins/hexagon-plugin.js | 19 + ui/src/phaser3-rex-plugins/plugins/hexagon.js | 2 + .../plugins/hiddeninputtext-plugin.js | 22 + .../plugins/hiddeninputtext.d.ts | 2 + .../plugins/hiddeninputtext.js | 2 + .../plugins/horrifipipeline-plugin.d.ts | 29 + .../plugins/horrifipipeline-plugin.js | 19 + .../plugins/horrifipipeline.d.ts | 2 + .../plugins/horrifipipeline.js | 2 + .../plugins/horrifipipelinebehavior.d.ts | 2 + .../plugins/horrifipipelinebehavior.js | 2 + .../plugins/hsladjustpipeline-plugin.d.ts | 30 + .../plugins/hsladjustpipeline-plugin.js | 14 + .../plugins/hsladjustpipeline.d.ts | 2 + .../plugins/hsladjustpipeline.js | 2 + .../plugins/imagebox-plugin.js | 23 + .../phaser3-rex-plugins/plugins/imagebox.d.ts | 2 + .../phaser3-rex-plugins/plugins/imagebox.js | 2 + .../plugins/imageuriloader-plugin.js | 16 + .../plugins/imageuriloader.d.ts | 3 + .../plugins/imageuriloader.js | 6 + .../plugins/input/button/Button.d.ts | 53 + .../plugins/input/button/Button.js | 198 + .../input/clickoutside/ClickOutside.d.ts | 52 + .../input/clickoutside/ClickOutside.js | 153 + .../input/cursoratbound/CursorAtBound.d.ts | 28 + .../input/cursoratbound/CursorAtBound.js | 84 + .../plugins/input/drag/Drag.d.ts | 34 + .../plugins/input/drag/Drag.js | 181 + .../plugins/input/dragrotate/DragRotate.d.ts | 48 + .../plugins/input/dragrotate/DragRotate.js | 222 + .../plugins/input/dragspeed/DragSpeed.js | 223 + .../plugins/input/gestures/ObjectFactory.js | 10 + .../onepointertracer/OnePointerTracer.d.ts | 27 + .../onepointertracer/OnePointerTracer.js | 260 + .../plugins/input/gestures/pan/Factory.d.ts | 6 + .../plugins/input/gestures/pan/Factory.js | 16 + .../plugins/input/gestures/pan/Pan.d.ts | 32 + .../plugins/input/gestures/pan/Pan.js | 100 + .../plugins/input/gestures/pinch/Factory.d.ts | 7 + .../plugins/input/gestures/pinch/Factory.js | 11 + .../plugins/input/gestures/pinch/Pinch.d.ts | 30 + .../plugins/input/gestures/pinch/Pinch.js | 91 + .../plugins/input/gestures/press/Factory.d.ts | 7 + .../plugins/input/gestures/press/Factory.js | 16 + .../plugins/input/gestures/press/Press.d.ts | 34 + .../plugins/input/gestures/press/Press.js | 112 + .../input/gestures/rotate/Factory.d.ts | 7 + .../plugins/input/gestures/rotate/Factory.js | 11 + .../plugins/input/gestures/rotate/Rotate.d.ts | 35 + .../plugins/input/gestures/rotate/Rotate.js | 109 + .../input/gestures/rotate/SpinObject.js | 37 + .../plugins/input/gestures/swipe/Factory.d.ts | 7 + .../plugins/input/gestures/swipe/Factory.js | 16 + .../plugins/input/gestures/swipe/Swipe.d.ts | 41 + .../plugins/input/gestures/swipe/Swipe.js | 152 + .../input/gestures/swipe/VelocityMethods.js | 42 + .../plugins/input/gestures/tap/Factory.d.ts | 7 + .../plugins/input/gestures/tap/Factory.js | 16 + .../plugins/input/gestures/tap/Tap.d.ts | 52 + .../plugins/input/gestures/tap/Tap.js | 198 + .../twopointerstracer/TwoPointersTracer.d.ts | 36 + .../twopointerstracer/TwoPointersTracer.js | 336 ++ .../plugins/input/intouching/InTouching.d.ts | 28 + .../plugins/input/intouching/InTouching.js | 137 + .../plugins/input/keyshub/KeyHub.js | 113 + .../plugins/input/keyshub/KeysHub.js | 122 + .../MouseWheelScroller.d.ts | 32 + .../mousewheelscroller/MouseWheelScroller.js | 70 + .../MouseWheelToUpDown.d.ts | 26 + .../mousewheeltoupdown/MouseWheelToUpDown.js | 52 + .../plugins/input/scroller/Scroller.d.ts | 64 + .../plugins/input/scroller/Scroller.js | 365 ++ .../plugins/input/scroller/State.js | 127 + .../plugins/input/slider/Slider.d.ts | 66 + .../plugins/input/slider/Slider.js | 178 + .../plugins/input/touchcursor/TouchCursor.js | 160 + .../input/toucheventstop/TouchEventStop.d.ts | 30 + .../input/toucheventstop/TouchEventStop.js | 107 + .../plugins/input/touchgroup/TouchGroup.js | 50 + .../plugins/input/touchstate/TouchState.d.ts | 21 + .../plugins/input/touchstate/TouchState.js | 177 + .../virtualjoystick/VirtualJoyStick.d.ts | 72 + .../input/virtualjoystick/VirtualJoyStick.js | 262 ++ .../plugins/inputtext-plugin.js | 23 + .../plugins/inputtext.d.ts | 2 + .../phaser3-rex-plugins/plugins/inputtext.js | 2 + .../plugins/interception-plugin.d.ts | 9 + .../plugins/interception-plugin.js | 13 + .../plugins/interception.d.ts | 2 + .../plugins/interception.js | 2 + .../plugins/intouching-plugin.js | 20 + .../plugins/intouching.d.ts | 2 + .../phaser3-rex-plugins/plugins/intouching.js | 2 + .../plugins/inversepipeline-plugin.d.ts | 30 + .../plugins/inversepipeline-plugin.js | 14 + .../plugins/inversepipeline.d.ts | 2 + .../plugins/inversepipeline.js | 2 + .../plugins/kawaseblurpipeline-plugin.d.ts | 30 + .../plugins/kawaseblurpipeline-plugin.js | 16 + .../plugins/kawaseblurpipeline.d.ts | 2 + .../plugins/kawaseblurpipeline.js | 2 + .../plugins/keyshub-plugin.js | 20 + ui/src/phaser3-rex-plugins/plugins/keyshub.js | 2 + .../plugins/layermanager-plugin.d.ts | 9 + .../plugins/layermanager-plugin.js | 18 + .../plugins/layermanager.d.ts | 2 + .../plugins/layermanager.js | 2 + .../plugins/lifetime-plugin.d.ts | 9 + .../plugins/lifetime-plugin.js | 20 + .../phaser3-rex-plugins/plugins/lifetime.d.ts | 2 + .../phaser3-rex-plugins/plugins/lifetime.js | 2 + .../plugins/line-plugin.js | 23 + ui/src/phaser3-rex-plugins/plugins/line.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/line.js | 2 + .../plugins/lineprogress-plugin.js | 23 + .../plugins/lineprogress.d.ts | 2 + .../plugins/lineprogress.js | 2 + .../plugins/lineprogresscanvas-plugin.js | 23 + .../plugins/lineprogresscanvas.d.ts | 2 + .../plugins/lineprogresscanvas.js | 2 + .../plugins/live2d-plugin.js | 41 + .../phaser3-rex-plugins/plugins/live2d.d.ts | 11 + ui/src/phaser3-rex-plugins/plugins/live2d.js | 14 + .../plugins/loader/awaitloader/AwaitFile.js | 50 + .../awaitloader/AwaitLoaderCallback.d.ts | 23 + .../loader/awaitloader/AwaitLoaderCallback.js | 34 + .../imageuri/ImageURILoaderCallback.d.ts | 8 + .../loader/imageuri/ImageURILoaderCallback.js | 32 + .../scripttag/ScriptTagLoaderCallback.d.ts | 15 + .../scripttag/ScriptTagLoaderCallback.js | 57 + .../plugins/loader/webfontloader/TestFont.js | 44 + .../plugins/loader/webfontloader/WebFont.js | 88 + .../webfontloader/WebFontLoaderCallback.d.ts | 8 + .../webfontloader/WebFontLoaderCallback.js | 32 + .../plugins/loadingprogress-plugin.js | 14 + .../plugins/loadingprogress.js | 2 + .../plugins/localforage-files-plugin.d.ts | 8 + .../plugins/localforage-files-plugin.js | 18 + .../plugins/localforage-files.d.ts | 2 + .../plugins/localforage-files.js | 2 + .../plugins/localstorage-data-plugin.d.ts | 34 + .../plugins/localstorage-data-plugin.js | 40 + .../plugins/localstorage-data.d.ts | 2 + .../plugins/localstorage-data.js | 2 + .../achievements/Achievements.d.ts | 95 + .../achievements/achievements/Achievements.js | 153 + .../csvachievements/Achievements.d.ts | 15 + .../csvachievements/Achievements.js | 26 + .../ymlachievements/Achievements.d.ts | 12 + .../ymlachievements/Achievements.js | 24 + .../plugins/logic/behaviortree/Factory.js | 86 + .../plugins/logic/behaviortree/LICENSE | 22 + .../logic/behaviortree/ObjectFactory.js | 6 + .../plugins/logic/behaviortree/README.md | 100 + .../behaviortree/behaviortree/BehaviorTree.js | 184 + .../logic/behaviortree/behaviortree/Dump.js | 68 + .../behaviortree/behaviortree/Factory.js | 13 + .../logic/behaviortree/behaviortree/Load.js | 65 + .../behaviortree/behaviortree/Traversal.js | 77 + .../logic/behaviortree/blackboard/Base.js | 171 + .../behaviortree/blackboard/Blackboard.js | 20 + .../logic/behaviortree/blackboard/Factory.js | 13 + .../plugins/logic/behaviortree/constants.js | 16 + .../plugins/logic/behaviortree/index.js | 106 + .../logic/behaviortree/nodes/Action.js | 61 + .../logic/behaviortree/nodes/BaseNode.js | 198 + .../logic/behaviortree/nodes/Composite.js | 102 + .../logic/behaviortree/nodes/Decorator.js | 55 + .../logic/behaviortree/nodes/Factory.js | 192 + .../logic/behaviortree/nodes/Service.js | 66 + .../logic/behaviortree/nodes/actions/Abort.js | 24 + .../logic/behaviortree/nodes/actions/Error.js | 24 + .../behaviortree/nodes/actions/Failer.js | 24 + .../behaviortree/nodes/actions/Runner.js | 24 + .../behaviortree/nodes/actions/Succeeder.js | 24 + .../logic/behaviortree/nodes/actions/Wait.js | 46 + .../nodes/composites/IfSelector.js | 72 + .../behaviortree/nodes/composites/Parallel.js | 127 + .../nodes/composites/RandomSelector.js | 58 + .../behaviortree/nodes/composites/Selector.js | 68 + .../behaviortree/nodes/composites/Sequence.js | 63 + .../nodes/composites/ShuffleSelector.js | 70 + .../nodes/composites/SwitchSelector.js | 80 + .../nodes/composites/WeightSelector.js | 98 + .../behaviortree/nodes/decorators/AbortIf.js | 54 + .../behaviortree/nodes/decorators/Bypass.js | 38 + .../nodes/decorators/ContinueIf.js | 54 + .../behaviortree/nodes/decorators/Cooldown.js | 64 + .../nodes/decorators/ForceFailure.js | 44 + .../nodes/decorators/ForceSuccess.js | 43 + .../logic/behaviortree/nodes/decorators/If.js | 57 + .../behaviortree/nodes/decorators/Invert.js | 42 + .../behaviortree/nodes/decorators/Limiter.js | 62 + .../behaviortree/nodes/decorators/Repeat.js | 64 + .../nodes/decorators/RepeatUntilFailure.js | 73 + .../nodes/decorators/RepeatUntilSuccess.js | 65 + .../nodes/decorators/TimeLimit.js | 60 + .../nodes/expressions/BaseExpression.js | 12 + .../nodes/expressions/BooleanExpression.js | 9 + .../nodes/expressions/Expression.js | 21 + .../expressions/StringTemplateExpression.js | 13 + .../behaviortree/nodes/expressions/index.js | 11 + .../plugins/logic/behaviortree/nodes/index.js | 73 + .../behaviortree/parsers/yaml/CreateNode.js | 117 + .../behaviortree/parsers/yaml/Factory.js | 14 + .../behaviortree/parsers/yaml/Handlers.js | 58 + .../behaviortree/parsers/yaml/LoadYaml.js | 23 + .../behaviortree/parsers/yaml/actions/Wait.js | 16 + .../parsers/yaml/composites/IfSelector.js | 17 + .../parsers/yaml/composites/Parallel.js | 17 + .../parsers/yaml/composites/RandomSelector.js | 16 + .../parsers/yaml/composites/Selector.js | 16 + .../parsers/yaml/composites/Sequence.js | 16 + .../yaml/composites/ShuffleSelector.js | 16 + .../parsers/yaml/composites/SwitchSelector.js | 17 + .../parsers/yaml/composites/WeightSelector.js | 19 + .../parsers/yaml/decorators/AbortIf.js | 24 + .../parsers/yaml/decorators/ContinueIf.js | 24 + .../parsers/yaml/decorators/Cooldown.js | 24 + .../parsers/yaml/decorators/ForceFailure.js | 21 + .../parsers/yaml/decorators/ForceSuccess.js | 21 + .../parsers/yaml/decorators/If.js | 24 + .../parsers/yaml/decorators/Invert.js | 21 + .../parsers/yaml/decorators/Repeat.js | 25 + .../yaml/decorators/RepeatUntilFailure.js | 25 + .../yaml/decorators/RepeatUntilSuccess.js | 25 + .../parsers/yaml/decorators/TimeLimit.js | 24 + .../plugins/logic/behaviortree/tick/Tick.js | 95 + .../logic/behaviortree/utils/CreateID.js | 36 + .../bracketparser/BracketParser.d.ts | 27 + .../bracketparser/BracketParser.js | 95 + .../bracketparser/bracketparser/ParseValue.js | 12 + .../bracketparser2/BracketParser.d.ts | 26 + .../bracketparser2/BracketParser.js | 84 + .../bracketparser2/ParseValue.js | 29 + .../bracketparserbase/BracketParser.d.ts | 44 + .../bracketparserbase/BracketParser.js | 260 + .../bracketparserbase/ParseValue.js | 29 + .../TokenExpressionMethods.js | 60 + .../bracketparser/tagplayer/TagPlayer.d.ts | 123 + .../bracketparser/tagplayer/TagPlayer.js | 91 + .../tagplayer/methods/ContentMethods.js | 7 + .../tagplayer/methods/Methods.js | 33 + .../tagplayer/methods/PauseMethods.js | 11 + .../tagplayer/methods/PlayMethods.js | 15 + .../tagplayer/methods/ResumeMethods.js | 6 + .../tagplayer/methods/SetClickTarget.js | 17 + .../tagplayer/methods/SetSkipSoundEffect.js | 16 + .../tagplayer/methods/SetTargetCamera.js | 6 + .../bracketparser/tagplayer/methods/Wait.js | 15 + .../GameObjectManagerMethods.js | 43 + .../OnParseAddGameObjectTag.js | 46 + .../OnParseCallGameObjectMethodTag.js | 48 + .../OnParseEaseGameObjectPropertyTag.js | 76 + .../OnParseRemoveAllGameObjectsTag.js | 21 + .../methods/spritemanager/AddSpriteManager.js | 21 + .../spritemanager/OnParseChainAnimationTag.js | 30 + .../spritemanager/OnParsePauseAnimationTag.js | 29 + .../spritemanager/OnParsePlayAnimationTag.js | 73 + .../methods/spritemanager/SpriteMethods.js | 11 + .../methods/textmanager/AddTextManager.js | 19 + .../methods/textmanager/OnParseSetTextTag.js | 16 + .../textmanager/OnParseTypingTextTag.js | 19 + .../methods/textmanager/TextMethods.js | 11 + .../tagplayer/methods/utils/ClearEvents.js | 9 + .../tagplayer/methods/utils/Events.js | 15 + .../methods/utils/wait/GetWrapCallback.js | 9 + .../methods/utils/wait/WaitCallback.js | 10 + .../methods/utils/wait/WaitCameraEffect.js | 77 + .../tagplayer/methods/utils/wait/WaitClick.js | 23 + .../methods/utils/wait/WaitGameObject.js | 107 + .../methods/utils/wait/WaitKeyDown.js | 20 + .../methods/utils/wait/WaitMultiple.js | 62 + .../tagplayer/methods/utils/wait/WaitMusic.js | 23 + .../tagplayer/methods/utils/wait/WaitTime.js | 22 + .../tagplayer/parser/AddParseCallbacks.js | 50 + .../bracketparser/tagplayer/parser/Parser.js | 33 + .../tagplayer/parser/PreProcessSource.js | 28 + .../OnParseCrossFadeBackgroundMusicTag.js | 26 + .../OnParseFadeInBackgroundMusicTag.js | 26 + .../OnParseFadeOutBackgroundMusicTag.js | 28 + .../OnParsePauseBackgroundMusicTag.js | 30 + .../OnParsePlayBackgroundMusicTag.js | 36 + .../OnParseSetBackgroundMusicVolumeTag.js | 26 + .../parser/camera/OnParseFadeInCameraTag.js | 11 + .../parser/camera/OnParseFadeOutCameraTag.js | 11 + .../parser/camera/OnParseFlashCameraTag.js | 11 + .../parser/camera/OnParseRotateCameraTag.js | 19 + .../parser/camera/OnParseScrollCameraTag.js | 26 + .../parser/camera/OnParseShakeCameraTag.js | 11 + .../parser/camera/OnParseZoomCameraTag.js | 16 + .../parser/content/OnParseContent.js | 32 + .../parser/custom/OnParseCustomTag.js | 19 + .../OnParseFadeInSoundEffectTag.js | 26 + .../OnParseFadeOutSoundEffectTag.js | 28 + .../soundeffect/OnParsePlaySoundEffectTag.js | 40 + .../OnParseSetSoundEffectVolumeTag.js | 26 + .../tagplayer/parser/wait/OnParseWaitTag.js | 21 + .../conditiontable/ConditionsTable.d.ts | 54 + .../conditiontable/ConditionsTable.js | 93 + .../csvconditiontable/ConditionsTable.d.ts | 17 + .../csvconditiontable/ConditionsTable.js | 27 + .../csvconditiontable/CreateTestFunction.js | 37 + .../ymlconditiontable/ConditionsTable.d.ts | 13 + .../ymlconditiontable/ConditionsTable.js | 33 + .../eventsheettrees/EventBehaviorTree.js | 32 + .../eventsheettrees/EventSheetTrees.d.ts | 50 + .../eventsheettrees/EventSheetTrees.js | 53 + .../eventsheets/eventsheettrees/TaskAction.js | 73 + .../eventsheettrees/TaskSequence.js | 29 + .../logic/eventsheets/eventsheettrees/Tree.md | 21 + .../methods/CustomNodeMapping.js | 5 + .../eventsheettrees/methods/DataMethods.js | 24 + .../eventsheettrees/methods/RunMethods.js | 123 + .../eventsheettrees/methods/StateMethods.js | 46 + .../eventsheettrees/methods/TreeMethods.js | 84 + .../methods/ValueConvertMethods.js | 16 + .../markedeventsheets/MarkedEventSheets.d.ts | 23 + .../markedeventsheets/MarkedEventSheets.js | 22 + .../methods/CreateTaskSequence.js | 193 + .../methods/GetConditionExpression.js | 56 + .../methods/GetHeadingTree.js | 62 + .../markedeventsheets/methods/GetNodeType.js | 13 + .../methods/GetTreeConfig.js | 15 + .../markedeventsheets/methods/Marked2Tree.js | 43 + .../markedeventsheets/methods/ParseNodes.js | 56 + .../methods/ParseProperty.js | 16 + .../plugins/logic/fsm/FSM.d.ts | 64 + .../plugins/logic/fsm/FSM.js | 137 + .../plugins/logic/fsm/FSMBase.d.ts | 66 + .../plugins/logic/fsm/FSMBase.js | 232 + .../logic/loopindexgenerator/LoopIndex.js | 41 + .../loopindexgenerator/LoopIndexGenerator.js | 114 + .../plugins/logic/loopinticks/LoopInTicks.js | 101 + .../plugins/logic/quest/quest/Quest.d.ts | 34 + .../plugins/logic/quest/quest/Quest.js | 110 + .../logic/quest/questions/AddQuestion.js | 35 + .../logic/quest/questions/DataMethods.js | 25 + .../logic/quest/questions/QuestMethods.js | 40 + .../quest/questions/QuestionManager.d.ts | 115 + .../logic/quest/questions/QuestionManager.js | 115 + .../logic/quest/questions/parse/ParseCSV.js | 66 + .../quest/questions/parse/ParseInputData.js | 34 + .../logic/quest/questions/parse/ParseYaml.js | 27 + .../plugins/logic/quest/questions/types.d.ts | 10 + .../logic/runcommands/RunCommands.d.ts | 17 + .../plugins/logic/runcommands/RunCommands.js | 64 + .../logic/runcommands/arcadetcrp/Player.d.ts | 12 + .../logic/runcommands/arcadetcrp/Player.js | 22 + .../runcommands/arcadetcrp/Recorder.d.ts | 13 + .../logic/runcommands/arcadetcrp/Recorder.js | 14 + .../runcommands/arcadetcrp/StepRunner.d.ts | 18 + .../runcommands/arcadetcrp/StepRunner.js | 51 + .../runcommands/csvscenario/CSVScenario.d.ts | 79 + .../runcommands/csvscenario/CSVScenario.js | 394 ++ .../logic/runcommands/csvscenario/InstMem.js | 61 + .../csvscenario/commands/BaseCmd.js | 20 + .../csvscenario/commands/CmdHandlers.js | 45 + .../csvscenario/commands/CustomCmd.js | 104 + .../csvscenario/commands/ExitCmd.js | 19 + .../csvscenario/commands/GotoCmd.js | 31 + .../runcommands/csvscenario/commands/IfCmd.js | 55 + .../csvscenario/commands/LabelCmd.js | 80 + .../csvscenario/commands/WaitCmd.js | 56 + .../logic/runcommands/managers/Managers.d.ts | 2 + .../logic/runcommands/managers/Managers.js | 2 + .../logic/runcommands/sequence/Sequence.d.ts | 44 + .../logic/runcommands/sequence/Sequence.js | 169 + .../logic/runcommands/tcrp/Player.d.ts | 59 + .../plugins/logic/runcommands/tcrp/Player.js | 251 + .../logic/runcommands/tcrp/Recorder.d.ts | 39 + .../logic/runcommands/tcrp/Recorder.js | 127 + .../logic/statemanager/StateManager.d.ts | 67 + .../logic/statemanager/StateManager.js | 110 + .../logic/statemanager/StateManagerBase.d.ts | 58 + .../logic/statemanager/StateManagerBase.js | 199 + .../plugins/logic/waitevents/WaitEvents.d.ts | 32 + .../plugins/logic/waitevents/WaitEvents.js | 62 + .../plugins/loopinticks-plugin.js | 18 + .../plugins/loopinticks.js | 2 + .../plugins/lzstring-plugin.d.ts | 8 + .../plugins/lzstring-plugin.js | 29 + .../phaser3-rex-plugins/plugins/lzstring.d.ts | 2 + .../phaser3-rex-plugins/plugins/lzstring.js | 2 + .../plugins/markedeventsheets-plugin.d.ts | 8 + .../plugins/markedeventsheets-plugin.js | 22 + .../plugins/markedeventsheets.d.ts | 2 + .../plugins/markedeventsheets.js | 2 + .../expressionparser/ExpressionParser.d.ts | 32 + .../math/expressionparser/ExpressionParser.js | 85 + .../math/expressionparser/GetProperty.js | 33 + .../expressionparser/parser/export-parser.bat | 1 + .../math/expressionparser/parser/export.js | 26 + .../expressionparser/parser/grammar.jison | 229 + .../math/expressionparser/parser/parser.js | 843 ++++ .../math/expressionparser/utils/Compile.d.ts | 7 + .../math/expressionparser/utils/Complile.js | 8 + .../plugins/math/fuzzy/BuildFuzzyModule.d.ts | 21 + .../plugins/math/fuzzy/BuildFuzzyModule.js | 36 + .../plugins/math/fuzzy/FuzzyModule.d.ts | 10 + .../plugins/math/fuzzy/FuzzyModule.js | 156 + .../math/fuzzy/rules/BuildFuzzyRule.js | 44 + .../math/fuzzy/rules/BuildFuzzyRules.js | 17 + .../plugins/math/fuzzy/rules/FuzzyRule.js | 23 + .../math/fuzzy/rules/operators/FuzzyAND.js | 30 + .../rules/operators/FuzzyCompositeTerm.js | 34 + .../math/fuzzy/rules/operators/FuzzyFAIRLY.js | 30 + .../math/fuzzy/rules/operators/FuzzyOR.js | 29 + .../math/fuzzy/rules/operators/FuzzyVERY.js | 32 + .../math/fuzzy/utils/GetVariableName.js | 9 + .../plugins/math/fuzzy/utils/IsInvalidLine.js | 12 + .../plugins/math/fuzzy/utils/parser/Parse.js | 8 + .../math/fuzzy/utils/parser/export-parser.bat | 1 + .../plugins/math/fuzzy/utils/parser/export.js | 26 + .../math/fuzzy/utils/parser/grammar.jison | 154 + .../plugins/math/fuzzy/utils/parser/parser.js | 735 +++ .../math/fuzzy/variables/BuildFuzzySet.js | 33 + .../fuzzy/variables/BuildFuzzyVariable.js | 18 + .../fuzzy/variables/BuildFuzzyVariables.js | 52 + .../math/fuzzy/variables/FuzzyVariable.js | 116 + .../math/fuzzy/variables/GetAllFuzzySets.js | 14 + .../math/fuzzy/variables/sets/FuzzySet.js | 43 + .../variables/sets/LeftSCurveFuzzySet.js | 52 + .../variables/sets/LeftShoulderFuzzySet.js | 43 + .../variables/sets/NormalDistFuzzySet.js | 64 + .../variables/sets/RightSCurveFuzzySet.js | 50 + .../variables/sets/RightShoulderFuzzySet.js | 43 + .../fuzzy/variables/sets/SingletonFuzzySet.js | 26 + .../variables/sets/TriangularFuzzySet.js | 45 + .../plugins/math/gashapon/Gashapon.d.ts | 87 + .../plugins/math/gashapon/Gashapon.js | 339 ++ .../plugins/math/raycaster/GetLineToPoints.js | 57 + .../math/raycaster/GetLineToPolygon.js | 57 + .../plugins/math/raycaster/Obstacles.js | 112 + .../plugins/math/raycaster/Raycaster.d.ts | 44 + .../plugins/math/raycaster/Raycaster.js | 111 + .../plugins/maxdelta-plugin.js | 39 + .../plugins/modal-plugin.d.ts | 12 + .../plugins/modal-plugin.js | 30 + ui/src/phaser3-rex-plugins/plugins/modal.d.ts | 4 + ui/src/phaser3-rex-plugins/plugins/modal.js | 4 + .../plugins/mousewheelscroller-plugin.d.ts | 9 + .../plugins/mousewheelscroller-plugin.js | 20 + .../plugins/mousewheelscroller.d.ts | 2 + .../plugins/mousewheelscroller.js | 2 + .../plugins/mousewheeltoupdown-plugin.d.ts | 9 + .../plugins/mousewheeltoupdown-plugin.js | 20 + .../plugins/mousewheeltoupdown.d.ts | 2 + .../plugins/mousewheeltoupdown.js | 2 + .../plugins/moveto-plugin.d.ts | 9 + .../plugins/moveto-plugin.js | 19 + .../phaser3-rex-plugins/plugins/moveto.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/moveto.js | 2 + .../plugins/ninepatch-plugin.js | 23 + .../plugins/ninepatch.d.ts | 2 + .../phaser3-rex-plugins/plugins/ninepatch.js | 2 + .../plugins/ninepatch2-plugin.js | 23 + .../plugins/ninepatch2.d.ts | 2 + .../phaser3-rex-plugins/plugins/ninepatch2.js | 2 + .../plugins/objectpool-plugin.js | 18 + .../phaser3-rex-plugins/plugins/objectpool.js | 2 + .../plugins/outlineeffectlayer-plugin.js | 23 + .../plugins/outlineeffectlayer.js | 2 + .../plugins/outlinepipeline-plugin.d.ts | 37 + .../plugins/outlinepipeline-plugin.js | 34 + .../plugins/outlinepipeline.d.ts | 2 + .../plugins/outlinepipeline.js | 2 + .../plugins/parse-plugin.js | 34 + ui/src/phaser3-rex-plugins/plugins/parse.js | 27 + .../plugins/parse/ObjectFactory.js | 15 + .../plugins/parse/itemtable/DeleteMethods.js | 16 + .../plugins/parse/itemtable/Factory.js | 11 + .../plugins/parse/itemtable/GetItemCount.js | 7 + .../plugins/parse/itemtable/GetQuery.js | 13 + .../plugins/parse/itemtable/ItemTable.js | 103 + .../plugins/parse/itemtable/LoadMethods.js | 54 + .../parse/itemtable/LoadRandomItems.js | 34 + .../plugins/parse/itemtable/Save.js | 26 + .../plugins/parse/itemtable/SaveItems.js | 39 + .../plugins/parse/leaderboard/Const.js | 26 + .../parse/leaderboard/DeleteMethods.js | 25 + .../plugins/parse/leaderboard/Factory.js | 11 + .../parse/leaderboard/GetQueryMethods.js | 41 + .../plugins/parse/leaderboard/GetRank.js | 19 + .../plugins/parse/leaderboard/GetScore.js | 15 + .../plugins/parse/leaderboard/GetTime.js | 16 + .../plugins/parse/leaderboard/LeaderBoard.js | 135 + .../plugins/parse/leaderboard/LoadMethods.js | 86 + .../plugins/parse/leaderboard/Post.js | 60 + .../plugins/parse/pageloader/Factory.js | 11 + .../plugins/parse/pageloader/LoadMethods.js | 53 + .../plugins/parse/pageloader/PageLoader.js | 45 + .../plugins/parse/quicklogin/QuickLogin.js | 23 + .../plugins/parse/utils/DataToItem.js | 9 + .../plugins/parse/utils/InitialTable.js | 8 + .../plugins/parse/utils/SetOwnerAccessMode.js | 21 + .../parse/utils/preload/LoaderCallback.js | 19 + .../plugins/parse/utils/preload/Preload.js | 14 + .../plugins/parse/utils/query/Delete.js | 15 + .../plugins/parse/utils/query/FindFirst.js | 29 + .../plugins/parse/utils/query/Load.js | 17 + .../plugins/parse/utils/query/Query.js | 39 + .../plugins/particlesalongbounds-plugin.d.ts | 5 + .../plugins/particlesalongbounds-plugin.js | 18 + .../plugins/particlesalongbounds.d.ts | 2 + .../plugins/particlesalongbounds.js | 2 + .../plugins/pathfollower-plugin.d.ts | 9 + .../plugins/pathfollower-plugin.js | 20 + .../plugins/pathfollower.d.ts | 2 + .../plugins/pathfollower.js | 2 + .../plugins/perlin-plugin.d.ts | 6 + .../plugins/perlin-plugin.js | 20 + .../phaser3-rex-plugins/plugins/perlin.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/perlin.js | 2 + .../plugins/perlingrivatywell-plugin.js | 20 + .../plugins/perlingrivatywell.js | 2 + .../plugins/persistenceeffect-plugin.js | 23 + .../plugins/persistenceeffect.js | 2 + .../plugins/perspectiveimage-plugin.js | 62 + .../plugins/perspectiveimage.d.ts | 17 + .../plugins/perspectiveimage.js | 17 + .../plugins/pinch-plugin.js | 20 + ui/src/phaser3-rex-plugins/plugins/pinch.js | 2 + .../plugins/pixelationpipeline-plugin.d.ts | 32 + .../plugins/pixelationpipeline-plugin.js | 14 + .../plugins/pixelationpipeline.d.ts | 2 + .../plugins/pixelationpipeline.js | 2 + .../plugins/pngappender-plugin.d.ts | 7 + .../plugins/pngappender-plugin.js | 20 + .../plugins/pngappender.d.ts | 9 + .../plugins/pngappender.js | 7 + .../plugins/polarcoordinate-plugin.d.ts | 5 + .../plugins/polarcoordinate-plugin.js | 19 + .../plugins/polarcoordinate.d.ts | 2 + .../plugins/polarcoordinate.js | 2 + ui/src/phaser3-rex-plugins/plugins/pool.js | 2 + ui/src/phaser3-rex-plugins/plugins/popup.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/popup.js | 2 + .../plugins/postfxpipelinebehavior.js | 2 + .../plugins/quadimage-plugin.js | 51 + .../plugins/quadimage.d.ts | 13 + .../phaser3-rex-plugins/plugins/quadimage.js | 13 + .../plugins/quest-plugin.d.ts | 8 + .../plugins/quest-plugin.js | 18 + ui/src/phaser3-rex-plugins/plugins/quest.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/quest.js | 2 + .../plugins/randomplace-plugin.d.ts | 6 + .../plugins/randomplace-plugin.js | 19 + .../plugins/randomplace.d.ts | 2 + .../plugins/randomplace.js | 2 + .../plugins/raycaster-plugin.d.ts | 8 + .../plugins/raycaster-plugin.js | 19 + .../plugins/raycaster.d.ts | 2 + .../phaser3-rex-plugins/plugins/raycaster.js | 2 + .../plugins/realtimetimers-plugin.d.ts | 8 + .../plugins/realtimetimers-plugin.js | 20 + .../plugins/realtimetimers.d.ts | 2 + .../plugins/realtimetimers.js | 2 + .../plugins/requestdrag.d.ts | 2 + .../plugins/requestdrag.js | 2 + .../plugins/restorabledata-plugin.d.ts | 10 + .../plugins/restorabledata-plugin.js | 20 + .../plugins/restorabledata.d.ts | 2 + .../plugins/restorabledata.js | 2 + .../plugins/rhombus-plugin.js | 19 + ui/src/phaser3-rex-plugins/plugins/rhombus.js | 2 + .../plugins/rotate-plugin.d.ts | 9 + .../plugins/rotate-plugin.js | 19 + .../phaser3-rex-plugins/plugins/rotate.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/rotate.js | 2 + .../plugins/rotateto-plugin.d.ts | 9 + .../plugins/rotateto-plugin.js | 19 + .../phaser3-rex-plugins/plugins/rotateto.d.ts | 2 + .../phaser3-rex-plugins/plugins/rotateto.js | 2 + .../plugins/roundrectangle-plugin.js | 23 + .../plugins/roundrectangle.d.ts | 2 + .../plugins/roundrectangle.js | 2 + .../plugins/roundrectanglecanvas-plugin.js | 23 + .../plugins/roundrectanglecanvas.d.ts | 2 + .../plugins/roundrectanglecanvas.js | 2 + .../plugins/runcommands-plugin.d.ts | 10 + .../plugins/runcommands-plugin.js | 14 + .../plugins/runcommands.d.ts | 2 + .../plugins/runcommands.js | 2 + .../plugins/scale-down-destroy.d.ts | 2 + .../plugins/scale-down-destroy.js | 2 + .../plugins/scale-plugin.d.ts | 17 + .../plugins/scale-plugin.js | 35 + ui/src/phaser3-rex-plugins/plugins/scale.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/scale.js | 2 + .../scale/scaleouter/CheckScaleMode.js | 17 + .../scale/scaleouter/GetInnerViewport.js | 14 + .../scale/scaleouter/GetOuterViewport.js | 18 + .../GetScaleOuterCameraParameters.js | 28 + .../plugins/scale/scaleouter/ScaleOuter.d.ts | 30 + .../plugins/scale/scaleouter/ScaleOuter.js | 132 + .../scale/scaleouter/ShrinkSizeByRatio.js | 17 + .../plugins/scaleouter-plugin.d.ts | 19 + .../plugins/scaleouter-plugin.js | 64 + .../plugins/scaleouter.d.ts | 2 + .../phaser3-rex-plugins/plugins/scaleouter.js | 2 + .../plugins/scripttagloader-plugin.js | 16 + .../plugins/scripttagloader.d.ts | 3 + .../plugins/scripttagloader.js | 6 + .../plugins/scroller-plugin.d.ts | 9 + .../plugins/scroller-plugin.js | 20 + .../phaser3-rex-plugins/plugins/scroller.d.ts | 2 + .../phaser3-rex-plugins/plugins/scroller.js | 2 + .../plugins/sequence-plugin.d.ts | 8 + .../plugins/sequence-plugin.js | 18 + .../phaser3-rex-plugins/plugins/sequence.d.ts | 2 + .../phaser3-rex-plugins/plugins/sequence.js | 2 + .../shaders/barrel/BarrelPostFxPipeline.d.ts | 33 + .../shaders/barrel/BarrelPostFxPipeline.js | 86 + .../shaders/barrel/barrel-postfxfrag.js | 37 + .../ColorReplacePostFxPipeline.d.ts | 24 + .../ColorReplacePostFxPipeline.js | 73 + .../colorreplace/colorreplace-postfxfrag.js | 27 + .../CrossStitchingPostFxPipeline.d.ts | 23 + .../CrossStitchingPostFxPipeline.js | 69 + .../crossstitching-postfxfrag.js | 50 + .../dissolve/DissolvePostFxPipeline.d.ts | 52 + .../dissolve/DissolvePostFxPipeline.js | 159 + .../shaders/dissolve/dissolve-postfxfrag.js | 67 + .../dropshadow/DropShadowPostFxPipeline.d.ts | 54 + .../dropshadow/DropShadowPostFxPipeline.js | 230 + .../shaders/dropshadow/ShadowDrawer.js | 30 + .../dropshadow/dropshadow-postfxfrag.js | 31 + .../fisheye/FishEyePostFxPipeline.d.ts | 30 + .../shaders/fisheye/FishEyePostFxPipeline.js | 82 + .../shaders/fisheye/fisheye-postfxfrag.js | 40 + .../glowfilter/GlowFilterPostFxPipeline.d.ts | 16 + .../glowfilter/GlowFilterPostFxPipeline.js | 34 + .../glowfilter/glowfilter-postfxfrag.js | 38 + .../glowfilter2/GlowFilterPostFxPipeline.d.ts | 34 + .../glowfilter2/GlowFilterPostFxPipeline.js | 107 + .../glowfilter2/glowfilter-postfxfrag.js | 85 + .../grayscale/GrayScalePostFxPipeline.d.ts | 16 + .../grayscale/GrayScalePostFxPipeline.js | 34 + .../shaders/grayscale/grayscale-postfxfrag.js | 20 + .../horrifi/HorrifiPostFxPipeline.d.ts | 111 + .../shaders/horrifi/HorrifiPostFxPipeline.js | 139 + .../HorrifiPostFxPipelineBehavior.d.ts | 21 + .../horrifi/HorrifiPostFxPipelineBehavior.js | 10 + .../shaders/horrifi/horrifi-postfxfrag.js | 160 + .../methods/BloonConfigurationMethods.js | 35 + .../horrifi/methods/CRTConfigurationMethod.js | 18 + .../methods/ChromaticConfigurationMethods.js | 14 + .../shaders/horrifi/methods/Methods.js | 25 + .../methods/NoiseConfigurationMethod.js | 19 + .../methods/ScanlinesConfigurationMethod.js | 14 + .../shaders/horrifi/methods/SetEnable.js | 16 + .../horrifi/methods/VHSConfigurationMethod.js | 14 + .../methods/VignetteConfigurationMethod.js | 19 + .../hsladjust/HslAdjustPostFxPipeline.d.ts | 23 + .../hsladjust/HslAdjustPostFxPipeline.js | 52 + .../shaders/hsladjust/hslAdjust-postfxfrag.js | 34 + .../inverse/InversePostFxPipeline.d.ts | 15 + .../shaders/inverse/InversePostFxPipeline.js | 34 + .../shaders/inverse/inverse-postfxfrag.js | 23 + .../shaders/kawaseblur/GenerateKernels.js | 13 + .../shaders/kawaseblur/KawaseBlurDrawer.js | 40 + .../KawaseBlurFilterPostFxPipeline.d.ts | 25 + .../KawaseBlurFilterPostFxPipeline.js | 125 + .../kawaseblur/kawaseblurFilter-postfxfrag.js | 38 + .../outline/OutlinePostFxPipeline.d.ts | 12 + .../shaders/outline/OutlinePostFxPipeline.js | 69 + .../shaders/outline/outline-postfxfrag.js | 54 + .../pixelation/PixelationPostFxPipeline.d.ts | 10 + .../pixelation/PixelationPostFxPipeline.js | 63 + .../pixelation/pixelation-postfxfrag.js | 33 + .../shockwave/ShockwavePostFxPipeline.d.ts | 20 + .../shockwave/ShockwavePostFxPipeline.js | 87 + .../shaders/shockwave/shockwave-postfxfrag.js | 43 + .../shaders/split/SplitPostFxPipeline.d.ts | 27 + .../shaders/split/SplitPostFxPipeline.js | 182 + .../plugins/shaders/split/split-postfxfrag.js | 56 + .../shaders/swirl/SwirlPostFxPipeline.d.ts | 15 + .../shaders/swirl/SwirlPostFxPipeline.js | 82 + .../plugins/shaders/swirl/swirl-postfxfrag.js | 37 + .../toonify/ToonifyPostFxPipeline.d.ts | 18 + .../shaders/toonify/ToonifyPostFxPipeline.js | 124 + .../shaders/toonify/toonify-postfxfrag.js | 51 + .../plugins/shaders/utils/AvgRGB.js | 6 + .../plugins/shaders/utils/HSLToRGB.js | 27 + .../plugins/shaders/utils/HSVToRGB.js | 48 + .../plugins/shaders/utils/HUEToRGB.js | 24 + .../plugins/shaders/utils/IsEdge.js | 39 + .../plugins/shaders/utils/RGBToHSL.js | 43 + .../plugins/shaders/utils/RGBToHSV.js | 36 + .../plugins/shaders/utils/drawer/Drawer.js | 41 + .../shaders/utils/drawer/alpha/AlphaDrawer.js | 37 + .../utils/drawer/alpha/alpha-postfxfrag.js | 21 + .../plugins/shaders/utils/noise/Perlin.js | 38 + .../plugins/shaders/utils/noise/Turbulence.js | 17 + .../shaders/utils/spritefxcontrol/Base.js | 23 + .../shaders/warp/WarpPostFxPipeline.d.ts | 43 + .../shaders/warp/WarpPostFxPipeline.js | 162 + .../warp/WarpPostFxPipelineBehavior.d.ts | 21 + .../warp/WarpPostFxPipelineBehavior.js | 10 + .../plugins/shaders/warp/warp-postfxfrag.js | 33 + .../plugins/shakeposition-plugin.d.ts | 9 + .../plugins/shakeposition-plugin.js | 19 + .../plugins/shakeposition.d.ts | 2 + .../plugins/shakeposition.js | 2 + .../plugins/shatterimage-plugin.js | 30 + .../plugins/shatterimage.d.ts | 2 + .../plugins/shatterimage.js | 9 + .../plugins/ship-plugin.d.ts | 9 + .../plugins/ship-plugin.js | 20 + ui/src/phaser3-rex-plugins/plugins/ship.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/ship.js | 2 + .../plugins/shockwavepipeline-plugin.d.ts | 36 + .../plugins/shockwavepipeline-plugin.js | 14 + .../plugins/shockwavepipeline.d.ts | 2 + .../plugins/shockwavepipeline.js | 2 + .../plugins/slider-plugin.d.ts | 9 + .../plugins/slider-plugin.js | 20 + .../phaser3-rex-plugins/plugins/slider.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/slider.js | 2 + .../plugins/soundfade-plugin.d.ts | 7 + .../plugins/soundfade-plugin.js | 20 + .../plugins/soundfade.d.ts | 9 + .../phaser3-rex-plugins/plugins/soundfade.js | 7 + .../plugins/spiralcurve-plugin.d.ts | 15 + .../plugins/spiralcurve-plugin.js | 22 + .../plugins/spiralcurve.d.ts | 2 + .../plugins/spiralcurve.js | 2 + .../plugins/splitpipeline-plugin.d.ts | 36 + .../plugins/splitpipeline-plugin.js | 14 + .../plugins/splitpipeline.d.ts | 2 + .../plugins/splitpipeline.js | 2 + .../plugins/statemanager-plugin.d.ts | 8 + .../plugins/statemanager-plugin.js | 23 + .../plugins/statemanager.d.ts | 2 + .../plugins/statemanager.js | 2 + .../plugins/step-plugin.d.ts | 9 + .../plugins/step-plugin.js | 19 + ui/src/phaser3-rex-plugins/plugins/step.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/step.js | 2 + .../storage/localforage/files/Clear.js | 8 + .../localforage/files/DataProcessMethods.js | 17 + .../storage/localforage/files/Delete.js | 10 + .../storage/localforage/files/Files.d.ts | 51 + .../storage/localforage/files/Files.js | 35 + .../storage/localforage/files/GetKey.js | 25 + .../plugins/storage/localforage/files/Load.js | 27 + .../storage/localforage/files/LoadHeaders.js | 28 + .../plugins/storage/localforage/files/Save.js | 73 + .../storage/localforage/utils/GetItems.js | 31 + .../storage/localforage/utils/RemoveItems.js | 27 + .../storage/localforage/utils/SetItems.js | 17 + .../storage/localstorage/data/AddCallbacks.js | 41 + .../localstorage/data/DataManager.d.ts | 34 + .../storage/localstorage/data/DataManager.js | 39 + .../storage/localstorage/data/Extend.d.ts | 6 + .../storage/localstorage/data/Extend.js | 45 + .../localstorage/data/GetDefaultValue.js | 4 + .../plugins/storage/localstorage/data/Load.js | 44 + .../localstorage/data/StorageMethods.js | 28 + .../localstorage/utils/StorageMethods.js | 43 + .../plugins/string/lzstring/LZString.d.ts | 22 + .../plugins/string/lzstring/LZString.js | 71 + .../string/stringtemplate/StringTemplate.js | 123 + .../string/stringtemplate/utils/Complile.js | 8 + .../string/stringtemplate/utils/Render.js | 14 + .../plugins/string/xor/Decrypt.d.ts | 1 + .../plugins/string/xor/Decrypt.js | 40 + .../plugins/string/xor/Encrypt.d.ts | 1 + .../plugins/string/xor/Encrypt.js | 46 + .../plugins/stringtemplate-plugin.js | 37 + .../plugins/stringtemplate.js | 2 + .../plugins/swirlpipeline-plugin.d.ts | 36 + .../plugins/swirlpipeline-plugin.js | 14 + .../plugins/swirlpipeline.d.ts | 2 + .../plugins/swirlpipeline.js | 2 + .../plugins/tagplayer-plugin.d.ts | 13 + .../plugins/tagplayer-plugin.js | 18 + .../plugins/tagplayer.d.ts | 2 + .../phaser3-rex-plugins/plugins/tagplayer.js | 2 + .../plugins/tagtext-plugin.js | 23 + .../phaser3-rex-plugins/plugins/tagtext.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/tagtext.js | 2 + .../plugins/tcrp-plugin.d.ts | 18 + .../plugins/tcrp-plugin.js | 34 + ui/src/phaser3-rex-plugins/plugins/tcrp.d.ts | 11 + ui/src/phaser3-rex-plugins/plugins/tcrp.js | 9 + .../plugins/textedit-plugin.js | 27 + .../phaser3-rex-plugins/plugins/textedit.d.ts | 3 + .../phaser3-rex-plugins/plugins/textedit.js | 3 + .../plugins/textpage-plugin.d.ts | 9 + .../plugins/textpage-plugin.js | 20 + .../phaser3-rex-plugins/plugins/textpage.d.ts | 2 + .../phaser3-rex-plugins/plugins/textpage.js | 2 + .../plugins/textplayer-plugin.js | 23 + .../plugins/textplayer.d.ts | 2 + .../phaser3-rex-plugins/plugins/textplayer.js | 2 + .../plugins/texttranslation-plugin.d.ts | 38 + .../plugins/texttranslation-plugin.js | 70 + .../plugins/texttranslation.d.ts | 2 + .../plugins/texttranslation.js | 2 + .../plugins/texttyping-plugin.d.ts | 9 + .../plugins/texttyping-plugin.js | 20 + .../plugins/texttyping.d.ts | 2 + .../phaser3-rex-plugins/plugins/texttyping.js | 2 + .../CanvasFrameManager.d.ts | 84 + .../canvasframemanager/CanvasFrameManager.js | 131 + .../methods/AddEmptyFrame.js | 16 + .../methods/AddToBitmapFont.js | 53 + .../canvasframemanager/methods/Draw.js | 37 + .../canvasframemanager/methods/Methods.js | 14 + .../canvasframemanager/methods/Paste.js | 30 + .../charactercache/CharacterCache.d.ts | 61 + .../texture/charactercache/CharacterCache.js | 65 + .../methods/BitmapTextMethods.js | 12 + .../methods/CharacterQueryMethods.js | 67 + .../texture/charactercache/methods/Clear.js | 11 + .../methods/CreateCharacterCollection.js | 17 + .../methods/CreateFrameManager.js | 20 + .../charactercache/methods/GetAllData.js | 7 + .../texture/charactercache/methods/Load.js | 92 + .../texture/charactercache/methods/Methods.js | 20 + .../texture/charactercache/methods/Unlock.js | 11 + .../plugins/time/awaytime/AwayTime.d.ts | 16 + .../plugins/time/awaytime/AwayTime.js | 71 + .../plugins/time/clock/ArcadeStepClock.d.ts | 12 + .../plugins/time/clock/ArcadeStepClock.js | 26 + .../plugins/time/clock/BaseClock.d.ts | 32 + .../plugins/time/clock/BaseClock.js | 64 + .../plugins/time/clock/Clock.d.ts | 12 + .../plugins/time/clock/Clock.js | 25 + .../plugins/time/clock/GameClock.js | 24 + .../plugins/time/lifetime/LifeTime.d.ts | 34 + .../plugins/time/lifetime/LifeTime.js | 72 + .../plugins/time/progresses/Timeline.js | 130 + .../plugins/time/progresses/Timer.js | 127 + .../plugins/time/progresses/TimerPool.js | 21 + .../time/realtimetimers/RealTimeTimers.d.ts | 81 + .../time/realtimetimers/RealTimeTimers.js | 192 + .../plugins/tintrgb-plugin.d.ts | 5 + .../plugins/tintrgb-plugin.js | 19 + .../phaser3-rex-plugins/plugins/tintrgb.d.ts | 2 + ui/src/phaser3-rex-plugins/plugins/tintrgb.js | 2 + .../plugins/toggleswitch-plugin.js | 28 + .../plugins/toggleswitch.d.ts | 2 + .../plugins/toggleswitch.js | 2 + .../plugins/toggleswitchshape.d.ts | 2 + .../plugins/toggleswitchshape.js | 2 + .../plugins/toonifypipeline-plugin.d.ts | 35 + .../plugins/toonifypipeline-plugin.js | 14 + .../plugins/toonifypipeline.d.ts | 2 + .../plugins/toonifypipeline.js | 2 + .../plugins/touchcursor-plugin.js | 20 + .../plugins/touchcursor.js | 2 + .../plugins/toucheventstop-plugin.d.ts | 9 + .../plugins/toucheventstop-plugin.js | 18 + .../plugins/toucheventstop.d.ts | 2 + .../plugins/toucheventstop.js | 2 + .../plugins/touchhelper-plugin.js | 40 + .../plugins/touchstate-plugin.js | 20 + .../plugins/touchstate.d.ts | 2 + .../phaser3-rex-plugins/plugins/touchstate.js | 2 + .../plugins/transitionimage-plugin.js | 23 + .../plugins/transitionimage.d.ts | 2 + .../plugins/transitionimage.js | 2 + .../plugins/triangle-plugin.js | 23 + .../phaser3-rex-plugins/plugins/triangle.d.ts | 2 + .../phaser3-rex-plugins/plugins/triangle.js | 2 + .../plugins/uniqueitemlist-plugin.d.ts | 11 + .../plugins/uniqueitemlist-plugin.js | 20 + .../plugins/uniqueitemlist.d.ts | 2 + .../plugins/uniqueitemlist.js | 2 + .../plugins/utils/actions/AlignConst.js | 18 + .../plugins/utils/actions/AlignIn.js | 9 + .../plugins/utils/actions/GlobZone.js | 12 + .../utils/actions/RotateObjectAround.js | 9 + .../utils/actions/SortByDisplayOrder.js | 8 + .../plugins/utils/align/align/const.js | 122 + .../utils/align/align/in/BottomCenter.js | 16 + .../utils/align/align/in/BottomLeft.js | 16 + .../utils/align/align/in/BottomRight.js | 16 + .../plugins/utils/align/align/in/Center.js | 14 + .../utils/align/align/in/LeftCenter.js | 16 + .../plugins/utils/align/align/in/QuickSet.js | 28 + .../utils/align/align/in/RightCenter.js | 16 + .../plugins/utils/align/align/in/TopCenter.js | 16 + .../plugins/utils/align/align/in/TopLeft.js | 16 + .../plugins/utils/align/align/in/TopRight.js | 16 + .../plugins/utils/align/align/in/index.js | 23 + .../plugins/utils/align/align/index.js | 17 + .../utils/align/align/to/BottomCenter.js | 16 + .../utils/align/align/to/BottomLeft.js | 16 + .../utils/align/align/to/BottomRight.js | 16 + .../utils/align/align/to/LeftBottom.js | 16 + .../utils/align/align/to/LeftCenter.js | 16 + .../plugins/utils/align/align/to/LeftTop.js | 16 + .../utils/align/align/to/RightBottom.js | 16 + .../utils/align/align/to/RightCenter.js | 16 + .../plugins/utils/align/align/to/RightTop.js | 16 + .../plugins/utils/align/align/to/TopCenter.js | 16 + .../plugins/utils/align/align/to/TopLeft.js | 16 + .../plugins/utils/align/align/to/TopRight.js | 16 + .../plugins/utils/align/align/to/index.js | 27 + .../plugins/utils/align/bounds/CenterOn.js | 9 + .../plugins/utils/align/bounds/GetBottom.js | 8 + .../plugins/utils/align/bounds/GetCenterX.js | 8 + .../plugins/utils/align/bounds/GetCenterY.js | 8 + .../plugins/utils/align/bounds/GetLeft.js | 8 + .../plugins/utils/align/bounds/GetOffsetX.js | 8 + .../plugins/utils/align/bounds/GetOffsetY.js | 8 + .../plugins/utils/align/bounds/GetRight.js | 8 + .../plugins/utils/align/bounds/GetTop.js | 8 + .../plugins/utils/align/bounds/SetBottom.js | 9 + .../plugins/utils/align/bounds/SetCenterX.js | 11 + .../plugins/utils/align/bounds/SetCenterY.js | 11 + .../plugins/utils/align/bounds/SetLeft.js | 9 + .../plugins/utils/align/bounds/SetRight.js | 10 + .../plugins/utils/align/bounds/SetTop.js | 9 + .../plugins/utils/align/bounds/index.js | 33 + .../plugins/utils/align/index.js | 7 + .../utils/arcade/BuildArcadeObject.d.ts | 9 + .../plugins/utils/arcade/BuildArcadeObject.js | 43 + .../plugins/utils/arcade/Helpers.js | 56 + .../plugins/utils/array/Add.js | 111 + .../plugins/utils/array/CSVToArray.js | 81 + .../plugins/utils/array/Copy.js | 14 + .../plugins/utils/array/Fill.js | 14 + .../plugins/utils/array/GetRandom.js | 29 + .../plugins/utils/array/Remove.js | 85 + .../plugins/utils/array/Shuffle.js | 30 + .../plugins/utils/array/SpliceOne.js | 40 + .../utils/arraybuffers/BooleanBuffer.js | 54 + .../plugins/utils/arraybuffers/ByteBuffer.js | 38 + .../utils/arraybuffers/FourBytesBuffer.js | 38 + .../utils/arraybuffers/Uint8ArrayReader.js | 66 + .../utils/arraybuffers/Uint8ArrayWriter.js | 43 + .../audio/howlersoundmanager/HowlerSound.js | 74 + .../howlersoundmanager/HowlerSoundManager.js | 108 + .../ogg-opus-decoder/ogg-opus-decoder.js | 4171 +++++++++++++++++ .../ogg-opus-decoder/ogg-opus-decoder.min.js | Bin 0 -> 107469 bytes .../audio/soundmanager/SoundManager.d.ts | 108 + .../utils/audio/soundmanager/SoundManager.js | 113 + .../methods/BackgroundMusic2Methods.js | 124 + .../methods/BackgroundMusicMethods.js | 124 + .../audio/soundmanager/methods/Methods.js | 14 + .../methods/SoundEffects2Methods.js | 90 + .../methods/SoundEffectsMethods.js | 90 + .../bitmaptext/IsBitmapTextGameObject.js | 7 + .../plugins/utils/bitmaptext/WrapText.js | 22 + .../plugins/utils/blob-util/blob-util.js | 460 ++ .../plugins/utils/blob-util/blob-util.min.js | 1 + .../plugins/utils/bounds/BoundsToPath.js | 50 + .../plugins/utils/bounds/BoundsToPolygon.js | 20 + .../plugins/utils/bounds/DrawBounds.d.ts | 26 + .../plugins/utils/bounds/DrawBounds.js | 75 + .../plugins/utils/bounds/GetBounds.js | 193 + .../plugins/utils/bounds/GetBoundsConfig.js | 21 + .../utils/bounds/GetBoundsOfGameObjects.js | 41 + .../plugins/utils/bounds/IsPointInBounds.js | 25 + .../plugins/utils/buff/Buff.js | 77 + .../plugins/utils/canvas/AddPolygonPath.js | 18 + .../utils/canvas/AddRoundRectanglePath.js | 139 + .../plugins/utils/canvas/DrawCircle.js | 35 + .../plugins/utils/canvas/DrawHSVPalette.js | 52 + .../plugins/utils/canvas/DrawPolygon.js | 31 + .../plugins/utils/canvas/DrawRectangle.js | 36 + .../utils/canvas/DrawRoundRectangle.js | 37 + .../plugins/utils/canvas/DrawText.js | 42 + .../plugins/utils/canvas/GetStyle.js | 31 + .../plugins/utils/canvas/GetTextSize.js | 27 + .../plugins/utils/color/ColorNameToInteger.js | 28 + .../utils/color/ColorStringToInteger.js | 18 + .../plugins/utils/color/GetHexColorString.js | 13 + .../plugins/utils/color/GetRGB.js | 15 + .../plugins/utils/color/GrayScale.js | 8 + .../plugins/utils/color/MixColor.js | 11 + .../plugins/utils/color/SetColor.js | 23 + .../plugins/utils/commandhub/CommandHub.js | 36 + .../utils/componentbase/ComponentBase.d.ts | 23 + .../utils/componentbase/ComponentBase.js | 92 + .../componentbase/SceneUpdateTickTask.d.ts | 17 + .../componentbase/SceneUpdateTickTask.js | 50 + .../plugins/utils/componentbase/TickTask.d.ts | 25 + .../plugins/utils/componentbase/TickTask.js | 115 + .../componentbase/timerticktask/Timer.js | 187 + .../timerticktask/TimerTask.d.ts | 16 + .../componentbase/timerticktask/TimerTask.js | 43 + .../tweentask/EaseValueTaskBase.d.ts | 23 + .../tweentask/EaseValueTaskBase.js | 135 + .../componentbase/tweentask/TweenTask.d.ts | 14 + .../componentbase/tweentask/TweenTask.js | 89 + .../plugins/utils/dat.gui/dat.gui.min.js | 13 + .../plugins/utils/data/DataManagerMethods.js | 57 + .../plugins/utils/data/DataMethods.d.ts | 31 + .../plugins/utils/data/DataMethods.js | 54 + .../plugins/utils/datetime/GetTime.js | 30 + .../plugins/utils/ease/EaseValueMethods.js | 80 + .../plugins/utils/ease/EaseValueTask.d.ts | 7 + .../plugins/utils/ease/EaseValueTask.js | 53 + .../utils/eventemitter/EventEmitter.d.ts | 15 + .../utils/eventemitter/EventEmitter.js | 11 + .../utils/eventemitter/EventEmitterMethods.js | 91 + .../plugins/utils/eventemitter/HasListener.js | 24 + .../plugins/utils/function/Override.js | 22 + .../utils/gameobject/addevent/AddEvent.d.ts | 7 + .../utils/gameobject/addevent/AddEvent.js | 19 + .../gameobject/addevent/AddSceneEvent.d.ts | 6 + .../gameobject/addevent/AddSceneEvent.js | 9 + .../gameobject/addevent/AddUpdateEvent.d.ts | 5 + .../gameobject/addevent/AddUpdateEvent.js | 7 + .../utils/gameobject/gomanager/GOManager.d.ts | 132 + .../utils/gameobject/gomanager/GOManager.js | 114 + .../gameobject/gomanager/bobbase/BobBase.d.ts | 40 + .../gameobject/gomanager/bobbase/BobBase.js | 73 + .../gomanager/bobbase/CallMethods.js | 16 + .../gomanager/bobbase/DataMethods.js | 15 + .../gomanager/bobbase/PropertyMethods.js | 52 + .../gomanager/methods/AddMethods.js | 76 + .../gomanager/methods/CallMethods.js | 17 + .../gomanager/methods/DataMethods.js | 23 + .../methods/DrawGameObjectsBounds.js | 14 + .../gomanager/methods/FadeMethods.js | 69 + .../gameobject/gomanager/methods/Methods.js | 23 + .../gomanager/methods/PropertyMethods.js | 92 + .../gomanager/methods/RemoveMethods.js | 50 + .../plugins/utils/geom/circle/Area.js | 22 + .../plugins/utils/geom/circle/Circle.d.ts | 238 + .../plugins/utils/geom/circle/Circle.js | 336 ++ .../utils/geom/circle/Circumference.js | 22 + .../utils/geom/circle/CircumferencePoint.js | 32 + .../plugins/utils/geom/circle/Clone.js | 23 + .../plugins/utils/geom/circle/Contains.js | 35 + .../utils/geom/circle/ContainsPoint.js | 24 + .../plugins/utils/geom/circle/ContainsRect.js | 29 + .../plugins/utils/geom/circle/CopyFrom.js | 26 + .../plugins/utils/geom/circle/Equals.js | 28 + .../plugins/utils/geom/circle/GetBounds.js | 33 + .../plugins/utils/geom/circle/GetPoint.js | 36 + .../plugins/utils/geom/circle/GetPoints.js | 43 + .../plugins/utils/geom/circle/Offset.js | 29 + .../plugins/utils/geom/circle/OffsetPoint.js | 28 + .../plugins/utils/geom/circle/Random.js | 37 + .../plugins/utils/geom/circle/index.js | 40 + .../plugins/utils/geom/const.js | 74 + .../plugins/utils/geom/ellipse/Area.js | 28 + .../utils/geom/ellipse/Circumference.js | 26 + .../utils/geom/ellipse/CircumferencePoint.js | 35 + .../plugins/utils/geom/ellipse/Clone.js | 23 + .../plugins/utils/geom/ellipse/Contains.js | 36 + .../utils/geom/ellipse/ContainsPoint.js | 24 + .../utils/geom/ellipse/ContainsRect.js | 29 + .../plugins/utils/geom/ellipse/CopyFrom.js | 26 + .../plugins/utils/geom/ellipse/Ellipse.d.ts | 258 + .../plugins/utils/geom/ellipse/Ellipse.js | 342 ++ .../plugins/utils/geom/ellipse/Equals.js | 29 + .../plugins/utils/geom/ellipse/GetBounds.js | 33 + .../plugins/utils/geom/ellipse/GetPoint.js | 36 + .../plugins/utils/geom/ellipse/GetPoints.js | 45 + .../plugins/utils/geom/ellipse/Offset.js | 29 + .../plugins/utils/geom/ellipse/OffsetPoint.js | 28 + .../plugins/utils/geom/ellipse/Random.js | 34 + .../plugins/utils/geom/ellipse/index.js | 40 + .../plugins/utils/geom/index.js | 31 + .../utils/geom/intersects/CircleToCircle.js | 24 + .../geom/intersects/CircleToRectangle.js | 48 + .../geom/intersects/GetCircleToCircle.js | 80 + .../geom/intersects/GetCircleToRectangle.js | 44 + .../utils/geom/intersects/GetLineToCircle.js | 79 + .../geom/intersects/GetLineToRectangle.js | 51 + .../intersects/GetRectangleIntersection.js | 41 + .../intersects/GetRectangleToRectangle.js | 43 + .../geom/intersects/GetRectangleToTriangle.js | 40 + .../geom/intersects/GetTriangleToCircle.js | 41 + .../geom/intersects/GetTriangleToLine.js | 50 + .../geom/intersects/GetTriangleToTriangle.js | 41 + .../utils/geom/intersects/LineToCircle.js | 74 + .../utils/geom/intersects/LineToLine.js | 67 + .../utils/geom/intersects/LineToRectangle.js | 95 + .../utils/geom/intersects/PointToLine.js | 64 + .../geom/intersects/PointToLineSegment.js | 33 + .../geom/intersects/RectangleToRectangle.js | 30 + .../geom/intersects/RectangleToTriangle.js | 79 + .../geom/intersects/RectangleToValues.js | 34 + .../utils/geom/intersects/TriangleToCircle.js | 53 + .../utils/geom/intersects/TriangleToLine.js | 45 + .../geom/intersects/TriangleToTriangle.js | 77 + .../plugins/utils/geom/intersects/index.js | 61 + .../plugins/utils/geom/line/Angle.js | 22 + .../utils/geom/line/BresenhamPoints.js | 68 + .../plugins/utils/geom/line/CenterOn.js | 34 + .../plugins/utils/geom/line/Clone.js | 23 + .../plugins/utils/geom/line/CopyFrom.js | 25 + .../plugins/utils/geom/line/Equals.js | 28 + .../plugins/utils/geom/line/Extend.js | 49 + .../plugins/utils/geom/line/GetMidPoint.js | 31 + .../utils/geom/line/GetNearestPoint.js | 47 + .../plugins/utils/geom/line/GetNormal.js | 37 + .../plugins/utils/geom/line/GetPoint.js | 32 + .../plugins/utils/geom/line/GetPoints.js | 56 + .../utils/geom/line/GetShortestDistance.js | 41 + .../plugins/utils/geom/line/Height.js | 22 + .../plugins/utils/geom/line/Length.js | 22 + .../plugins/utils/geom/line/Line.d.ts | 360 ++ .../plugins/utils/geom/line/Line.js | 296 ++ .../plugins/utils/geom/line/NormalAngle.js | 27 + .../plugins/utils/geom/line/NormalX.js | 24 + .../plugins/utils/geom/line/NormalY.js | 25 + .../plugins/utils/geom/line/Offset.js | 32 + .../plugins/utils/geom/line/PerpSlope.js | 22 + .../plugins/utils/geom/line/Random.js | 33 + .../plugins/utils/geom/line/ReflectAngle.js | 27 + .../plugins/utils/geom/line/Rotate.js | 29 + .../utils/geom/line/RotateAroundPoint.js | 27 + .../plugins/utils/geom/line/RotateAroundXY.js | 42 + .../plugins/utils/geom/line/SetToAngle.js | 34 + .../plugins/utils/geom/line/Slope.js | 22 + .../plugins/utils/geom/line/Width.js | 22 + .../plugins/utils/geom/line/index.js | 66 + .../plugins/utils/geom/point/Ceil.js | 24 + .../plugins/utils/geom/point/Clone.js | 23 + .../plugins/utils/geom/point/CopyFrom.js | 25 + .../plugins/utils/geom/point/Equals.js | 23 + .../plugins/utils/geom/point/Floor.js | 24 + .../plugins/utils/geom/point/GetCentroid.js | 52 + .../plugins/utils/geom/point/GetMagnitude.js | 22 + .../utils/geom/point/GetMagnitudeSq.js | 22 + .../geom/point/GetRectangleFromPoints.js | 58 + .../plugins/utils/geom/point/Interpolate.js | 34 + .../plugins/utils/geom/point/Invert.js | 24 + .../plugins/utils/geom/point/Negative.js | 28 + .../plugins/utils/geom/point/Point.d.ts | 146 + .../plugins/utils/geom/point/Point.js | 73 + .../plugins/utils/geom/point/Project.js | 38 + .../plugins/utils/geom/point/ProjectUnit.js | 36 + .../plugins/utils/geom/point/SetMagnitude.js | 36 + .../plugins/utils/geom/point/index.js | 40 + .../plugins/utils/geom/polygon/Clone.js | 23 + .../plugins/utils/geom/polygon/Contains.js | 43 + .../utils/geom/polygon/ContainsPoint.js | 24 + .../plugins/utils/geom/polygon/Earcut.js | 759 +++ .../plugins/utils/geom/polygon/GetAABB.js | 48 + .../utils/geom/polygon/GetNumberArray.js | 37 + .../plugins/utils/geom/polygon/GetPoints.js | 66 + .../plugins/utils/geom/polygon/Perimeter.js | 40 + .../plugins/utils/geom/polygon/Polygon.d.ts | 213 + .../plugins/utils/geom/polygon/Polygon.js | 215 + .../plugins/utils/geom/polygon/Reverse.js | 26 + .../plugins/utils/geom/polygon/Smooth.js | 70 + .../plugins/utils/geom/polygon/index.js | 28 + .../plugins/utils/geom/rectangle/Area.js | 22 + .../plugins/utils/geom/rectangle/Ceil.js | 27 + .../plugins/utils/geom/rectangle/CeilAll.js | 29 + .../plugins/utils/geom/rectangle/CenterOn.js | 31 + .../plugins/utils/geom/rectangle/Clone.js | 23 + .../plugins/utils/geom/rectangle/Contains.js | 29 + .../utils/geom/rectangle/ContainsPoint.js | 24 + .../utils/geom/rectangle/ContainsRect.js | 34 + .../plugins/utils/geom/rectangle/CopyFrom.js | 25 + .../plugins/utils/geom/rectangle/Decompose.js | 31 + .../plugins/utils/geom/rectangle/Equals.js | 28 + .../plugins/utils/geom/rectangle/FitInside.js | 44 + .../utils/geom/rectangle/FitOutside.js | 44 + .../plugins/utils/geom/rectangle/Floor.js | 27 + .../plugins/utils/geom/rectangle/FloorAll.js | 29 + .../utils/geom/rectangle/FromPoints.js | 74 + .../utils/geom/rectangle/GetAspectRatio.js | 22 + .../plugins/utils/geom/rectangle/GetCenter.js | 31 + .../plugins/utils/geom/rectangle/GetPoint.js | 64 + .../plugins/utils/geom/rectangle/GetPoints.js | 45 + .../plugins/utils/geom/rectangle/GetSize.js | 33 + .../plugins/utils/geom/rectangle/Inflate.js | 35 + .../utils/geom/rectangle/Intersection.js | 42 + .../utils/geom/rectangle/MarchingAnts.js | 103 + .../utils/geom/rectangle/MergePoints.js | 43 + .../plugins/utils/geom/rectangle/MergeRect.js | 41 + .../plugins/utils/geom/rectangle/MergeXY.js | 38 + .../plugins/utils/geom/rectangle/Offset.js | 29 + .../utils/geom/rectangle/OffsetPoint.js | 28 + .../plugins/utils/geom/rectangle/Overlaps.js | 28 + .../plugins/utils/geom/rectangle/Perimeter.js | 22 + .../utils/geom/rectangle/PerimeterPoint.js | 48 + .../plugins/utils/geom/rectangle/Random.js | 31 + .../utils/geom/rectangle/RandomOutside.js | 62 + .../utils/geom/rectangle/Rectangle.d.ts | 465 ++ .../plugins/utils/geom/rectangle/Rectangle.js | 459 ++ .../utils/geom/rectangle/SameDimensions.js | 23 + .../plugins/utils/geom/rectangle/Scale.js | 33 + .../plugins/utils/geom/rectangle/Union.js | 35 + .../plugins/utils/geom/rectangle/index.js | 84 + .../plugins/utils/geom/triangle/Area.js | 33 + .../utils/geom/triangle/BuildEquilateral.js | 37 + .../utils/geom/triangle/BuildFromPolygon.js | 67 + .../plugins/utils/geom/triangle/BuildRight.js | 42 + .../plugins/utils/geom/triangle/CenterOn.js | 46 + .../plugins/utils/geom/triangle/Centroid.js | 37 + .../utils/geom/triangle/CircumCenter.js | 68 + .../utils/geom/triangle/CircumCircle.js | 74 + .../plugins/utils/geom/triangle/Clone.js | 23 + .../plugins/utils/geom/triangle/Contains.js | 47 + .../utils/geom/triangle/ContainsArray.js | 81 + .../utils/geom/triangle/ContainsPoint.js | 24 + .../plugins/utils/geom/triangle/CopyFrom.js | 25 + .../plugins/utils/geom/triangle/Decompose.js | 29 + .../plugins/utils/geom/triangle/Equals.js | 30 + .../plugins/utils/geom/triangle/GetPoint.js | 76 + .../plugins/utils/geom/triangle/GetPoints.js | 81 + .../plugins/utils/geom/triangle/InCenter.js | 57 + .../plugins/utils/geom/triangle/Offset.js | 35 + .../plugins/utils/geom/triangle/Perimeter.js | 29 + .../plugins/utils/geom/triangle/Random.js | 48 + .../plugins/utils/geom/triangle/Rotate.js | 29 + .../utils/geom/triangle/RotateAroundPoint.js | 27 + .../utils/geom/triangle/RotateAroundXY.js | 48 + .../plugins/utils/geom/triangle/Triangle.d.ts | 331 ++ .../plugins/utils/geom/triangle/Triangle.js | 400 ++ .../plugins/utils/geom/triangle/index.js | 58 + .../plugins/utils/geom/types.d.ts | 6 + .../utils/grid/hexagon/CubeTransfer.js | 134 + .../grid/hexagon/DeltaTileXYToDirection.js | 42 + .../utils/grid/hexagon/DirectionBetween.js | 44 + .../grid/hexagon/DirectionToDeltaTileXY.js | 85 + .../plugins/utils/grid/hexagon/GetDistance.js | 17 + .../grid/hexagon/GetNeighborTileDirection.js | 18 + .../utils/grid/hexagon/GetNeighborTileXY.js | 7 + .../grid/hexagon/GetOppositeDirection.js | 4 + .../plugins/utils/grid/hexagon/GetParity.js | 23 + .../plugins/utils/grid/hexagon/GetTileX.js | 5 + .../plugins/utils/grid/hexagon/GetTileXY.js | 49 + .../grid/hexagon/GetTileXYAtDirection.js | 65 + .../plugins/utils/grid/hexagon/GetTileY.js | 5 + .../plugins/utils/grid/hexagon/GetWorldX.js | 5 + .../plugins/utils/grid/hexagon/GetWorldXY.js | 54 + .../plugins/utils/grid/hexagon/GetWorldY.js | 5 + .../plugins/utils/grid/hexagon/Hexagon.js | 141 + .../plugins/utils/grid/hexagon/Mirror.js | 50 + .../plugins/utils/grid/hexagon/Offset.js | 55 + .../utils/grid/hexagon/RingToTileXYArray.js | 55 + .../plugins/utils/grid/hexagon/Rotate.js | 56 + .../plugins/utils/grid/hexagon/const.js | 6 + .../utils/grid/quad/DeltaTileXYToDirection.js | 29 + .../utils/grid/quad/DirectionBetween.js | 74 + .../utils/grid/quad/DistanceToDeltaTileXY.js | 17 + .../plugins/utils/grid/quad/GetDistance.js | 12 + .../grid/quad/GetNeighborTileDirection.js | 29 + .../utils/grid/quad/GetNeighborTileXY.js | 7 + .../utils/grid/quad/GetOppositeDirection.js | 14 + .../plugins/utils/grid/quad/GetTileX.js | 5 + .../plugins/utils/grid/quad/GetTileXY.js | 27 + .../utils/grid/quad/GetTileXYAtDirection.js | 33 + .../plugins/utils/grid/quad/GetTileY.js | 5 + .../plugins/utils/grid/quad/GetWorldX.js | 5 + .../plugins/utils/grid/quad/GetWorldXY.js | 28 + .../plugins/utils/grid/quad/GetWorldY.js | 5 + .../plugins/utils/grid/quad/Mirror.js | 14 + .../plugins/utils/grid/quad/Offset.js | 17 + .../plugins/utils/grid/quad/Quad.js | 109 + .../utils/grid/quad/RingToTileXYArray.js | 32 + .../plugins/utils/grid/quad/Rotate.js | 38 + .../plugins/utils/input/CursorKeys.js | 97 + .../plugins/utils/input/HitTest.js | 43 + .../plugins/utils/input/InputCandidate.js | 26 + .../utils/input/IsPointerInBounds.d.ts | 8 + .../plugins/utils/input/IsPointerInBounds.js | 23 + .../plugins/utils/input/IsPointerInHitArea.js | 54 + .../plugins/utils/input/KeyMap.js | 8 + .../plugins/utils/input/RequestDrag.d.ts | 3 + .../plugins/utils/input/RequestDrag.js | 38 + .../plugins/utils/input/SetCursorStyle.js | 8 + .../plugins/utils/input/VectorToCursorKeys.js | 183 + .../utils/js-interpreter/js-interpreter.js | 8 + .../plugins/utils/jsdiff/LICENSE | 31 + .../plugins/utils/jsdiff/README.md | 211 + .../plugins/utils/jsdiff/convert/dmp.js | 19 + .../plugins/utils/jsdiff/convert/xml.js | 30 + .../plugins/utils/jsdiff/diff/array.js | 11 + .../plugins/utils/jsdiff/diff/base.js | 235 + .../plugins/utils/jsdiff/diff/character.js | 4 + .../plugins/utils/jsdiff/diff/css.js | 8 + .../plugins/utils/jsdiff/diff/json.js | 83 + .../plugins/utils/jsdiff/diff/line.js | 35 + .../plugins/utils/jsdiff/diff/sentence.js | 9 + .../plugins/utils/jsdiff/diff/word.js | 60 + .../plugins/utils/jsdiff/index.js | 61 + .../plugins/utils/jsdiff/patch/apply.js | 160 + .../plugins/utils/jsdiff/patch/create.js | 144 + .../plugins/utils/jsdiff/patch/merge.js | 376 ++ .../plugins/utils/jsdiff/patch/parse.js | 153 + .../plugins/utils/jsdiff/release-notes.md | 303 ++ .../plugins/utils/jsdiff/util/array.js | 21 + .../utils/jsdiff/util/distance-iterator.js | 45 + .../plugins/utils/jsdiff/util/params.js | 13 + .../utils/loader/BinaryToTextureCache.d.ts | 8 + .../utils/loader/BinaryToTextureCache.js | 22 + .../plugins/utils/loader/FileObjectToCache.js | 36 + .../utils/loader/LoadBinaryAndImage.d.ts | 20 + .../utils/loader/LoadBinaryAndImage.js | 26 + .../plugins/utils/loader/LoadScript.js | 21 + .../plugins/utils/loader/LoadScriptPromise.js | 9 + .../plugins/utils/lokijs/SerializeMethods.js | 27 + .../utils/lokijs/loki-indexed-adapter.js | 617 +++ .../plugins/utils/lokijs/lokijs.min.js | 3 + .../plugins/utils/lzstring/lz-string.min.js | 1 + .../plugins/utils/managers/DestroyManagers.js | 24 + .../plugins/utils/managers/Extend.js | 28 + .../managers/GameObjectManagerMethods.js | 50 + .../utils/managers/GameObjectMethods.js | 92 + .../plugins/utils/managers/GetTimeScale.js | 5 + .../plugins/utils/managers/InitManagers.js | 24 + .../plugins/utils/managers/Managers.d.ts | 131 + .../plugins/utils/managers/Managers.js | 33 + .../plugins/utils/managers/SetTimeScale.js | 9 + .../managers/waiteventmanager/WaitAny.js | 90 + .../waiteventmanager/WaitCameraMethods.js | 52 + .../waiteventmanager/WaitEventManager.d.ts | 52 + .../waiteventmanager/WaitEventManager.js | 86 + .../waiteventmanager/WaitGameObjectMethods.js | 67 + .../waiteventmanager/WaitInputMethods.js | 18 + .../waiteventmanager/WaitMusicMethods.js | 45 + .../utils/managers/waiteventmanager/const.js | 2 + .../plugins/utils/marked/marked.min.js | 6 + .../plugins/utils/mask/MaskToGameObject.js | 4 + .../DefaultMaskGraphics.js | 103 + .../mask/defaultmaskgraphics/DrawShape.js | 23 + .../plugins/utils/math/Bernstein.js | 24 + .../plugins/utils/math/Between.js | 23 + .../plugins/utils/math/ByteArrayToUint32.js | 14 + .../plugins/utils/math/CatmullRom.js | 30 + .../plugins/utils/math/Clamp.js | 24 + .../plugins/utils/math/DegToRad.js | 24 + .../plugins/utils/math/Factorial.js | 31 + .../plugins/utils/math/FromPercent.js | 27 + .../plugins/utils/math/Linear.js | 24 + .../plugins/utils/math/MapRange.js | 6 + .../plugins/utils/math/RadToDeg.js | 24 + .../plugins/utils/math/RotateAround.js | 37 + .../plugins/utils/math/RoundUpPowerOf2.js | 12 + .../plugins/utils/math/SmoothStep.js | 38 + .../plugins/utils/math/SmootherStep.js | 32 + .../plugins/utils/math/Sum.js | 9 + .../plugins/utils/math/Uint32ToByteArray.js | 26 + .../plugins/utils/math/Vector2.js | 600 +++ .../plugins/utils/math/Wrap.js | 26 + .../plugins/utils/math/Yoyo.js | 14 + .../plugins/utils/math/angle/Between.js | 25 + .../plugins/utils/math/angle/Normalize.js | 31 + .../utils/math/angle/ShortestBetween.js | 43 + .../plugins/utils/math/angle/Wrap.js | 25 + .../angletodirections/AngleToDirections.js | 73 + .../math/angle/angletodirections/Const.js | 6 + .../angletodirections/RotationToDirection.js | 7 + .../utils/math/color/Color32Methods.js | 32 + .../utils/math/color/InterpolateColor32.js | 22 + .../plugins/utils/math/const.js | 66 + .../utils/math/coordinate/PolarToCartesian.js | 16 + .../utils/math/distance/DistanceBetween.js | 28 + .../plugins/utils/math/fuzzy/Ceil.js | 25 + .../plugins/utils/math/fuzzy/Equal.js | 28 + .../plugins/utils/math/fuzzy/Floor.js | 25 + .../plugins/utils/math/fuzzy/GreaterThan.js | 28 + .../plugins/utils/math/fuzzy/LessThan.js | 28 + .../math/interpolation/BezierInterpolation.js | 30 + .../interpolation/CatmullRomInterpolation.js | 45 + .../interpolation/CubicBezierInterpolation.js | 59 + .../math/interpolation/LinearInterpolation.js | 37 + .../QuadraticBezierInterpolation.js | 49 + .../interpolation/SmoothStepInterpolation.js | 26 + .../SmootherStepInterpolation.js | 26 + .../plugins/utils/math/noise/Perlin.d.ts | 11 + .../plugins/utils/math/noise/Perlin.js | 312 ++ .../plugins/utils/math/noise/Simplex1.js | 89 + .../ShatterRectangleToTriangles.js | 122 + .../utils/math/triangulate/Triangulate.js | 29 + .../utils/math/triangulate/delaunay.js | 236 + .../plugins/utils/midi-parser/midi-parser.js | 304 ++ .../utils/minmaxbounds/MinMaxBounds.js | 35 + .../plugins/utils/movement/MoveTo.js | 46 + .../plugins/utils/movement/Movement.js | 74 + .../plugins/utils/movement/SlowDown.js | 61 + .../plugins/utils/mustache/mustache.min.js | 1 + .../plugins/utils/ninepatch/Methods.js | 23 + .../plugins/utils/ninepatch/NinePatch.d.ts | 128 + .../plugins/utils/ninepatch/NinePatch.js | 131 + .../utils/ninepatch/texture/GetStretchMode.js | 7 + .../utils/ninepatch/texture/SetBaseTexture.js | 139 + .../texture/SetGetFrameNameCallback.js | 17 + .../ninepatch/texture/SetMaxFixedPartScale.js | 11 + .../ninepatch/texture/SetPreserveRatio.js | 10 + .../utils/ninepatch/texture/SetStretchMode.js | 28 + .../utils/ninepatch/texture/UpdateTexture.js | 106 + .../plugins/utils/ninepatch/utils/IsEdge.js | 6 + .../plugins/utils/object/AreValuesEqual.js | 32 + .../plugins/utils/object/Class.js | 248 + .../plugins/utils/object/Clear.js | 17 + .../plugins/utils/object/Clone.js | 32 + .../plugins/utils/object/CopyProperty.js | 21 + .../plugins/utils/object/DeepClone.js | 31 + .../plugins/utils/object/DeepMerge.js | 22 + .../plugins/utils/object/ExtractByPrefix.js | 31 + .../plugins/utils/object/ForEachLeafNode.js | 55 + .../plugins/utils/object/GetAdvancedValue.js | 74 + .../plugins/utils/object/GetFastValue.js | 37 + .../plugins/utils/object/GetPartialData.js | 21 + .../plugins/utils/object/GetValue.js | 57 + .../utils/object/GetValueFromAliasKeys.js | 17 + .../plugins/utils/object/HasAll.js | 31 + .../plugins/utils/object/HasAny.js | 31 + .../plugins/utils/object/HasValue.js | 30 + .../plugins/utils/object/IndexOf.js | 14 + .../plugins/utils/object/IsArray.js | 4 + .../plugins/utils/object/IsEmpty.js | 8 + .../plugins/utils/object/IsFunction.js | 5 + .../plugins/utils/object/IsKeyValueEqual.js | 21 + .../plugins/utils/object/IsPlainObject.js | 50 + .../plugins/utils/object/Merge.js | 39 + .../plugins/utils/object/MergeRight.js | 37 + .../plugins/utils/object/NOOP.js | 5 + .../plugins/utils/object/RemoveItem.js | 19 + .../plugins/utils/object/SetValue.js | 72 + .../plugins/utils/origin/ChangeOrigin.js | 22 + .../plugins/utils/padding/PaddingMethods.js | 38 + .../position/GameObjectLocalXYToWorldXY.d.ts | 8 + .../position/GameObjectLocalXYToWorldXY.js | 33 + .../utils/position/ScreenXYToWorldXY.js | 14 + .../position/WorldXYToGameObjectLocalXY.d.ts | 9 + .../position/WorldXYToGameObjectLocalXY.js | 41 + .../utils/progressbase/ProgressBase.js | 52 + .../progressvalue/ProgressValueMethods.js | 32 + .../plugins/utils/promise/Delay.d.ts | 4 + .../plugins/utils/promise/Delay.js | 12 + .../plugins/utils/promise/DelaySceneTick.js | 10 + .../plugins/utils/promise/WaitEvent.d.ts | 10 + .../plugins/utils/promise/WaitEvent.js | 13 + .../CreateProxyContext.d.ts | 13 + .../createproxycontext/CreateProxyContext.js | 18 + .../proxy/datamonitor/AddDataMonitor.d.ts | 5 + .../utils/proxy/datamonitor/AddDataMonitor.js | 10 + .../utils/proxy/datamonitor/AddMonitor.js | 71 + .../utils/proxy/datamonitor/EmitEvents.js | 29 + .../proxy/datamonitor/GetPropertyPath.js | 4 + .../AddPostFxPipelineInstance.js | 17 + .../BasePostFxPipelineBehavior.d.ts | 22 + .../BasePostFxPipelineBehavior.js | 76 + .../BasePostFxPipelinePlugin.js | 34 + .../GetPostFxPipelineInstance.js | 23 + .../postfxpipeline/RegisterPostPipeline.js | 7 + .../RemovePostFxPipelineInstance.js | 25 + .../rendertexture/CreateDynamicTexture.js | 14 + .../utils/rendertexture/FitToViewport.js | 28 + .../plugins/utils/rendertexture/Snapshot.d.ts | 19 + .../plugins/utils/rendertexture/Snapshot.js | 108 + .../plugins/utils/size/FitTo.d.ts | 11 + .../plugins/utils/size/FitTo.js | 30 + .../plugins/utils/size/GetDisplaySize.js | 20 + .../plugins/utils/size/ResizeGameObject.js | 28 + .../plugins/utils/size/SetDisplaySize.js | 32 + .../utils/speedmonitor/SpeedMonitor.js | 31 + .../utils/sprite/spritemanager/SpriteBob.js | 36 + .../sprite/spritemanager/SpriteManager.d.ts | 53 + .../sprite/spritemanager/SpriteManager.js | 47 + .../spritemanager/methods/AnimationMethods.js | 37 + .../sprite/spritemanager/methods/Methods.js | 9 + .../plugins/utils/string/EscapeRegex.js | 12 + .../plugins/utils/string/GetRandomWord.js | 17 + .../plugins/utils/string/GetTimeStamp.js | 5 + .../utils/string/NumberToColorString.js | 8 + .../plugins/utils/string/TypeConvert.js | 28 + .../plugins/utils/string/UUID.js | 33 + .../plugins/utils/struct/Stack.js | 32 + .../plugins/utils/struct/Tree.d.ts | 30 + .../plugins/utils/struct/Tree.js | 140 + .../plugins/utils/system/GetCache.js | 40 + .../plugins/utils/system/GetEventEmitter.js | 11 + .../plugins/utils/system/GetGLTexture.js | 9 + .../plugins/utils/system/GetGame.js | 18 + .../utils/system/GetGameObjectByName.js | 38 + .../plugins/utils/system/GetSceneObject.js | 17 + .../plugins/utils/system/GetSoundManager.js | 10 + .../plugins/utils/system/GetTickDelta.js | 7 + .../plugins/utils/system/GetViewport.d.ts | 12 + .../plugins/utils/system/GetViewport.js | 25 + .../plugins/utils/system/IsCameraObject.js | 7 + .../plugins/utils/system/IsEventEmitter.js | 8 + .../plugins/utils/system/IsGame.js | 5 + .../plugins/utils/system/IsGameObject.js | 5 + .../plugins/utils/system/IsSceneObject.js | 5 + .../plugins/utils/system/IsSoundObject.js | 6 + .../plugins/utils/system/LogMaxDelta.js | 16 + .../plugins/utils/system/MaxDelta.js | 57 + .../plugins/utils/system/OS.js | 165 + .../utils/system/SortGameObjectsByDepth.js | 27 + .../plugins/utils/text/AppendText.js | 28 + .../plugins/utils/text/FullFill.js | 22 + .../plugins/utils/text/GetTextObjectType.js | 25 + .../plugins/utils/text/GetWrapText.js | 23 + .../plugins/utils/text/IsTextGameObject.js | 7 + .../plugins/utils/text/SetNoWrapText.js | 49 + .../plugins/utils/text/TextToLines.js | 27 + .../SetFontSizeToFitWidth.d.ts | 5 + .../SetFontSizeToFitWidth.js | 99 + .../plugins/utils/text/textmanager/TextBob.js | 63 + .../utils/text/textmanager/TextManager.js | 35 + .../utils/text/textmanager/methods/Methods.js | 9 + .../textmanager/methods/SetTextMethods.js | 55 + .../utils/texture/CopyCanvasToTexture.js | 41 + .../utils/texture/CreateCircleTexture.js | 48 + .../utils/texture/CreateDashedTexture.js | 21 + .../utils/texture/CreatePolygonTexture.js | 135 + .../utils/texture/CreateRectangleTexture.js | 56 + .../texture/CreateRoundRectangleTexture.js | 60 + .../utils/texture/CreateTriangleTexture.js | 71 + .../utils/texture/DrawFrameToCanvas.js | 9 + .../plugins/utils/texture/HasTexture.js | 15 + .../plugins/utils/texture/LocalXYToColor.js | 9 + .../plugins/utils/texture/ToBase64.js | 28 + .../texture/gridcut/GetFrameNameCallback.js | 19 + .../plugins/utils/texture/gridcut/GridCut.js | 52 + .../utils/texture/imagemanager/AddImage.js | 48 + .../utils/texture/imagemanager/DrawImage.js | 21 + .../texture/imagemanager/ImageManager.js | 77 + .../plugins/utils/time/GetPeriodMS.js | 20 + .../plugins/utils/time/NextTick.js | 5 + .../plugins/utils/time/PostStepDelayCall.js | 14 + .../plugins/utils/time/PostUpdateDelayCall.js | 14 + .../plugins/utils/time/PreUpdateDelayCall.js | 14 + .../plugins/utils/time/UpdateDelayCall.js | 14 + .../plugins/utils/time/cooldown/Cooldown.js | 52 + .../plugins/utils/tween/AutoRemoveTween.js | 14 + .../utils/types/CanvasGameObjectBase.d.ts | 128 + .../utils/webfontloader/webfontloader.js | 17 + .../plugins/utils/yaml/ParseYaml.js | 13 + .../plugins/video-plugin.js | 28 + ui/src/phaser3-rex-plugins/plugins/video.js | 4 + .../plugins/viewportcoordinate-plugin.d.ts | 7 + .../plugins/viewportcoordinate-plugin.js | 24 + .../plugins/viewportcoordinate.d.ts | 5 + .../plugins/viewportcoordinate.js | 5 + .../plugins/virtualjoystick-plugin.d.ts | 9 + .../plugins/virtualjoystick-plugin.js | 20 + .../plugins/virtualjoystick.d.ts | 2 + .../plugins/virtualjoystick.js | 2 + .../plugins/waitevents-plugin.d.ts | 9 + .../plugins/waitevents-plugin.js | 20 + .../plugins/waitevents.d.ts | 2 + .../phaser3-rex-plugins/plugins/waitevents.js | 2 + .../plugins/warppipeline-plugin.d.ts | 29 + .../plugins/warppipeline-plugin.js | 19 + .../plugins/warppipeline.d.ts | 2 + .../plugins/warppipeline.js | 2 + .../plugins/warppipelinebehavior.d.ts | 2 + .../plugins/warppipelinebehavior.js | 2 + .../plugins/webfontloader-plugin.js | 15 + .../plugins/webfontloader.d.ts | 2 + .../plugins/webfontloader.js | 5 + .../plugins/xor-plugin.d.ts | 7 + .../phaser3-rex-plugins/plugins/xor-plugin.js | 20 + ui/src/phaser3-rex-plugins/plugins/xor.d.ts | 9 + ui/src/phaser3-rex-plugins/plugins/xor.js | 7 + .../plugins/ymlachievements-plugin.d.ts | 6 + .../plugins/ymlachievements-plugin.js | 18 + .../plugins/ymlachievements.d.ts | 2 + .../plugins/ymlachievements.js | 2 + .../plugins/ymlconditionstable-plugin.d.ts | 6 + .../plugins/ymlconditionstable-plugin.js | 18 + .../plugins/ymlconditionstable.d.ts | 2 + .../plugins/ymlconditionstable.js | 2 + .../plugins/youtubeplayer-plugin.js | 23 + .../plugins/youtubeplayer.d.ts | 2 + .../plugins/youtubeplayer.js | 2 + .../templates/bejeweled/Bejeweled.d.ts | 192 + .../templates/bejeweled/Bejeweled.js | 82 + .../bejeweled/actions/EliminateChess.js | 15 + .../bejeweled/actions/FallingAllChess.js | 26 + .../bejeweled/actions/SelectChess.js | 9 + .../templates/bejeweled/actions/SwapChess.js | 30 + .../templates/bejeweled/board/Board.js | 152 + .../templates/bejeweled/board/BreakMatch3.js | 38 + .../templates/bejeweled/board/Fill.js | 36 + .../templates/bejeweled/board/Init.js | 9 + .../templates/bejeweled/board/PreTest.js | 47 + .../templates/bejeweled/board/Reset.js | 16 + .../bejeweled/board/chess/CreateChess.js | 30 + .../bejeweled/board/chess/RandomSymobl.js | 38 + .../bejeweled/board/match/AnyMatch.js | 5 + .../bejeweled/board/match/GetAllMatch.js | 35 + .../bejeweled/board/match/GetMatchN.js | 6 + .../board/match/RefreshSymbolCache.js | 15 + .../templates/bejeweled/input/Input.js | 64 + .../bejeweled/methods/BoardMethods.js | 41 + .../bejeweled/methods/InputMethods.js | 26 + .../bejeweled/methods/WaitEventMethods.js | 13 + .../templates/bejeweled/states/BaseState.js | 36 + .../templates/bejeweled/states/MainState.js | 222 + .../templates/bejeweled/states/MatchState.js | 160 + .../templates/dialog-quest/DataMethods.js | 25 + .../templates/dialog-quest/DialogQuest.d.ts | 74 + .../templates/dialog-quest/DialogQuest.js | 54 + .../templates/dialog-quest/QuestMethods.js | 18 + .../templates/spinner/ObjectFactory.js | 20 + .../templates/spinner/audio/Audio.d.ts | 2 + .../templates/spinner/audio/Audio.js | 61 + .../templates/spinner/audio/Factory.d.ts | 6 + .../templates/spinner/audio/Factory.js | 13 + .../templates/spinner/ball/Ball.d.ts | 2 + .../templates/spinner/ball/Ball.js | 45 + .../templates/spinner/ball/Factory.d.ts | 6 + .../templates/spinner/ball/Factory.js | 13 + .../templates/spinner/bars/Bars.d.ts | 2 + .../templates/spinner/bars/Bars.js | 54 + .../templates/spinner/bars/Factory.d.ts | 6 + .../templates/spinner/bars/Factory.js | 13 + .../templates/spinner/base/Base.d.ts | 48 + .../templates/spinner/base/Base.js | 112 + .../spinner/base/EaseValueMethods.js | 67 + .../templates/spinner/box/Box.d.ts | 2 + .../templates/spinner/box/Box.js | 50 + .../templates/spinner/box/Factory.d.ts | 6 + .../templates/spinner/box/Factory.js | 13 + .../templates/spinner/clock/Clock.d.ts | 2 + .../templates/spinner/clock/Clock.js | 67 + .../templates/spinner/clock/Factory.d.ts | 6 + .../templates/spinner/clock/Factory.js | 13 + .../templates/spinner/cube/Cube.d.ts | 2 + .../templates/spinner/cube/Cube.js | 57 + .../templates/spinner/cube/Factory.d.ts | 6 + .../templates/spinner/cube/Factory.js | 13 + .../templates/spinner/custom/Custom.d.ts | 48 + .../templates/spinner/custom/Custom.js | 18 + .../templates/spinner/custom/Factory.d.ts | 5 + .../templates/spinner/custom/Factory.js | 13 + .../templates/spinner/dots/Dots.d.ts | 2 + .../templates/spinner/dots/Dots.js | 54 + .../templates/spinner/dots/Factory.d.ts | 6 + .../templates/spinner/dots/Factory.js | 13 + .../templates/spinner/facebook/Facebook.d.ts | 2 + .../templates/spinner/facebook/Facebook.js | 50 + .../templates/spinner/facebook/Factory.d.ts | 6 + .../templates/spinner/facebook/Factory.js | 13 + .../templates/spinner/grid/Factory.d.ts | 6 + .../templates/spinner/grid/Factory.js | 13 + .../templates/spinner/grid/Grid.d.ts | 2 + .../templates/spinner/grid/Grid.js | 60 + .../templates/spinner/los/Factory.d.ts | 6 + .../templates/spinner/los/Factory.js | 13 + .../templates/spinner/los/Los.d.ts | 2 + .../templates/spinner/los/Los.js | 49 + .../templates/spinner/orbit/Factory.d.ts | 6 + .../templates/spinner/orbit/Factory.js | 13 + .../templates/spinner/orbit/Orbit.d.ts | 2 + .../templates/spinner/orbit/Orbit.js | 39 + .../templates/spinner/oval/Factory.d.ts | 6 + .../templates/spinner/oval/Factory.js | 13 + .../templates/spinner/oval/Oval.d.ts | 2 + .../templates/spinner/oval/Oval.js | 39 + .../templates/spinner/pie/Factory.d.ts | 6 + .../templates/spinner/pie/Factory.js | 13 + .../templates/spinner/pie/Pie.d.ts | 2 + .../templates/spinner/pie/Pie.js | 68 + .../templates/spinner/puff/Factory.d.ts | 6 + .../templates/spinner/puff/Factory.js | 13 + .../templates/spinner/puff/Puff.d.ts | 2 + .../templates/spinner/puff/Puff.js | 31 + .../templates/spinner/radio/Factory.d.ts | 6 + .../templates/spinner/radio/Factory.js | 13 + .../templates/spinner/radio/Radio.d.ts | 2 + .../templates/spinner/radio/Radio.js | 82 + .../templates/spinner/rings/Factory.d.ts | 6 + .../templates/spinner/rings/Factory.js | 13 + .../templates/spinner/rings/Rings.d.ts | 2 + .../templates/spinner/rings/Rings.js | 38 + .../templates/spinner/spinner-components.d.ts | 39 + .../templates/spinner/spinner-components.js | 39 + .../templates/spinner/spinner-plugin.d.ts | 87 + .../templates/spinner/spinner-plugin.js | 35 + .../templates/spinner/spinner/Factory.d.ts | 6 + .../templates/spinner/spinner/Factory.js | 13 + .../templates/spinner/spinner/Spinner.d.ts | 2 + .../templates/spinner/spinner/Spinner.js | 34 + .../templates/spinner/utils/Geoms.js | 23 + .../templates/spinner/utils/Yoyo.js | 2 + .../templates/ui/ObjectFactory.js | 20 + .../ui/alphamaskimage/AlphaMaskImage.d.ts | 2 + .../ui/alphamaskimage/AlphaMaskImage.js | 2 + .../templates/ui/alphamaskimage/Factory.d.ts | 7 + .../templates/ui/alphamaskimage/Factory.js | 13 + .../templates/ui/anchor/Anchor.d.ts | 2 + .../templates/ui/anchor/Anchor.js | 2 + .../templates/ui/anchor/Factory.d.ts | 7 + .../templates/ui/anchor/Factory.js | 11 + .../templates/ui/badgelabel/BadgeLabel.d.ts | 30 + .../templates/ui/badgelabel/BadgeLabel.js | 49 + .../templates/ui/badgelabel/Factory.d.ts | 5 + .../templates/ui/badgelabel/Factory.js | 13 + .../templates/ui/basesizer/AddChildMethods.js | 37 + .../templates/ui/basesizer/AddChildrenMap.js | 6 + .../templates/ui/basesizer/BaseSizer.d.ts | 739 +++ .../templates/ui/basesizer/BaseSizer.js | 241 + .../templates/ui/basesizer/BroadcastEvent.js | 10 + .../templates/ui/basesizer/ClickMethods.js | 65 + .../ui/basesizer/ClickOutsideMethods.js | 65 + .../templates/ui/basesizer/DrawBounds.js | 90 + .../templates/ui/basesizer/EaseDataMethods.js | 44 + .../templates/ui/basesizer/EaseMoveMethods.js | 120 + .../templates/ui/basesizer/FadeMethods.js | 86 + .../ui/basesizer/GetAllChildrenSizers.js | 14 + .../templates/ui/basesizer/GetChildHeight.js | 17 + .../templates/ui/basesizer/GetChildWidth.js | 18 + .../ui/basesizer/GetChildrenHeight.js | 6 + .../ui/basesizer/GetChildrenSizers.js | 8 + .../ui/basesizer/GetChildrenWidth.js | 6 + .../templates/ui/basesizer/GetElement.js | 41 + .../ui/basesizer/GetExpandedChildHeight.js | 6 + .../ui/basesizer/GetExpandedChildWidth.js | 6 + .../ui/basesizer/GetParentSizerMethods.js | 56 + .../ui/basesizer/GetShownChildrenMethods.js | 43 + .../templates/ui/basesizer/GetSizerConfig.js | 8 + .../templates/ui/basesizer/HideMethods.js | 30 + .../templates/ui/basesizer/IsInTouching.js | 19 + .../templates/ui/basesizer/Layout.js | 19 + .../ui/basesizer/LayoutBackgrounds.js | 41 + .../templates/ui/basesizer/LayoutChildren.js | 6 + .../templates/ui/basesizer/Methods.js | 108 + .../templates/ui/basesizer/ModalMethods.js | 41 + .../templates/ui/basesizer/PaddingMethods.js | 36 + .../templates/ui/basesizer/PointToChild.js | 41 + .../templates/ui/basesizer/PostLayout.js | 7 + .../templates/ui/basesizer/PostResolveSize.js | 4 + .../templates/ui/basesizer/PreLayout.js | 15 + .../templates/ui/basesizer/PushIntoBounds.js | 15 + .../ui/basesizer/RemoveChildMethods.js | 39 + .../ui/basesizer/RemoveChildrenMap.js | 17 + .../ui/basesizer/ResolveChildrenWidth.js | 14 + .../templates/ui/basesizer/ResolveHeight.js | 14 + .../templates/ui/basesizer/ResolveWidth.js | 16 + .../templates/ui/basesizer/RunLayout.js | 47 + .../templates/ui/basesizer/RunWidthWrap.js | 23 + .../templates/ui/basesizer/ScaleMethods.js | 121 + .../templates/ui/basesizer/SetAnchor.js | 34 + .../ui/basesizer/SetChildrenInteractive.js | 8 + .../templates/ui/basesizer/SetDraggable.js | 47 + .../templates/ui/basesizer/ShakeMethods.js | 53 + .../templates/ui/basesizer/TouchingMethods.js | 118 + .../templates/ui/basesizer/utils/AddChild.js | 16 + .../templates/ui/basesizer/utils/CheckSize.js | 12 + .../ui/basesizer/utils/ClearChildren.js | 29 + .../ui/basesizer/utils/LayoutChild.js | 20 + .../ui/basesizer/utils/PreLayoutChild.js | 10 + .../ui/basesizer/utils/RemoveChild.js | 19 + .../templates/ui/bbcodetext/BBCodeText.d.ts | 2 + .../templates/ui/bbcodetext/BBCodeText.js | 2 + .../templates/ui/bbcodetext/Factory.d.ts | 7 + .../templates/ui/bbcodetext/Factory.js | 13 + .../templates/ui/buttons/AddChildMethods.js | 85 + .../templates/ui/buttons/Buttons.d.ts | 95 + .../templates/ui/buttons/Buttons.js | 88 + .../templates/ui/buttons/Factory.d.ts | 5 + .../templates/ui/buttons/Factory.js | 13 + .../ui/buttons/RemoveChildMethods.js | 55 + .../templates/ui/canvas/Canvas.d.ts | 2 + .../templates/ui/canvas/Canvas.js | 2 + .../templates/ui/canvas/Factory.d.ts | 6 + .../templates/ui/canvas/Factory.js | 13 + .../templates/ui/canvasinput/CanvasInput.d.ts | 2 + .../templates/ui/canvasinput/CanvasInput.js | 2 + .../templates/ui/canvasinput/Factory.d.ts | 7 + .../templates/ui/canvasinput/Factory.js | 13 + .../templates/ui/chart/Chart.d.ts | 42 + .../templates/ui/chart/Chart.js | 65 + .../templates/ui/chart/Factory.d.ts | 5 + .../templates/ui/chart/Factory.js | 13 + .../templates/ui/chart/GetChartData.js | 15 + .../templates/ui/chart/GetChartDataset.js | 21 + .../templates/ui/chart/SetChart.js | 66 + .../templates/ui/chart/SetChartData.js | 17 + .../templates/ui/chart/UpdateChart.js | 8 + .../templates/ui/checkbox/Checkbox.d.ts | 2 + .../templates/ui/checkbox/Checkbox.js | 2 + .../templates/ui/checkbox/Factory.d.ts | 19 + .../templates/ui/checkbox/Factory.js | 13 + .../ui/circlemaskimage/CircleMaskImage.d.ts | 2 + .../ui/circlemaskimage/CircleMaskImage.js | 2 + .../templates/ui/circlemaskimage/Factory.d.ts | 9 + .../templates/ui/circlemaskimage/Factory.js | 13 + .../ui/circularprogress/CircularProgress.d.ts | 2 + .../ui/circularprogress/CircularProgress.js | 2 + .../ui/circularprogress/Factory.d.ts | 13 + .../templates/ui/circularprogress/Factory.js | 13 + .../CircularProgressCanvas.d.ts | 2 + .../CircularProgressCanvas.js | 2 + .../ui/circularprogresscanvas/Factory.d.ts | 13 + .../ui/circularprogresscanvas/Factory.js | 13 + .../templates/ui/click/Click.d.ts | 2 + .../templates/ui/click/Click.js | 2 + .../templates/ui/click/Factory.d.ts | 7 + .../templates/ui/click/Factory.js | 11 + .../ui/clickoutside/ClickOutside.d.ts | 2 + .../templates/ui/clickoutside/ClickOutside.js | 2 + .../templates/ui/clickoutside/Factory.d.ts | 7 + .../templates/ui/clickoutside/Factory.js | 11 + .../colorcomponents/ColorComponents.d.ts | 60 + .../colorcomponents/ColorComponents.js | 187 + .../colorinput/colorcomponents/Factory.d.ts | 5 + .../ui/colorinput/colorcomponents/Factory.js | 13 + .../ui/colorinput/colorinput/ColorInput.d.ts | 59 + .../ui/colorinput/colorinput/ColorInput.js | 91 + .../ui/colorinput/colorinput/Factory.d.ts | 5 + .../ui/colorinput/colorinput/Factory.js | 13 + .../colorinput/methods/ColorPicker.js | 101 + .../methods/ConfigurationMethods.js | 107 + .../colorinput/methods/CreateColorPicker.js | 56 + .../colorinput/colorinput/methods/Methods.js | 13 + .../colorinput/methods/OpenColorPicker.js | 53 + .../colorinputbase/ColorInputBase.d.ts | 38 + .../colorinputbase/ColorInputBase.js | 145 + .../ui/colorinput/colorinputbase/Factory.d.ts | 5 + .../ui/colorinput/colorinputbase/Factory.js | 13 + .../colorinputbase/methods/CreateSwatch.js | 16 + .../colorinputbase/methods/SetSwatchColor.js | 13 + .../colorinput/colorpicker/ColorPicker.d.ts | 38 + .../ui/colorinput/colorpicker/ColorPicker.js | 177 + .../ui/colorinput/colorpicker/Factory.d.ts | 5 + .../ui/colorinput/colorpicker/Factory.js | 13 + .../colorpicker/methods/HPalette.js | 96 + .../colorpicker/methods/HPaletteCanvas.js | 107 + .../colorpicker/methods/SVPalette.js | 79 + .../colorpicker/methods/SVPaletteCanvas.js | 98 + .../colorpicker/methods/Transform.js | 27 + .../ui/confirmdialog/ConfirmDialog.d.ts | 127 + .../ui/confirmdialog/ConfirmDialog.js | 103 + .../templates/ui/confirmdialog/Factory.d.ts | 5 + .../templates/ui/confirmdialog/Factory.js | 13 + .../ui/confirmdialog/methods/CreateContent.js | 32 + .../ui/confirmdialog/methods/Methods.js | 9 + .../ui/confirmdialog/methods/Modal.js | 29 + .../confirmdialog/methods/RegisterEvents.js | 41 + .../methods/ResetDisplayContent.js | 115 + .../templates/ui/container/Container.d.ts | 2 + .../templates/ui/container/Container.js | 2 + .../templates/ui/container/Factory.d.ts | 8 + .../templates/ui/container/Factory.js | 13 + .../ui/customprogress/CustomProgress.d.ts | 2 + .../ui/customprogress/CustomProgress.js | 2 + .../templates/ui/customprogress/Factory.d.ts | 5 + .../templates/ui/customprogress/Factory.js | 13 + .../ui/customshapes/CustomShapes.d.ts | 2 + .../templates/ui/customshapes/CustomShapes.js | 2 + .../templates/ui/customshapes/Factory.d.ts | 5 + .../templates/ui/customshapes/Factory.js | 13 + .../templates/ui/dialog/Dialog.d.ts | 310 ++ .../templates/ui/dialog/Dialog.js | 306 ++ .../templates/ui/dialog/Factory.d.ts | 5 + .../templates/ui/dialog/Factory.js | 13 + .../ui/dialog/methods/ButtonMethods.js | 333 ++ .../templates/ui/dialog/methods/Methods.js | 12 + .../ui/dialog/methods/ModalMethods.js | 43 + .../templates/ui/drag/Drag.d.ts | 2 + .../templates/ui/drag/Drag.js | 2 + .../templates/ui/drag/Factory.d.ts | 7 + .../templates/ui/drag/Factory.js | 11 + .../templates/ui/dropdown/DropDown.js | 3 + .../ui/dropdownlist/DropDownList.d.ts | 130 + .../templates/ui/dropdownlist/DropDownList.js | 119 + .../templates/ui/dropdownlist/Factory.d.ts | 5 + .../templates/ui/dropdownlist/Factory.js | 13 + .../ui/dropdownlist/methods/Methods.js | 18 + .../methods/listpanel/CloseListPanel.js | 11 + .../methods/listpanel/ConfigurationMethods.js | 129 + .../methods/listpanel/CreateListPanel.js | 64 + .../methods/listpanel/OpenListPanel.js | 83 + .../methods/listpanel/ToggleListPanel.js | 10 + .../templates/ui/dynamictext/DynamicText.d.ts | 2 + .../templates/ui/dynamictext/DynamicText.js | 2 + .../templates/ui/dynamictext/Factory.d.ts | 5 + .../templates/ui/dynamictext/Factory.js | 13 + .../templates/ui/easemove/EaseMove.js | 2 + .../templates/ui/easemove/EaseMove.ts | 2 + .../templates/ui/fade/Fade.js | 5 + .../templates/ui/fade/Fade.ts | 5 + .../templates/ui/filechooser/Factory.d.ts | 5 + .../templates/ui/filechooser/Factory.js | 13 + .../templates/ui/filechooser/FileChooser.d.ts | 2 + .../templates/ui/filechooser/FileChooser.js | 2 + .../templates/ui/filedropzone/Factory.d.ts | 5 + .../templates/ui/filedropzone/Factory.js | 13 + .../ui/filedropzone/FileDropZone.d.ts | 2 + .../templates/ui/filedropzone/FileDropZone.js | 2 + .../ui/fileselectorbutton/Factory.d.ts | 5 + .../ui/fileselectorbutton/Factory.js | 13 + .../fileselectorbutton/FileChooserMethods.js | 21 + .../FileSelectorButton.d.ts | 45 + .../fileselectorbutton/FileSelectorButton.js | 45 + .../ui/fixwidthbuttons/AddChildMethods.js | 46 + .../templates/ui/fixwidthbuttons/Factory.d.ts | 5 + .../templates/ui/fixwidthbuttons/Factory.js | 13 + .../ui/fixwidthbuttons/FixWidthButtons.d.ts | 89 + .../ui/fixwidthbuttons/FixWidthButtons.js | 87 + .../ui/fixwidthbuttons/RemoveChildMethods.js | 50 + .../ui/fixwidthsizer/AddChildMethods.js | 74 + .../templates/ui/fixwidthsizer/Factory.d.ts | 17 + .../templates/ui/fixwidthsizer/Factory.js | 13 + .../ui/fixwidthsizer/FixWidthSizer.d.ts | 124 + .../ui/fixwidthsizer/FixWidthSizer.js | 124 + .../ui/fixwidthsizer/GetChildrenHeight.js | 10 + .../ui/fixwidthsizer/GetChildrenSizers.js | 17 + .../ui/fixwidthsizer/GetChildrenWidth.js | 10 + .../ui/fixwidthsizer/GetMaxChildHeight.js | 22 + .../ui/fixwidthsizer/GetMaxChildWidth.js | 18 + .../ui/fixwidthsizer/GetNearestChildIndex.js | 41 + .../ui/fixwidthsizer/LayoutChildren.js | 102 + .../templates/ui/fixwidthsizer/Methods.js | 25 + .../templates/ui/fixwidthsizer/PreLayout.js | 9 + .../ui/fixwidthsizer/RemoveChildMethods.js | 28 + .../ui/fixwidthsizer/RunChildrenWrap.js | 93 + .../ui/fixwidthsizer/RunWidthWrap.js | 10 + .../templates/ui/flip/Factory.d.ts | 7 + .../templates/ui/flip/Factory.js | 11 + .../templates/ui/flip/Flip.d.ts | 2 + .../templates/ui/flip/Flip.js | 2 + .../templates/ui/folder/Factory.d.ts | 5 + .../templates/ui/folder/Factory.js | 13 + .../templates/ui/folder/Folder.d.ts | 65 + .../templates/ui/folder/Folder.js | 122 + .../ui/folder/methods/ChildTransition.js | 24 + .../ui/folder/methods/ConfigurationMethods.js | 37 + .../ui/folder/methods/ExpandMethods.js | 75 + .../ui/fullwindowrectangle/Factory.d.ts | 7 + .../ui/fullwindowrectangle/Factory.js | 13 + .../FullWindowRectangle.d.ts | 2 + .../FullWindowRectangle.js | 2 + .../ui/gridbuttons/AddChildMethods.js | 18 + .../templates/ui/gridbuttons/Factory.d.ts | 5 + .../templates/ui/gridbuttons/Factory.js | 13 + .../templates/ui/gridbuttons/GridButtons.d.ts | 101 + .../templates/ui/gridbuttons/GridButtons.js | 124 + .../ui/gridbuttons/RemoveChildMethods.js | 50 + .../templates/ui/gridsizer/AddChildMethods.js | 112 + .../templates/ui/gridsizer/Factory.d.ts | 24 + .../templates/ui/gridsizer/Factory.js | 13 + .../ui/gridsizer/GetChildrenHeight.js | 49 + .../ui/gridsizer/GetChildrenSizers.js | 15 + .../ui/gridsizer/GetChildrenWidth.js | 45 + .../ui/gridsizer/GetExpandedChildHeight.js | 11 + .../ui/gridsizer/GetExpandedChildWidth.js | 11 + .../ui/gridsizer/GetTotalColumnProportions.js | 13 + .../ui/gridsizer/GetTotalRowProportions.js | 13 + .../templates/ui/gridsizer/GridSizer.d.ts | 145 + .../templates/ui/gridsizer/GridSizer.js | 171 + .../ui/gridsizer/InsertEmptyColumn.js | 34 + .../templates/ui/gridsizer/InsertEmptyRow.js | 35 + .../templates/ui/gridsizer/LayoutChildren.js | 68 + .../templates/ui/gridsizer/Methods.js | 45 + .../templates/ui/gridsizer/PreLayout.js | 11 + .../ui/gridsizer/RemoveChildMethods.js | 45 + .../templates/ui/gridsizer/ResetGrid.js | 77 + .../ui/gridsizer/ResolveChildrenWidth.js | 16 + .../templates/ui/gridsizer/ResolveHeight.js | 24 + .../templates/ui/gridsizer/ResolveWidth.js | 24 + .../templates/ui/gridsizer/RunWidthWrap.js | 25 + .../templates/ui/gridtable/Factory.d.ts | 5 + .../templates/ui/gridtable/Factory.js | 13 + .../templates/ui/gridtable/GridTable.d.ts | 63 + .../templates/ui/gridtable/GridTable.js | 140 + .../ui/gridtable/InjectProperties.js | 32 + .../templates/ui/gridtable/ScrollMethods.js | 13 + .../templates/ui/gridtable/SetItems.js | 16 + .../ui/gridtable/TableOnCellVisible.js | 28 + .../templates/ui/gridtable/input/ClickCell.js | 20 + .../ui/gridtable/input/EmitCellEvent.js | 17 + .../templates/ui/gridtable/input/OverCell.js | 30 + .../ui/gridtable/input/PointerUpDownCell.js | 13 + .../templates/ui/gridtable/input/PressCell.js | 22 + .../templates/ui/gridtable/input/SwipeCell.js | 26 + .../ui/gridtable/input/TableSetInteractive.js | 19 + .../templates/ui/gridtable/input/TapCell.js | 20 + .../templates/ui/hiddenedit/Factory.d.ts | 6 + .../templates/ui/hiddenedit/Factory.js | 13 + .../templates/ui/hiddenedit/HiddenEdit.d.ts | 2 + .../templates/ui/hiddenedit/HiddenEdit.js | 2 + .../templates/ui/holygrail/Factory.d.ts | 5 + .../templates/ui/holygrail/Factory.js | 13 + .../templates/ui/holygrail/HolyGrail.d.ts | 69 + .../templates/ui/holygrail/HolyGrail.js | 28 + .../templates/ui/holygrail/methods/Build.js | 40 + .../holygrail/methods/CreatExpandContainer.js | 11 + .../ui/holygrail/methods/GetAddChildConfig.js | 70 + .../ui/holygrail/methods/LayoutMode0.js | 58 + .../ui/holygrail/methods/LayoutMode1.js | 73 + .../ui/holygrail/methods/LayoutMode2.js | 74 + .../ui/holygrail/methods/LayoutMode3.js | 68 + .../templates/ui/imagebox/Factory.d.ts | 7 + .../templates/ui/imagebox/Factory.js | 13 + .../templates/ui/imagebox/ImageBox.d.ts | 2 + .../templates/ui/imagebox/ImageBox.js | 2 + .../templates/ui/inputtext/Factory.d.ts | 5 + .../templates/ui/inputtext/Factory.js | 13 + .../templates/ui/inputtext/InputText.d.ts | 2 + .../templates/ui/inputtext/InputText.js | 2 + .../templates/ui/intouching/Factory.d.ts | 7 + .../templates/ui/intouching/Factory.js | 11 + .../templates/ui/intouching/InTouching.d.ts | 2 + .../templates/ui/intouching/InTouching.js | 2 + .../templates/ui/knob/Factory.d.ts | 5 + .../templates/ui/knob/Factory.js | 13 + .../templates/ui/knob/Knob.d.ts | 63 + .../templates/ui/knob/Knob.js | 123 + .../templates/ui/knob/TextObjectMethods.js | 36 + .../ui/knob/input/IsLocalPointInKnob.js | 8 + .../templates/ui/knob/input/OnPanPad.js | 90 + .../templates/ui/knob/input/OnTouchPad.js | 40 + .../templates/ui/label/Factory.d.ts | 5 + .../templates/ui/label/Factory.js | 13 + .../templates/ui/label/Label.d.ts | 100 + .../templates/ui/label/Label.js | 297 ++ .../templates/ui/label/methods/Methods.js | 9 + .../ui/label/methods/ResetDisplayContent.js | 53 + .../templates/ui/lineprogress/Factory.d.ts | 13 + .../templates/ui/lineprogress/Factory.js | 13 + .../ui/lineprogress/LineProgress.d.ts | 2 + .../templates/ui/lineprogress/LineProgress.js | 2 + .../ui/lineprogresscanvas/Factory.d.ts | 19 + .../ui/lineprogresscanvas/Factory.js | 13 + .../LineProgressCanvas.d.ts | 2 + .../lineprogresscanvas/LineProgressCanvas.js | 2 + .../templates/ui/maker/Factory.d.ts | 6 + .../templates/ui/maker/Factory.js | 11 + .../templates/ui/maker/Make.d.ts | 15 + .../templates/ui/maker/Make.js | 31 + .../templates/ui/maker/Maker.d.ts | 35 + .../templates/ui/maker/Maker.js | 80 + .../templates/ui/maker/YAMLMake.d.ts | 15 + .../templates/ui/maker/YAMLMake.js | 35 + .../templates/ui/maker/builders/Builders.d.ts | 82 + .../templates/ui/maker/builders/Builders.js | 79 + .../ui/maker/builders/CreateBBCodeText.js | 16 + .../ui/maker/builders/CreateBadgeLabel.js | 26 + .../ui/maker/builders/CreateButtons.js | 18 + .../ui/maker/builders/CreateCanvas.js | 23 + .../maker/builders/CreateCircleMaskImage.js | 22 + .../ui/maker/builders/CreateDialog.js | 31 + .../maker/builders/CreateFixWidthButtons.js | 18 + .../ui/maker/builders/CreateFixWidthSizer.js | 8 + .../ui/maker/builders/CreateGridButtons.js | 43 + .../ui/maker/builders/CreateGridSizer.js | 27 + .../ui/maker/builders/CreateHolyGrail.js | 21 + .../ui/maker/builders/CreateImage.js | 9 + .../templates/ui/maker/builders/CreateKnob.js | 17 + .../ui/maker/builders/CreateLabel.js | 8 + .../templates/ui/maker/builders/CreateMenu.js | 42 + .../ui/maker/builders/CreateNinePatch.js | 15 + .../ui/maker/builders/CreateNinePatch2.js | 12 + .../ui/maker/builders/CreateNumberBar.js | 20 + .../ui/maker/builders/CreateOverlapSizer.js | 8 + .../ui/maker/builders/CreatePages.js | 8 + .../ui/maker/builders/CreateRoundRectangle.js | 12 + .../ui/maker/builders/CreateScrollBar.js | 26 + .../maker/builders/CreateScrollablePanel.js | 38 + .../ui/maker/builders/CreateSizer.js | 8 + .../ui/maker/builders/CreateSlider.js | 19 + .../ui/maker/builders/CreateSpace.js | 10 + .../ui/maker/builders/CreateSprite.js | 9 + .../templates/ui/maker/builders/CreateText.js | 17 + .../ui/maker/builders/CreateTextArea.js | 21 + .../ui/maker/builders/CreateTextBox.js | 8 + .../ui/maker/builders/CreateToast.js | 8 + .../ui/maker/builders/CreateVideo.js | 23 + .../ui/maker/builders/utils/CreateAnyImage.js | 21 + .../ui/maker/builders/utils/CreateAnyLabel.js | 18 + .../ui/maker/builders/utils/CreateAnySizer.js | 30 + .../ui/maker/builders/utils/CreateChild.js | 16 + .../ui/maker/builders/utils/CreateChildren.js | 26 + .../ui/maker/builders/utils/GetTypeName.js | 27 + .../ui/maker/builders/utils/MergeStyle.js | 33 + .../builders/utils/ReplaceChildrenConfig.js | 22 + .../builders/utils/ReplaceSliderConfig.js | 14 + .../builders/utils/SetTextureProperties.js | 22 + .../templates/ui/maker/index.d.ts | 12 + .../templates/ui/maker/index.js | 12 + .../templates/ui/maker/schemas/BBCodeText.yml | 50 + .../templates/ui/maker/schemas/BadgeLabel.yml | 27 + .../templates/ui/maker/schemas/Buttons.yml | 29 + .../templates/ui/maker/schemas/Canvas.yml | 13 + .../ui/maker/schemas/CircleMaskImage.yml | 22 + .../templates/ui/maker/schemas/Dialog.yml | 82 + .../ui/maker/schemas/FixWidthButtons.yml | 33 + .../ui/maker/schemas/FixWidthSizer.yml | 41 + .../ui/maker/schemas/GridButtons.yml | 57 + .../templates/ui/maker/schemas/GridSizer.yml | 63 + .../templates/ui/maker/schemas/HolyGrail.yml | 64 + .../templates/ui/maker/schemas/Image.yml | 14 + .../templates/ui/maker/schemas/Knob.yml | 32 + .../templates/ui/maker/schemas/Label.yml | 28 + .../templates/ui/maker/schemas/Menu.yml | 31 + .../templates/ui/maker/schemas/NinePatch.yml | 17 + .../templates/ui/maker/schemas/NinePatch2.yml | 11 + .../templates/ui/maker/schemas/NumberBar.yml | 34 + .../ui/maker/schemas/OverlapSizer.yml | 37 + .../templates/ui/maker/schemas/Pages.yml | 37 + .../ui/maker/schemas/RoundRectangle.yml | 11 + .../templates/ui/maker/schemas/ScrollBar.yml | 37 + .../ui/maker/schemas/ScrollablePanel.yml | 70 + .../templates/ui/maker/schemas/Sizer.yml | 41 + .../templates/ui/maker/schemas/Slider.yml | 28 + .../templates/ui/maker/schemas/Space.yml | 1 + .../templates/ui/maker/schemas/Sprite.yml | 14 + .../templates/ui/maker/schemas/Text.yml | 39 + .../templates/ui/maker/schemas/TextArea.yml | 73 + .../templates/ui/maker/schemas/TextBox.yml | 33 + .../templates/ui/maker/schemas/Toast.yml | 35 + .../templates/ui/maker/schemas/Video.yml | 13 + .../templates/ui/maker/utils/ParseYAML.js | 15 + .../templates/ui/menu/Factory.d.ts | 5 + .../templates/ui/menu/Factory.js | 13 + .../templates/ui/menu/Menu.d.ts | 49 + .../templates/ui/menu/Menu.js | 210 + .../templates/ui/menu/methods/Collapse.js | 16 + .../ui/menu/methods/CollapseSubMenu.js | 12 + .../ui/menu/methods/CreateBackground.js | 16 + .../ui/menu/methods/CreateButtons.js | 22 + .../ui/menu/methods/DelayCallMethods.js | 17 + .../templates/ui/menu/methods/Expand.js | 16 + .../ui/menu/methods/ExpandSubMenu.js | 40 + .../ui/menu/methods/GetEaseConfig.js | 10 + .../ui/menu/methods/MenuSetInteractive.js | 45 + .../templates/ui/menu/methods/Methods.js | 18 + .../ui/menu/methods/ParseEaseConfig.js | 18 + .../menu/methods/SetTransitCallbackMethods.js | 32 + .../templates/ui/modal/Modal.d.ts | 2 + .../templates/ui/modal/Modal.js | 2 + .../templates/ui/namevaluelabel/Factory.d.ts | 5 + .../templates/ui/namevaluelabel/Factory.js | 13 + .../ui/namevaluelabel/NameValueLabel.d.ts | 94 + .../ui/namevaluelabel/NameValueLabel.js | 161 + .../ui/namevaluelabel/methods/Build.js | 186 + .../namevaluelabel/methods/SetValueMethods.js | 50 + .../templates/ui/ninepatch/Factory.d.ts | 39 + .../templates/ui/ninepatch/Factory.js | 13 + .../templates/ui/ninepatch/NinePatch.d.ts | 2 + .../templates/ui/ninepatch/NinePatch.js | 2 + .../templates/ui/ninepatch2/Factory.d.ts | 39 + .../templates/ui/ninepatch2/Factory.js | 13 + .../templates/ui/ninepatch2/NinePatch.d.ts | 2 + .../templates/ui/ninepatch2/NinePatch.js | 2 + .../templates/ui/numberbar/Factory.d.ts | 5 + .../templates/ui/numberbar/Factory.js | 13 + .../templates/ui/numberbar/NumberBar.d.ts | 71 + .../templates/ui/numberbar/NumberBar.js | 232 + .../ui/overlapsizer/AddChildMethods.js | 102 + .../templates/ui/overlapsizer/Factory.d.ts | 16 + .../templates/ui/overlapsizer/Factory.js | 13 + .../ui/overlapsizer/GetChildrenHeight.js | 24 + .../ui/overlapsizer/GetChildrenSizers.js | 15 + .../ui/overlapsizer/GetChildrenWidth.js | 19 + .../ui/overlapsizer/GetExpandedChildHeight.js | 16 + .../ui/overlapsizer/GetExpandedChildWidth.js | 16 + .../ui/overlapsizer/LayoutChildren.js | 60 + .../templates/ui/overlapsizer/Methods.js | 25 + .../ui/overlapsizer/OverlapSizer.d.ts | 109 + .../templates/ui/overlapsizer/OverlapSizer.js | 49 + .../ui/overlapsizer/RemoveChildMethods.js | 46 + .../templates/ui/pages/Factory.d.ts | 5 + .../templates/ui/pages/Factory.js | 13 + .../templates/ui/pages/Pages.d.ts | 72 + .../templates/ui/pages/Pages.js | 65 + .../ui/pages/methods/AddChildMethods.js | 14 + .../templates/ui/pages/methods/GetPage.js | 10 + .../templates/ui/pages/methods/HasPage.js | 5 + .../templates/ui/pages/methods/Methods.js | 17 + .../templates/ui/pages/methods/SwapPage.js | 38 + .../templates/ui/pan/Factory.d.ts | 7 + .../templates/ui/pan/Factory.js | 16 + .../templates/ui/pan/Pan.d.ts | 2 + .../templates/ui/pan/Pan.js | 2 + .../templates/ui/perspective/Factory.d.ts | 7 + .../templates/ui/perspective/Factory.js | 11 + .../templates/ui/perspective/Perspective.d.ts | 2 + .../templates/ui/perspective/Perspective.js | 2 + .../CreatePerspectiveCardMesh.js | 39 + .../templates/ui/perspectivecard/Factory.d.ts | 5 + .../templates/ui/perspectivecard/Factory.js | 13 + .../ui/perspectivecard/PerspectiveCard.d.ts | 57 + .../ui/perspectivecard/PerspectiveCard.js | 161 + .../ui/perspectivecard/PerspectiveMethods.js | 67 + .../templates/ui/pinch/Factory.d.ts | 7 + .../templates/ui/pinch/Factory.js | 11 + .../templates/ui/pinch/Pinch.d.ts | 2 + .../templates/ui/pinch/Pinch.js | 2 + .../templates/ui/press/Factory.d.ts | 7 + .../templates/ui/press/Factory.js | 16 + .../templates/ui/press/Press.d.ts | 2 + .../templates/ui/press/Press.js | 2 + .../templates/ui/rotate/Factory.d.ts | 7 + .../templates/ui/rotate/Factory.js | 11 + .../templates/ui/rotate/Rotate.d.ts | 2 + .../templates/ui/rotate/Rotate.js | 2 + .../templates/ui/roundrectangle/Factory.d.ts | 16 + .../templates/ui/roundrectangle/Factory.js | 13 + .../ui/roundrectangle/RoundRectangle.d.ts | 2 + .../ui/roundrectangle/RoundRectangle.js | 2 + .../ui/roundrectanglecanvas/Factory.d.ts | 20 + .../ui/roundrectanglecanvas/Factory.js | 13 + .../RoundRectangleCanvas.d.ts | 2 + .../RoundRectangleCanvas.js | 2 + .../templates/ui/scrollablepanel/Factory.d.ts | 5 + .../templates/ui/scrollablepanel/Factory.js | 13 + .../ui/scrollablepanel/ScrollablePanel.d.ts | 40 + .../ui/scrollablepanel/ScrollablePanel.js | 71 + .../scrollableblock/GetChildrenHeight.js | 24 + .../scrollableblock/GetChildrenSizers.js | 10 + .../scrollableblock/GetChildrenWidth.js | 20 + .../scrollableblock/LayoutChildren.js | 29 + .../scrollableblock/Methods.js | 21 + .../scrollableblock/ResetChildPosition.js | 15 + .../scrollableblock/ScrollableBlock.js | 206 + .../templates/ui/scrollbar/Factory.d.ts | 5 + .../templates/ui/scrollbar/Factory.js | 13 + .../templates/ui/scrollbar/ScrollBar.d.ts | 67 + .../templates/ui/scrollbar/ScrollBar.js | 188 + .../templates/ui/shake/Factory.d.ts | 7 + .../templates/ui/shake/Factory.js | 11 + .../templates/ui/shake/Shake.d.ts | 2 + .../templates/ui/shake/Shake.js | 2 + .../templates/ui/sides/Factory.js | 13 + .../templates/ui/sides/ShowChildMethods.js | 111 + .../templates/ui/sides/Sides.js | 84 + .../templates/ui/sides/childbehaviors/Fade.js | 36 + .../templates/ui/sides/childbehaviors/Move.js | 156 + .../ui/sides/childbehaviors/Visible.js | 21 + .../ui/sides/childbehaviors/index.js | 14 + .../sides/defaultcallbacks/FadeCallbacks.js | 26 + .../defaultcallbacks/GetDefaultCallbacks.js | 32 + .../sides/defaultcallbacks/MoveCallbacks.js | 12 + .../defaultcallbacks/MovePanelCallbacks.js | 12 + .../defaultcallbacks/VisibleCallbacks.js | 20 + .../ui/simpledropdownlist/Factory.d.ts | 6 + .../ui/simpledropdownlist/Factory.js | 13 + .../SimpleDropDownList.d.ts | 20 + .../simpledropdownlist/SimpleDropDownList.js | 27 + .../templates/ui/simplelabel/Factory.d.ts | 6 + .../templates/ui/simplelabel/Factory.js | 13 + .../templates/ui/simplelabel/SimpleLabel.d.ts | 24 + .../templates/ui/simplelabel/SimpleLabel.js | 37 + .../templates/ui/sizer/AddChildMethods.js | 170 + .../templates/ui/sizer/AlignMethods.js | 17 + .../templates/ui/sizer/ExpandMethods.js | 11 + .../templates/ui/sizer/Factory.d.ts | 23 + .../templates/ui/sizer/Factory.js | 13 + .../templates/ui/sizer/GetChildrenHeight.js | 58 + .../ui/sizer/GetChildrenProportion.js | 17 + .../templates/ui/sizer/GetChildrenSizers.js | 15 + .../templates/ui/sizer/GetChildrenWidth.js | 58 + .../ui/sizer/GetExpandedChildHeight.js | 22 + .../ui/sizer/GetExpandedChildWidth.js | 22 + .../ui/sizer/GetNearestChildIndex.js | 42 + .../templates/ui/sizer/LayoutChildren.js | 98 + .../templates/ui/sizer/Methods.js | 39 + .../templates/ui/sizer/PostResolveSize.js | 46 + .../templates/ui/sizer/PreLayout.js | 26 + .../templates/ui/sizer/ProportionMethods.js | 11 + .../templates/ui/sizer/RemoveChildMethods.js | 29 + .../templates/ui/sizer/ResolveHeight.js | 23 + .../templates/ui/sizer/ResolveWidth.js | 23 + .../templates/ui/sizer/Sizer.d.ts | 203 + .../templates/ui/sizer/Sizer.js | 79 + .../templates/ui/skew/Factory.d.ts | 7 + .../templates/ui/skew/Factory.js | 11 + .../templates/ui/skew/Skew.d.ts | 2 + .../templates/ui/skew/Skew.js | 2 + .../templates/ui/slider/Factory.d.ts | 5 + .../templates/ui/slider/Factory.js | 13 + .../templates/ui/slider/GetEndPoint.js | 27 + .../templates/ui/slider/GetStartPoint.js | 27 + .../templates/ui/slider/GetThumbAlignPoint.js | 23 + .../templates/ui/slider/OnDragThumb.js | 22 + .../templates/ui/slider/OnTouchTrack.js | 33 + .../templates/ui/slider/PercentToPosition.js | 13 + .../templates/ui/slider/PositionToPercent.js | 13 + .../templates/ui/slider/Slider.d.ts | 58 + .../templates/ui/slider/Slider.js | 194 + .../templates/ui/slider/UpdateIndicator.js | 67 + .../templates/ui/slider/UpdateThumb.js | 26 + .../templates/ui/space/Factory.d.ts | 3 + .../templates/ui/space/Factory.js | 14 + .../templates/ui/space/Space.d.ts | 6 + .../templates/ui/space/Space.js | 10 + .../ui/statesroundrectangle/Factory.d.ts | 6 + .../ui/statesroundrectangle/Factory.js | 13 + .../StatesRoundRectangle.d.ts | 49 + .../StatesRoundRectangle.js | 38 + .../methods/ExtractStyle.js | 18 + .../methods/SetStateMethods.js | 81 + .../templates/ui/swipe/Factory.d.ts | 7 + .../templates/ui/swipe/Factory.js | 16 + .../templates/ui/swipe/Swipe.d.ts | 2 + .../templates/ui/swipe/Swipe.js | 2 + .../templates/ui/tabpages/Factory.d.ts | 5 + .../templates/ui/tabpages/Factory.js | 13 + .../templates/ui/tabpages/TabPages.d.ts | 74 + .../templates/ui/tabpages/TabPages.js | 116 + .../templates/ui/tabpages/methods/AddPage.js | 25 + .../templates/ui/tabpages/methods/GetPage.js | 8 + .../ui/tabpages/methods/GetPageIndexByKey.js | 12 + .../ui/tabpages/methods/GetPageKeyByIndex.js | 10 + .../templates/ui/tabpages/methods/GetTab.js | 11 + .../templates/ui/tabpages/methods/Methods.js | 24 + .../ui/tabpages/methods/RemovePageMethods.js | 24 + .../ui/tabpages/methods/SwapPageMethods.js | 44 + .../templates/ui/tabs/ButtonMethods.js | 275 ++ .../templates/ui/tabs/Factory.d.ts | 5 + .../templates/ui/tabs/Factory.js | 13 + .../templates/ui/tabs/Tabs.d.ts | 250 + .../templates/ui/tabs/Tabs.js | 138 + .../templates/ui/tagtext/Factory.d.ts | 7 + .../templates/ui/tagtext/Factory.js | 13 + .../templates/ui/tagtext/TagText.d.ts | 2 + .../templates/ui/tagtext/TagText.js | 2 + .../templates/ui/tap/Factory.d.ts | 7 + .../templates/ui/tap/Factory.js | 16 + .../templates/ui/tap/Tap.d.ts | 2 + .../templates/ui/tap/Tap.js | 2 + .../templates/ui/textarea/Factory.d.ts | 5 + .../templates/ui/textarea/Factory.js | 13 + .../templates/ui/textarea/InjectProperties.js | 32 + .../templates/ui/textarea/ScrollMethods.js | 18 + .../templates/ui/textarea/SetTextMethods.js | 14 + .../templates/ui/textarea/TextArea.d.ts | 48 + .../templates/ui/textarea/TextArea.js | 83 + .../ui/textarea/textblock/GetLines.js | 22 + .../ui/textarea/textblock/LayoutChildren.js | 36 + .../textblock/LinesCountToTextHeight.js | 5 + .../ui/textarea/textblock/Methods.js | 11 + .../ui/textarea/textblock/PreLayout.js | 16 + .../textblock/ResetTextObjectPosition.js | 35 + .../ui/textarea/textblock/ResizeText.js | 36 + .../ui/textarea/textblock/SetText.js | 21 + .../ui/textarea/textblock/TextBlock.js | 287 ++ .../textblock/TextHeightToLinesCount.js | 5 + .../ui/textarea/textblock/UpdateTextObject.js | 21 + .../templates/ui/textbox/Factory.d.ts | 5 + .../templates/ui/textbox/Factory.js | 13 + .../templates/ui/textbox/TextBox.d.ts | 46 + .../templates/ui/textbox/TextBox.js | 122 + .../templates/ui/textedit/Edit.d.ts | 2 + .../templates/ui/textedit/Edit.js | 2 + .../templates/ui/textedit/Factory.d.ts | 7 + .../templates/ui/textedit/Factory.js | 11 + .../templates/ui/textedit/TextEdit.d.ts | 2 + .../templates/ui/textedit/TextEdit.js | 2 + .../templates/ui/textpage/Factory.d.ts | 7 + .../templates/ui/textpage/Factory.js | 11 + .../templates/ui/textpage/TextPage.d.ts | 2 + .../templates/ui/textpage/TextPage.js | 2 + .../templates/ui/textplayer/Factory.d.ts | 5 + .../templates/ui/textplayer/Factory.js | 13 + .../templates/ui/textplayer/TextPlayer.d.ts | 2 + .../templates/ui/textplayer/TextPlayer.js | 2 + .../templates/ui/texttyping/Factory.d.ts | 7 + .../templates/ui/texttyping/Factory.js | 11 + .../templates/ui/texttyping/TextTyping.d.ts | 2 + .../templates/ui/texttyping/TextTyping.js | 2 + .../templates/ui/titlelabel/Factory.d.ts | 5 + .../templates/ui/titlelabel/Factory.js | 13 + .../templates/ui/titlelabel/TitleLabel.d.ts | 63 + .../templates/ui/titlelabel/TitleLabel.js | 262 ++ .../ui/toast/DefaultTransitCallbacks.js | 17 + .../templates/ui/toast/Factory.d.ts | 5 + .../templates/ui/toast/Factory.js | 13 + .../templates/ui/toast/Toast.d.ts | 49 + .../templates/ui/toast/Toast.js | 193 + .../templates/ui/toggleswitch/Factory.d.ts | 19 + .../templates/ui/toggleswitch/Factory.js | 13 + .../ui/toggleswitch/ToggleSwitch.d.ts | 2 + .../templates/ui/toggleswitch/ToggleSwitch.js | 2 + .../templates/ui/toucheventstop/Factory.d.ts | 7 + .../templates/ui/toucheventstop/Factory.js | 11 + .../ui/toucheventstop/TouchEventStop.d.ts | 2 + .../ui/toucheventstop/TouchEventStop.js | 2 + .../templates/ui/transitionimage/Factory.d.ts | 7 + .../templates/ui/transitionimage/Factory.js | 13 + .../ui/transitionimage/TransitionImage.d.ts | 2 + .../ui/transitionimage/TransitionImage.js | 2 + .../templates/ui/triangle/Factory.d.ts | 11 + .../templates/ui/triangle/Factory.js | 13 + .../templates/ui/triangle/Triangle.d.ts | 2 + .../templates/ui/triangle/Triangle.js | 2 + .../templates/ui/tweaker/Factory.d.ts | 5 + .../templates/ui/tweaker/Factory.js | 13 + .../templates/ui/tweaker/Tweaker.d.ts | 225 + .../templates/ui/tweaker/Tweaker.js | 23 + .../templates/ui/tweaker/TweakerShell.js | 68 + .../ui/tweaker/builders/CreateBackground.js | 8 + .../ui/tweaker/builders/CreateButtons.js | 57 + .../ui/tweaker/builders/CreateButtonsInput.js | 12 + .../tweaker/builders/CreateCheckboxInput.js | 10 + .../ui/tweaker/builders/CreateColorInput.js | 10 + .../ui/tweaker/builders/CreateFolder.js | 46 + .../ui/tweaker/builders/CreateInputField.js | 66 + .../ui/tweaker/builders/CreateInputRow.js | 35 + .../ui/tweaker/builders/CreateListInput.js | 12 + .../ui/tweaker/builders/CreateNumberInput.js | 12 + .../ui/tweaker/builders/CreateRangeInput.js | 13 + .../ui/tweaker/builders/CreateTab.js | 39 + .../ui/tweaker/builders/CreateTextInput.js | 12 + .../ui/tweaker/builders/CreateTitleLabel.js | 10 + .../builders/CreateToggleSwitchInput.js | 10 + .../folder/BindingTargetMethods.js | 7 + .../ui/tweaker/gameobjects/folder/Folder.js | 25 + .../folder/InputRowTitleWidthMethods.js | 15 + .../gameobjects/inputfield/ButtonsInput.js | 91 + .../gameobjects/inputfield/CheckboxInput.js | 53 + .../gameobjects/inputfield/ColorInput.js | 45 + .../gameobjects/inputfield/InputFieldBase.js | 89 + .../gameobjects/inputfield/ListInput.js | 54 + .../gameobjects/inputfield/NumberInput.js | 55 + .../gameobjects/inputfield/RangeInput.js | 98 + .../gameobjects/inputfield/TextInput.js | 54 + .../inputfield/ToggleSwitchInput.js | 56 + .../inputrow/BindingTargetMethods.js | 53 + .../tweaker/gameobjects/inputrow/InputRow.js | 87 + .../inputrow/MinTitleWidthMethods.js | 25 + .../inputrow/MonitorTargetMethods.js | 36 + .../tweaker/gameobjects/label/FolderTitle.js | 40 + .../ui/tweaker/gameobjects/label/Title.js | 29 + .../tabpages/BindingTargetMethods.js | 9 + .../tabpages/InputRowTitleWidthMethods.js | 21 + .../tweaker/gameobjects/tabpages/TabPages.js | 18 + .../gameobjects/utils/CreateButtons.js | 9 + .../gameobjects/utils/CreateCheckbox.js | 9 + .../tweaker/gameobjects/utils/CreateSlider.js | 10 + .../gameobjects/utils/CreateToggleSwitch.js | 9 + .../gameobjects/utils/CreateTweaker.js | 9 + .../gameobjects/utils/CreateWrapButtons.js | 9 + .../utils/SetButtonsActiveState.js | 12 + .../ui/tweaker/methods/AddButtons.js | 46 + .../templates/ui/tweaker/methods/AddFolder.js | 42 + .../templates/ui/tweaker/methods/AddInput.js | 66 + .../ui/tweaker/methods/AddSeparator.js | 21 + .../templates/ui/tweaker/methods/AddTab.js | 46 + .../methods/GetMaxInputRowTitleWidth.js | 20 + .../templates/ui/tweaker/methods/Methods.js | 24 + .../ui/tweaker/methods/SetBindingTarget.js | 15 + .../tweaker/methods/SetInputRowTitleWidth.js | 20 + .../ui/tweaker/utils/OptionsMethods.js | 23 + .../ui/tweaker/utils/inputs/GetInputType.js | 52 + .../ui/tweaker/utils/inputs/InputTypes.js | 15 + .../templates/ui/ui-components.d.ts | 238 + .../templates/ui/ui-components.js | 239 + .../templates/ui/ui-plugin.d.ts | 420 ++ .../templates/ui/ui-plugin.js | 185 + .../templates/ui/utils/AlignConst.js | 2 + .../templates/ui/utils/ContainsPoint.js | 35 + .../templates/ui/utils/CopyState.js | 22 + .../templates/ui/utils/GetBoundsConfig.js | 2 + .../templates/ui/utils/GetChildPrevState.js | 11 + .../templates/ui/utils/GetGameObjectByName.js | 2 + .../templates/ui/utils/GetOrientationMode.js | 20 + .../templates/ui/utils/GetParentSizer.d.ts | 11 + .../templates/ui/utils/GetParentSizer.js | 6 + .../templates/ui/utils/GetScrollMode.js | 12 + .../templates/ui/utils/GetSizerConfig.js | 7 + .../templates/ui/utils/Hide.d.ts | 12 + .../templates/ui/utils/Hide.js | 39 + .../templates/ui/utils/ScrollModeConst.js | 9 + .../templates/ui/utils/WaitEvent.d.ts | 2 + .../templates/ui/utils/WaitEvent.js | 2 + .../ui/utils/buttongroup/AddMethods.js | 50 + .../ui/utils/buttongroup/ButtonGroup.js | 40 + .../ui/utils/buttongroup/ButtonMethods.js | 100 + .../utils/buttongroup/ButtonStateMethods.js | 33 + .../ui/utils/buttongroup/Buttons.d.ts | 84 + .../utils/buttongroup/ButtonsTypeMethods.js | 164 + .../ui/utils/buttongroup/FireEvent.js | 28 + .../buttongroup/InjectSelectedProperty.js | 27 + .../utils/buttongroup/OnButtonStateChange.js | 19 + .../ui/utils/buttongroup/RemoveMethods.js | 26 + .../FontSizeExpandText.d.ts | 4 + .../fontsizeexpandtext/FontSizeExpandText.js | 27 + .../utils/scrollable/CreateScrollableSizer.js | 169 + .../ui/utils/scrollable/ResizeController.js | 69 + .../ui/utils/scrollable/Scrollable.d.ts | 110 + .../ui/utils/scrollable/Scrollable.js | 333 ++ .../templates/ui/utils/scrollable/Slider.js | 36 + .../ui/utils/scrollable/UpdateController.js | 12 + .../setchildreninteractive/ClickChild.js | 34 + .../utils/setchildreninteractive/DownChild.js | 29 + .../setchildreninteractive/EmitChildEvent.js | 18 + .../utils/setchildreninteractive/OverChild.js | 59 + .../setchildreninteractive/PointToChild.js | 15 + .../setchildreninteractive/PressChild.js | 37 + .../SetChildrenInteractive.d.ts | 27 + .../SetChildrenInteractive.js | 31 + .../setchildreninteractive/SwipeChild.js | 40 + .../utils/setchildreninteractive/TapChild.js | 28 + .../utils/setchildreninteractive/UpChild.js | 29 + .../wrapexpandtext/BitmapTextRunWidthWrap.js | 11 + .../wrapexpandtext/DynamicTextRunWidthWrap.js | 13 + .../utils/wrapexpandtext/TextRunWidthWrap.js | 28 + .../utils/wrapexpandtext/WrapExpandText.d.ts | 4 + .../ui/utils/wrapexpandtext/WrapExpandText.js | 27 + .../templates/ui/yaml/yaml.d.ts | 2 + .../templates/ui/yaml/yaml.js | 3 + ui/src/scenes/index.ts | 1 - ui/src/scenes/message/message.ts | 35 - ui/src/scenes/town/town.ts | 187 +- 4194 files changed, 186761 insertions(+), 208 deletions(-) create mode 100644 ui/src/classes/event_center.ts delete mode 100644 ui/src/classes/textbox.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/achievements-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/achievements-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/achievements.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/achievements.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/actions/GridCutImage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/actions/GridCutImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/actions/HexagonGridAlign.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/actions/QuadGridAlign.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/actions/RandomPlace.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/actions/RandomPlace.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/alphamaskimage-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/alphamaskimage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/alphamaskimage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/anchor-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/anchor-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/anchor.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/anchor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/arcadestepclock-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/arcadestepclock.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/arcadetcrp-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/arcadetcrp-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/arcadetcrp.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/arcadetcrp.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/audio/fade/Fade.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeIn.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeIn.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeOut.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeOut.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/audio/midiplayer/MidiPlayer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/audio/midiplayer/Track.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/awaitloader-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/awaitloader.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/awaitloader.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/awaytime-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/awaytime-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/awaytime.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/awaytime.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bank-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bank.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/barrelpipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/barrelpipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/barrelpipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/barrelpipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bbcodetext-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bbcodetext.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/bbcodetext.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/anchor/Anchor.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/anchor/Anchor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/bitmapzone/BitmapZone.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/bitmapzone/BitmapZone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/boids/AddAlignmentForce.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/boids/AddCohesionForce.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/boids/AddSeparationForce.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/boids/Boids.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/boids/Boids.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/bounds/Bounds.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/bounds/Bounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/bullet/Bullet.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/bullet/Bullet.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/containerperspective/ContainerPerspective.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/containerperspective/ContainerPerspective.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/containerskew/ContainerSkew.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/containerskew/ContainerSkew.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/dropdown/DropDown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/dropdown/Dropdown.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/dropdown/SetPosition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/easedata/EaseData.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/easedata/EaseData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMove.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMove.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveFrom.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveFrom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveTo.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/ParseValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/eightdirection/EightDirection.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/eightdirection/EightDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/fade/Fade.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/fade/Fade.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/filechooser/CreateFileInput.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/filechooser/Open.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/filechooser/Open.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/flash/Flash.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/flash/Flash.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/flip/Flip.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/flip/Flip.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/flip/GetFaceUpdatingCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEdit.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEdit.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEditBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEditBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/defaultcallbacks/NumberInputUpdateCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/Close.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/CopyElementConfig.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/CreateElement.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/InputTextProperties.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/LastOpenedEditor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/Open.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/RemoveElement.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/interception/Interception.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/interception/Interception.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/loadingprogress/GetProgress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/loadingprogress/LoadingProgress.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/loadingprogress/LoadingProgress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/modal/CreateCover.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/modal/DefaultCoverTransitCallbacks.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/modal/DefaultTransitCallbacks.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/modal/Modal.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/modal/Modal.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/modal/ModalPromise.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/modal/ModalPromise.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/moveto/MoveTo.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/moveto/MoveTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/OpenCloseTransition.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/OpenCloseTransition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/State.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/CloseMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/ConfigurationMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/DelayCallMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/OpenMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/ParticlesAlongBounds.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/ParticlesAlongBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/methods/BoundsToPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/methods/CreateEmitterConfig.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/methods/SyncToGameObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/pathfollower/PathFollower.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/pathfollower/PathFollower.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/perlingrivatywell/PerlinGrivatyWell.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/polarcoordinate/AddPolarCoordinateProperties.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/polarcoordinate/AddPolarCoordinateProperties.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/rotate/Rotate.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/rotate/Rotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/rotateto/RotateTo.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/rotateto/RotateTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/scale/PopUp.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/scale/PopUp.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Scale.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Scale.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDown.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDownDestroy.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDownDestroy.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Yoyo.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Yoyo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/shake/ShakePosition.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/shake/ShakePosition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/ship/Ship.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/ship/Ship.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/step/Step.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/step/Step.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/Edit.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/Edit.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/TextEdit.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/TextEdit.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/Close.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/CreateInputText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/LastOpenedEditor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/Open.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/TextPage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/TextPage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/GetLines.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/GetPageMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/SetContentMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/ShowMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/texttranslation/TextTranslation.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/texttranslation/TextTranslation.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/texttyping/TextTyping.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/texttyping/TextTyping.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/tintrgb/AddTintRGBProperties.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/tintrgb/AddTintRGBProperties.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/AddViewportCoordinateProperties.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/AddViewportCoordinateProperties.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/MonitorViewport.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/VPXYToXY.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/VPXYToXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/behaviortree-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bitmapzone-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/bitmapzone-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bitmapzone.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/bitmapzone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/blitter-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/blitter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board-components.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board-components.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board-logic.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board-logic.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/ObjectFactory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/Board.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/Board.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/LogicBoard.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/LogicBoard.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/LogicMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/blocker/HasBlocker.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/blocker/HasEdgeBlocker.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/boarddata/BoardData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/boarddata/SetBoardHeight.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/boarddata/SetBoardWidth.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/camera/ForEachCullTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/chess/AddChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/chess/GetAllChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/chess/GetBoard.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/chess/RemoveAllChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/chess/RemoveChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/chess/SetChessTileZ.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/chess/SwapChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/chess/UidToChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetEmptyTileXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetEmptyTileXYArrayInRange.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetRandomEmptyTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetRandomEmptyTileXYInRange.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/empty/IsEmptyTileXYZ.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/input/EmitChessEvent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/input/Input.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/input/InstallPress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/input/InstallSwipe.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/input/InstallTap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/input/OnPointerDown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/input/OnPointerMove.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/input/OnPointerUp.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/input/SetInteractive.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/input/TouchZone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/AreNeighbors.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborChessDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborTileDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborTileXYAtAngle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetTileXYAtDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/MapNeighobrs.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/ring/FilledRingToChessArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/ring/FilledRingToTileXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/ring/RingToChessArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/ring/RingToTileXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/shape/CircleToTileXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/shape/EllipseToTileXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/shape/ForEachTileXYInShape.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/shape/LineToTileXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/shape/PolygonToTileXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/shape/RectangleToTileXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/shape/ShapeToTileXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/shape/TriangleToTileXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/ChessToTileXYZ.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/Contains.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/DirectionBetween.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/ForEachTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/GetDistance.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/GetOppositeDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/GetWrapTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/IsDirectionInCone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileXYArrayToChessArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileXYToChessArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileXYZToChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileZToChessArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/transform/Fit.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/transform/Mirror.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/transform/Offset.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/transform/Rotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/AngleBetween.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/AngleSnapToDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/AngleToward.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GetBoardBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GetGridBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GetGridPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GridAlign.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/IsAngleInCone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/IsOverlappingPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYArrayToWorldXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYToWorldX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYToWorldXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYToWorldY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYSnapToGrid.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToChessArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToTileX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToTileY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/chess/ChessBank.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/chess/ChessData.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/chess/ChessData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/chess/GetChessData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/chess/GetChessUID.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/chess/GetTileDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/chess/IsChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/chess/IsUID.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/FieldOfView.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/FieldOfView.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/FindFOV.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/GetCost.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/IsInCone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/IsInLOS.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/IsPathVisible.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/LOS.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/PreTest.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/fieldofview/const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/GetBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/GetGridPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Hexagon.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Hexagon.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/quad/GetBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/quad/GetGridPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Quad.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Quad.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/utils/DirectionNormalize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/utils/RestoreOrigin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/grid/utils/SaveOrigin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetHexagonMap.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetHexagonMap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetParallelogramMap.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetParallelogramMap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetTriangleMap.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetTriangleMap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/index.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/match/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/match/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/match/Group.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/match/Match.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/match/Match.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/match/MatchAll.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/match/MatchAtDir.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/match/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/IsMiniBoardObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/MiniBoard.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/MiniBoard.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/chess/AddChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/chess/RemoveAllChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/chess/RemoveChess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/DragEnd.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/OnPointerDown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/OnPointerMove.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/OnPointerUp.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/SetDraggable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/SetInteractive.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/AlignToMainBoard.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/CanPutOnMainBoard.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/IsOverlapping.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/MainBoardReference.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/PullOutFromMainBoard.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/PutBack.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/PutOnMainBoard.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/SetMainboard.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/CanMoveToTile.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveToRandomNeighbor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveToTile.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveToward.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/CanMirror.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/CanRotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/CanRotateTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/Mirror.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/ResetChessTileXYZ.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/Rotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/RotateTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/SetOrigin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/transferfunctions/Mirror.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/transferfunctions/Offset.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/transferfunctions/Rotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/miniboard/utils/GetMinMaxTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/monopoly/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/monopoly/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/monopoly/GetCost.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/monopoly/GetNextTile.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/monopoly/GetPath.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/monopoly/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/monopoly/Monopoly.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/monopoly/Monopoly.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/monopoly/TileData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/monopoly/const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/moveto/CanMoveToTile.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/moveto/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/moveto/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/moveto/GetSneakTileZ.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/moveto/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveAway.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveCloser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveTo.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveToRandomNeighbor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveToTile.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveToward.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/FindArea.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/FindPath.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/GetCost.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/GetPath.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/PathFinder.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/PathFinder.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/TileXYToCost.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/AStarSearch.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/BinaryHeap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/GetNodePath.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/Node.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/NodeManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/pathfinder/const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/shape/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/shape/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/shape/Shape.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/shape/Shape.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/texture/CreateTileTexture.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/texture/CreateTileTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/tilemap/AddLayers.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/tilemap/CreateBoard.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/tilemap/CreateBoardFromTilemap.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/tilemap/CreateBoardFromTilemap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/types/Position.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/utils/AreTileXYArrayEqual.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/utils/AreTileXYEqual.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/utils/IsTileXYInArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/utils/IsTileXYZ.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/utils/tilexyzkey/KeyToTileXYZ.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/utils/tilexyzkey/TileXYToKey.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/board/utils/tilexyzkey/TileXYZToKey.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/boids-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/boids-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/boids.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/boids.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bounds-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bracketparser-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/bracketparser-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bracketparser.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/bracketparser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bracketparser2-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/bracketparser2-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bracketparser2.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/bracketparser2.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/buffdata-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/buffdata-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/buffdata.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/buffdata.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/buildarcadeobject-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/buildarcadeobject-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/buildarcadeobject.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/buildarcadeobject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bullet-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/bullet-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/bullet.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/bullet.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/button-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/button-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/button.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/button.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvas-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvas.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvas.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvasdata-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvasdata-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvasdata.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvasdata.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvasframemanager-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvasframemanager-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvasframemanager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvasframemanager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvasinput-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvasinput.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/canvasinput.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/carousel-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/carousel.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/charactercache-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/charactercache-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/charactercache.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/charactercache.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/checkbox-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/checkbox.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/checkbox.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/checkboxshape.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/checkboxshape.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/circlemaskimage-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/circlemaskimage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/circlemaskimage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/circularprogress-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/circularprogress.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/circularprogress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/circularprogresscanvas-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/circularprogresscanvas.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/circularprogresscanvas.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/clickoutside-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/clickoutside-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/clickoutside.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/clickoutside.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/clock-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/clock-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/clock.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/clock.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/conditionstable-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/conditionstable-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/conditionstable.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/conditionstable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/containerlite-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/containerlite.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/containerlite.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/cover-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/cover.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/cover.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/csvscenario-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/csvscenario-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/csvscenario.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/csvscenario.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/csvtoarray-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/csvtoarray-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/csvtoarray.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/csvtoarray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/csvtohashtable-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/csvtohashtable-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/csvtohashtable.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/csvtohashtable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/cursoratbound-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/cursoratbound-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/cursoratbound.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/cursoratbound.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/curve/SpiralCurve.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/curve/SpiralCurve.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/customprogress-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/customprogress.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/customprogress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/customshapes-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/customshapes.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/customshapes.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/bank/Bank.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/buff/DataManager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/buff/DataManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/buff/Extend.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/buff/Extend.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/buff/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/canvasdata/CanvasObjectToBitmap.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/canvasdata/CanvasObjectToBitmap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/canvasdata/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/canvasdata/TextureToColorMap.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/canvasdata/TextureToColorMap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/canvasdata/canvasdata/CanvasData.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/canvasdata/canvasdata/CanvasData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/canvasdata/canvasdata/CanvasToData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/canvasdata/fillcallbacks/alpha.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/canvasdata/fillcallbacks/color32.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/csvtoarray/CSVToArray.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/csvtoarray/CSVToArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/csvtohashtable/CsvToHashTable.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/csvtohashtable/CsvToHashTable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/pngappender/AppendData.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/pngappender/AppendData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/pngappender/ExtractData.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/pngappender/ExtractData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/pngappender/GetChunkEndByteIndex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/pool/ObjectPool.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/restorabledata/DataManager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/restorabledata/DataManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/ArrayMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/ContainMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/DestroyCallbackMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/SetMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/UniqueItemList.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/UniqueItemList.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/dissolvepipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/dissolvepipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/dissolvepipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/dissolvepipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/drag-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/drag-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/drag.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/drag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/dragrotate-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/dragrotate-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/dragrotate.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/dragrotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/dragspeed-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/dragspeed.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/dropdown-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/dropdown-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/dropdown.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/dropdown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/dynamictext-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/dynamictext.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/dynamictext.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/easedata-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/easedata-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/easedata.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/easedata.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/easemove-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/easemove-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/easemove.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/easemove.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/effectlayer-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/effectlayer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/eightdirection-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/eightdirection-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/eightdirection.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/eightdirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/eventpromise-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/eventpromise-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/eventpromise.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/eventpromise.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/expressionparser-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/expressionparser-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/expressionparser.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/expressionparser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/fade-in.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/fade-in.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/fade-out-destroy.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/fade-out-destroy.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/fade-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/fade-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/fade.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/fade.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/filechooser-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/filechooser.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/filechooser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/filedropzone-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/filedropzone.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/filedropzone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase-components.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase-components.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/ObjectFactory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Broadcast.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Broadcast.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/History.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/ReceiveMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Send.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/schema.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/ItemTable.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/ItemTable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/BaseUpdater.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/ColumnUpdater.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/Init.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/PageUpdater.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/RowUpdater.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/schema.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/IncValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/RemoveData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/RemoveDataOnDisconnect.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/SetData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/SetDataOnDisconnect.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/Transaction.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/UpdateData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/ChangeUserName.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Join.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Leave.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/OnlineUserList.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/OnlineUserList.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/schema.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeFilterData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeRoomName.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeRoomState.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeUserName.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/CreateRandomRoom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/CreateRoom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/GetRefMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/GetRoomList.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/GetUserList.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/HasRoom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/IsRoomOpened.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/JoinRandomRoom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/JoinRoom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/KickUser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/LeaveRoom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/RemoveRoom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/Room.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/schema.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateBroadcast.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateRoomList.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateTables.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateUserList.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/OnJoinRoom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/RoomFilterMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/ChangeUserName.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/GetRefMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/GetUserList.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/JoinRoom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/KickUser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/LeaveRoom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/SingleRoom.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/SingleRoom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/schema.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/utils/CreateBroadcast.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/utils/CreateTables.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/utils/CreateUserList.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/ItemList.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/ItemMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/Callbacks.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/UpdateAll.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/UpdateChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/UpdateOnce.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Clear.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Delete.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/DocToHeader.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Files.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Files.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Load.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/LoadHeader.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/LoadHeaders.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Save.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/schema.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Add.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/AddAliasTransaction.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/AddRandom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/GetAlias.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/GetId.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/GetRandomAlias.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/IdAlias.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/IdAlias.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Remove.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/RetryAddRandomAliasTransaction.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/schema.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/DeleteMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetQueryMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetRank.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetScore.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetTime.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/LeaderBoard.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/LeaderBoard.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/LoadMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Post.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/schema.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/GetQueryMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/Messages.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/ReceiveMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/Send.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/schema.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadCurrentPage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadFirstPage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadInRange.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadNextPage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadPreviousPage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/PageLoader.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/Delete.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/FindFirst.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/Load.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/Query.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/preload/AvailableTest.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/preload/GetDefaultUrl.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/preload/LoaderCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/firebase/preload/Preload.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/fisheyepipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/fisheyepipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/fisheyepipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/fisheyepipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/flash-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/flash-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/flash.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/flash.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/flip-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/flip-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/flip.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/flip.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/fsm-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/fsm-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/fsm.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/fsm.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/fullwindowrectangle-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/fullwindowrectangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/fullwindowrectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/fuzzy-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/fuzzy-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/fuzzy.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/fuzzy.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/BitmapText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/AppendText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/RunVerticalWrap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/RunWordWrap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetFixedSize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetFont.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetLetterSpacing.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetPadding.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetWrapConfig.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/UpdateCharacterDataManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/UpdateText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/PenManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/AddTextPens.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/RemovePenMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/SetTextPens.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/runverticalwrap/RunVerticalWrap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/runwordwrap/GetWord.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/runwordwrap/RunWordWrap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/pen/Base.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/pen/CharPen.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/pen/ImagePen.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Blitter.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Blitter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/BlitterBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/BlitterBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/Base.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/Base.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/RenderBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/RenderBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/Types.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/CanvasRender.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/ImageData.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/ImageData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/WebglRender.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/AddChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/GetChildren.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/GetLastAppendedChildren.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/PopReusedBob.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/RemoveChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/RemoveChildren.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/Resize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/SetTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/TintMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/poolmanager/PoolManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/render/CanvasRenderer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/render/Render.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/render/WebGLRenderer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/utils/AddImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/NinePatch.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/NinePatch.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/texture/DrawImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/texture/DrawTileSprite.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/AlphaMaskImage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/AlphaMaskImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Canvas.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Canvas.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/LoadImageMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/Canvas.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/Canvas.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/CanvasMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/TextureMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/render/CanvasRenderer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/render/Render.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/render/WebGLRenderer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/CircleMaskImage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/CircleMaskImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/CircularProgress.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/CircularProgress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/DrawContent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/DrawContent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/LineProgress.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/LineProgress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/DrawContent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/RoundRectangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/RoundRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/utils/DrawRoundRectangleBackground.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/Carousel.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/methods/ShowCells.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Active.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/AddChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Alpha.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Base.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Base.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ChangeOrigin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ChildState.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Children.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ContainerLite.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ContainerLite.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Depth.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/DrawBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/GetParent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Layer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Mask.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/P3Container.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Parent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Position.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/RemoveChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/RenderTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Rotation.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Scale.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ScrollFactor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Transform.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Tween.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Visible.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/mask/AddChildMask.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/mask/ChildrenMaskMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/mask/MaskChildren.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/Enter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/Exit.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/Init.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/MeshRenderTextureBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/utils/GetLocalState.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/utils/GetScale.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/utils/Traversal.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/GridTable.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/GridTable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/EachCell.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/InsertNewCells.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/IsCellVisible.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/PointToCell.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/RemoveCells.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetCellsCount.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetColumnCount.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetGridSize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetTableOX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetTableOY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/UpdateVisibleCell.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/GetCellTLX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/GetCellTLY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/HideCell.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/HideCells.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/ShowCell.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/ShowCells.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/UpdateTable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/table/Cell.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/table/Table.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/ImageBox.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/ImageBox.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/TransitionImage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/TransitionImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/CrossFadeTransition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/GridCutMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/MaskMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/SetTransitionCallbackMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/TransitionMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/container/utils/FlipMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/ClickPromise.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/FileChooser.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/FileChooser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/FileDropZone.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/FileDropZone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/FileDropZoneProperties.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/methods/DropEnableMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/methods/FilterMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputText.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputTextProperties.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/LoadFileMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/Resize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/RouteEvents.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/SetProperties.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/StopPropagationTouchEvents.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/SyncTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/LoadAPI.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/YoutubePlayer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/YoutubePlayer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/CanvasInput.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/CanvasInput.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/AddLastInsertCursor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/InjectDefaultConfig.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/RegisterCursorStyle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/RegisterFocusStyle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/SetText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/ClearCursor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/ClearSelectRange.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/CreateHiddenTextEdit.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/HiddenTextEdit.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/HiddenTextEdit.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/MoveCursor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/SelectRange.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/DynamicText.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/DynamicText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/Base.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/Base.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/Types.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/background/Background.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/background/Background.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/CharData.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/CharData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/TextStyle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/TextStyle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/command/Command.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/command/Command.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/drawer/Drawer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/drawer/Drawer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/image/ImageData.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/image/ImageData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/innerbounds/InnerBounds.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/innerbounds/InnerBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/Contains.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/GetWorldPosition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/RenderBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/RenderBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/RenderMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/space/Space.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/space/Space.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/utils/GetProperty.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AddChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendCommand.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendDrawer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendSpace.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/BackgroundMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ClearContent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateCharChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateCharChildren.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateCommandChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateDrawerChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateImageChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateSpaceChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ForEachCharChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ForEachChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ForEachRenderableChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetActiveChildren.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharChildIndex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharChildren.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharIndex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharWorldPosition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetChildren.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetLastAppendedChildren.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetNearestChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetPadding.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/InnerBoundsMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/InsertText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ModifyDefaultTextStyle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ModifyTextStyle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/MoveChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/PopChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RemoveChild.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RemoveChildren.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RemoveText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RenderContent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ResetTextStyle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RunVerticalWrap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RunWordWrap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RunWrap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetAlignMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetFixedSize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetPadding.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetTestString.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetToMinSize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetWrapConfig.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/GetFirstChildContains.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/SetChildrenInteractive.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/SetChildrenInteractiveEnable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/SetInteractive.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/BobPositionToCanvasPosition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/BobPositionToWorldPosition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/CanvasPositionToBobPosition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/GetBobCenterPosition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/GetBobWorldPosition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/GetChildrenAlign.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/OffsetChildren.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runverticalwrap/AlignLines.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runverticalwrap/RunVerticalWrap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/AlignLines.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/GetDefaultTextHeight.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/GetWord.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/RunWordWrap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/poolmanager/PoolManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/TextPlayer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/TextPlayer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/AddImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/ContentMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/PauseMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/PlayMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/ResumeMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetClickTarget.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetIgnoreNextPageInput.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetIgnoreWait.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetNextPageInput.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetTargetCamera.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/ShowPage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SpriteMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/TypingNextPage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/TypingSpeedMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/Wait.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/GameObjectManagerMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseAddGameObjectTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseCallGameObjectMethodTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseEaseGameObjectPropertyTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseRemoveAllGameObjectsTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/AddSpriteManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/OnParseChainAnimationTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/OnParsePauseAnimationTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/OnParsePlayAnimationTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/SpriteMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/ClearEvents.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/Events.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/Progress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/GetWrapCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitCameraEffect.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitClick.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitGameObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitKeyDown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitMultiple.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitMusic.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitTime.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/AddParseCallbacks.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/Parser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/PreProcessSource.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseCrossFadeBackgroundMusicTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseFadeInBackgroundMusicTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseFadeOutBackgroundMusicTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParsePauseBackgroundMusicTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParsePlayBackgroundMusicTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseSetBackgroundMusicVolumeTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseFadeInCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseFadeOutCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseFlashCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseRotateCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseScrollCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseShakeCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseZoomCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseContent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseContentOff.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseContentOn.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseNewLineTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParsePageBreakTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/custom/OnParseCustomTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/image/OnParseImageTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParseFadeInSoundEffectTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParseFadeOutSoundEffectTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParsePlaySoundEffectTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParseSetSoundEffectVolumeTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/space/OnParseSpaceTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseAlignTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseBoldTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseColorTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseFontSizeTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseItalicTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseLeftSpaceTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseOffsetXTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseOffsetYTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseRightSpaceTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseShadowColorTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseStrokeColorTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/typing/OnParseTypingSpeedTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/wait/OnParseWaitTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/FadeOutPage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Pause.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/PauseTyping.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Resume.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/ResumeTyping.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetIgnoreWait.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetSkipSoundEffect.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetSkipSpaceEnable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetSkipTypingAnimation.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SkipCurrentTypingDelay.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Start.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/TimerTypes.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/TypeWriter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Typing.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/TypingSpeedMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Wait.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/layer/layermanager/LayerManager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/layer/layermanager/LayerManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/.eslintrc.yml create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/CHANGELOG.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/LICENSE.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/README.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/package-lock.json create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/package.json create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/cubismdefaultparameterid.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/cubismframeworkconfig.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/cubismmodelsettingjson.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/effect/cubismbreath.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/effect/cubismeyeblink.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/effect/cubismpose.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/icubismallcator.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/icubismmodelsetting.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/id/cubismid.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/id/cubismidmanager.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/live2dcubismframework.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismmath.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismmatrix44.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismmodelmatrix.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismtargetpoint.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismvector2.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismviewmatrix.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmoc.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmodel.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmodeluserdata.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmodeluserdatajson.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismusermodel.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/acubismmotion.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismexpressionmotion.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotion.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotioninternal.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionjson.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionmanager.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionqueueentry.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionqueuemanager.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/physics/cubismphysics.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/physics/cubismphysicsinternal.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/physics/cubismphysicsjson.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/rendering/cubismrenderer.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/rendering/cubismrenderer_webgl.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmmap.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmrectf.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmstring.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmvector.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/utils/cubismdebug.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/utils/cubismjson.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/utils/cubismstring.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/tsconfig.json create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObject.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObjectBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObjectBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnExpressionStart.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnIdle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnMotionComplete.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnMotionStart.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/globaldata/CanvasMatrix.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/globaldata/GlobalData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/SetModel.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/SetTimeScale.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/expression/GetExpressionNames.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/expression/SetExpression.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/expression/SetRandomExpression.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/GetHitTestResult.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/HitAreaCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/HitTest.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/SetInteractive.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/lipsync/SetLipSyncValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/AutoPlayIdleMotion.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/GetMotionGroupNames.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/GetMotionNames.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/GetPlayinigMotionNames.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/IsAnyMotionPlaying.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/StartMotion.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/StopAllMotions.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/AddParameterValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/GetParameters.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/LookAt.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/LookForward.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/RegisterParameter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/ResetParameterValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/position/WorldXYToModelXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/Const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/Model.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/ViewMatrix.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/draw/Draw.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/draw/UpdateViewMatrix.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/expression/GetExpressionNames.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/expression/SetExpression.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/expression/SetRandomExpression.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/hitarea/GetDrawableBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/hitarea/HitAreaNameToDrawIndex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/hitarea/HitTest.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/GetMotionGroupNames.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/GetMotionNames.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/GetPlayinigMotionNames.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/IsAnyMotionPlaying.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/StartMotion.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/StopAllMotions.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/parameter/AddParameterValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/parameter/RegisterParameter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/parameter/ResetParameterValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/position/LocalXToModelMatrixX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/setup/Setup.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/update/Update.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/render/CanvasRenderer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/render/Render.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/render/WebGLRenderer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/index.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/CoreScriptFileCallback.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/CoreScriptFileCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/Live2dCoreScriptFile.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/Live2dCoreScriptState.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/CreateBinaryFile.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/Live2dFile.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/Live2dFileCallback.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/Live2dFileCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/LoadChildrenFiles.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/note.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/utils/Initialize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Card.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Card.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Flip.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/LayoutFaces.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Carousel.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Carousel.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/FaceNameToIndex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/GetFirstFace.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/LayoutFaces.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Roll.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Image.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Image.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/GetFaceSize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/GetIndexOffsetMap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/ImageCarousel.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/ImageCarousel.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/Roll.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/RenderTexture.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/RenderTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Sprite.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Sprite.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/CreateFaces.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/CreatePerspectiveObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/FaceContainer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/FaceContainer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/ForEachFace.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/TransformVerts.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Image.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Image.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/methods/ControlPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/methods/GetPointPosition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/methods/InitFaces.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/RenderTexture.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/RenderTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/Skew.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/SkewImage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/SkewImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Face.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Image.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Image.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/RenderTexture.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/RenderTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/utils/LocalXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Line.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Line.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/UpdateTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/NinePatch.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/NinePatch.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/utils/DrawImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/utils/DrawTileSprite.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/utils/GetStampGameObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/effectlayer/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/effectlayer/EffectLayer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/effectlayer/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/OutlineEffectLayer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/outline-frag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Checkbox.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Checkbox.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShape.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShape.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShapeCreator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShapeFactory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/CheckerAnimationMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/ShapesUpdateMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/SizeMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/StyleMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/CircularProgress.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/CircularProgress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/ShapesUpdateMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/CustomProgress.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/CustomProgress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/CustomShapes.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/CustomShapes.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/ShapesUpdateMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/FullWindowRectangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/FullWindowRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/LineProgress.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/LineProgress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/UpdateShapes.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/RoundRectangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/RoundRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/render/CanvasRenderer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/render/Render.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/render/WebGLRenderer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/BaseShapes.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/BaseShapes.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/base/BaseGeom.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/base/BaseGeom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/base/StyleMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/index.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Curve.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Curve.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Line.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Line.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Lines.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Lines.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/PathBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/PathBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Arc.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Arc.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Circle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Circle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Ellipse.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Ellipse.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/roundrectangle/RoundRectangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/roundrectangle/RoundRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/rectangle/Rectangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/rectangle/Rectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/triangle/Triangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/triangle/Triangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/render/CanvasRenderer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/render/Render.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/render/WebGLRenderer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitch.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitch.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShape.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShape.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShapeCreator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShapeFactory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/PositionMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/ShapesUpdateMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/SizeMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/StyleMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/ToggleAnimationMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Triangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Triangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/DrawCircleVerticesTriangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/DrawFitTriangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/EaseDirectionMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/ShapesUpdateMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/FillPathWebGL.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/FillStyleCanvas.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/LineStyleCanvas.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/StrokePathWebGL.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/Parser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/PropToContextStyle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/PropToTagText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/SplitText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/TagRegex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/TagTextToProp.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/Parser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/TagText.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/TagText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/FullFill.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/IsCanvasTextGameObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/Text.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/Text.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/canvastext/CanvasText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/canvastext/DrawMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/canvastext/SetInteractive.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/hitareamanager/HitAreaManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/penmanger/Pen.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/penmanger/PenManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/wraptext/WrapText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/wraptext/WrapTextLinesPool.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/TextBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/render/CanvasRenderer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/render/Render.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/render/WebGLRenderer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/MeasureText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/MeasureTextMargins.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/PropertyMap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/TextStyle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/TextStyleInterface.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videobase/CreateVideoElement.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videobase/Load.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videobase/VideoBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videocanvas/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videocanvas/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videocanvas/VideoCanvas.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videodom/Creator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videodom/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videodom/VideoDOM.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gashapon-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gashapon-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gashapon.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gashapon.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/hexagon/Height.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/hexagon/Hexagon.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/hexagon/SetPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/hexagon/Width.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/ArcTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/CubicBezierCurveTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/DuplicateLast.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/LineTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/Offset.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/AddPathMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/GraphicsMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/PathDataBuilder.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/PathSegmentMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/SavePathDataMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/TransformPointsMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/QuadraticBezierTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/RotateAround.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/Scale.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/StartAt.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/ToPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/pathdata/ToPolygon.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/quad/Quad.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/quad/SetPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/rhombus/Rhombus.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/roundrectangle/RoundRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/utils/GetPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/utils/InitPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/utils/Offset.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/geom/utils/RotateAround.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gestures-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gestures-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gestures.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gestures.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/ObjectFactory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/Graph.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/AddEdge.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetAllEdges.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetEdgeData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetEdgeLength.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetEdgesOfVertex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/IsEdge.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/IsInLoop.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/RemoveEdge.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/neighbors/AreNeighborVertices.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/neighbors/GetNeighborVertices.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/AddVertex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/AddVertices.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetAllConnectedVertices.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetAllVertices.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetOppositeVertex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetVertexData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetVerticesOfEdge.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/IsVertex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/RemoveAllVertices.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/RemoveVertex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graphitem/GetGraphItem.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graphitem/GetObjUID.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graphitem/GraphItemData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graphitem/IsUID.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graphitem/ObjBank.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/graph/graphitem/UidToObj.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/grayscalepipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/grayscalepipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/grayscalepipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/grayscalepipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gridalign-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gridalign.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gridcutimage-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gridcutimage-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gridcutimage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gridcutimage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gridtable-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/gridtable.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/gridtable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/hexagon-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/hexagon.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/hiddeninputtext-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/hiddeninputtext.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/hiddeninputtext.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/horrifipipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/horrifipipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/horrifipipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/horrifipipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/horrifipipelinebehavior.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/horrifipipelinebehavior.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/imagebox-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/imagebox.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/imagebox.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/imageuriloader-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/imageuriloader.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/imageuriloader.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/button/Button.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/button/Button.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/clickoutside/ClickOutside.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/clickoutside/ClickOutside.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/cursoratbound/CursorAtBound.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/cursoratbound/CursorAtBound.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/drag/Drag.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/drag/Drag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/dragrotate/DragRotate.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/dragrotate/DragRotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/dragspeed/DragSpeed.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/ObjectFactory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/onepointertracer/OnePointerTracer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/onepointertracer/OnePointerTracer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Pan.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Pan.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Pinch.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Pinch.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Press.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Press.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Rotate.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Rotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/SpinObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Swipe.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Swipe.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/VelocityMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Tap.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Tap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/twopointerstracer/TwoPointersTracer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/gestures/twopointerstracer/TwoPointersTracer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/intouching/InTouching.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/intouching/InTouching.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/keyshub/KeyHub.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/keyshub/KeysHub.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/mousewheelscroller/MouseWheelScroller.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/mousewheelscroller/MouseWheelScroller.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/mousewheeltoupdown/MouseWheelToUpDown.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/mousewheeltoupdown/MouseWheelToUpDown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/scroller/Scroller.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/scroller/Scroller.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/scroller/State.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/slider/Slider.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/slider/Slider.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/touchcursor/TouchCursor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/toucheventstop/TouchEventStop.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/toucheventstop/TouchEventStop.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/touchgroup/TouchGroup.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/touchstate/TouchState.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/touchstate/TouchState.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/virtualjoystick/VirtualJoyStick.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/input/virtualjoystick/VirtualJoyStick.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/inputtext-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/inputtext.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/inputtext.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/interception-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/interception-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/interception.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/interception.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/intouching-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/intouching.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/intouching.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/inversepipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/inversepipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/inversepipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/inversepipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/keyshub-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/keyshub.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/layermanager-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/layermanager-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/layermanager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/layermanager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/lifetime-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/lifetime-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/lifetime.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/lifetime.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/line-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/line.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/line.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/lineprogress-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/lineprogress.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/lineprogress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/lineprogresscanvas-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/lineprogresscanvas.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/lineprogresscanvas.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/live2d-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/live2d.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/live2d.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/loader/awaitloader/AwaitFile.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/loader/awaitloader/AwaitLoaderCallback.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/loader/awaitloader/AwaitLoaderCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/loader/imageuri/ImageURILoaderCallback.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/loader/imageuri/ImageURILoaderCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/loader/scripttag/ScriptTagLoaderCallback.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/loader/scripttag/ScriptTagLoaderCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/TestFont.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/WebFont.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/WebFontLoaderCallback.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/WebFontLoaderCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/loadingprogress-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/loadingprogress.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/localforage-files-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/localforage-files-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/localforage-files.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/localforage-files.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/localstorage-data-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/localstorage-data-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/localstorage-data.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/localstorage-data.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/achievements/achievements/Achievements.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/achievements/achievements/Achievements.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/achievements/csvachievements/Achievements.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/achievements/csvachievements/Achievements.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/achievements/ymlachievements/Achievements.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/achievements/ymlachievements/Achievements.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/LICENSE create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/ObjectFactory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/README.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/BehaviorTree.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Dump.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Load.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Traversal.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/blackboard/Base.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/blackboard/Blackboard.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/blackboard/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/constants.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Action.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/BaseNode.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Composite.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Decorator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Service.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Abort.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Error.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Failer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Runner.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Succeeder.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Wait.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/IfSelector.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/Parallel.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/RandomSelector.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/Selector.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/Sequence.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/ShuffleSelector.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/SwitchSelector.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/WeightSelector.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/AbortIf.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Bypass.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/ContinueIf.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Cooldown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/ForceFailure.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/ForceSuccess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/If.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Invert.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Limiter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Repeat.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/RepeatUntilFailure.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/RepeatUntilSuccess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/TimeLimit.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/BaseExpression.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/BooleanExpression.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/Expression.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/StringTemplateExpression.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/CreateNode.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/Handlers.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/LoadYaml.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/actions/Wait.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/IfSelector.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/Parallel.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/RandomSelector.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/Selector.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/Sequence.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/ShuffleSelector.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/SwitchSelector.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/WeightSelector.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/AbortIf.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/ContinueIf.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/Cooldown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/ForceFailure.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/ForceSuccess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/If.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/Invert.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/Repeat.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/RepeatUntilFailure.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/RepeatUntilSuccess.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/TimeLimit.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/tick/Tick.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/utils/CreateID.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser/BracketParser.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser/BracketParser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser/ParseValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser2/BracketParser.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser2/BracketParser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser2/ParseValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/BracketParser.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/BracketParser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/ParseValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/TokenExpressionMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/TagPlayer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/TagPlayer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/ContentMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/PauseMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/PlayMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/ResumeMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/SetClickTarget.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/SetSkipSoundEffect.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/SetTargetCamera.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/Wait.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/GameObjectManagerMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseAddGameObjectTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseCallGameObjectMethodTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseEaseGameObjectPropertyTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseRemoveAllGameObjectsTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/AddSpriteManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/OnParseChainAnimationTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/OnParsePauseAnimationTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/OnParsePlayAnimationTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/SpriteMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/AddTextManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/OnParseSetTextTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/OnParseTypingTextTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/TextMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/ClearEvents.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/Events.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/GetWrapCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitCameraEffect.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitClick.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitGameObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitKeyDown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitMultiple.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitMusic.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitTime.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/AddParseCallbacks.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/Parser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/PreProcessSource.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseCrossFadeBackgroundMusicTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseFadeInBackgroundMusicTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseFadeOutBackgroundMusicTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParsePauseBackgroundMusicTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParsePlayBackgroundMusicTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseSetBackgroundMusicVolumeTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseFadeInCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseFadeOutCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseFlashCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseRotateCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseScrollCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseShakeCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseZoomCameraTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/content/OnParseContent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/custom/OnParseCustomTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParseFadeInSoundEffectTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParseFadeOutSoundEffectTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParsePlaySoundEffectTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParseSetSoundEffectVolumeTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/wait/OnParseWaitTag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/conditiontable/ConditionsTable.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/conditiontable/ConditionsTable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/csvconditiontable/ConditionsTable.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/csvconditiontable/ConditionsTable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/csvconditiontable/CreateTestFunction.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/ymlconditiontable/ConditionsTable.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/ymlconditiontable/ConditionsTable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/EventBehaviorTree.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/EventSheetTrees.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/EventSheetTrees.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/TaskAction.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/TaskSequence.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/Tree.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/CustomNodeMapping.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/DataMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/RunMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/StateMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/TreeMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/ValueConvertMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/MarkedEventSheets.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/MarkedEventSheets.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/CreateTaskSequence.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetConditionExpression.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetHeadingTree.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetNodeType.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetTreeConfig.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/Marked2Tree.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/ParseNodes.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/ParseProperty.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSM.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSM.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSMBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSMBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/loopindexgenerator/LoopIndex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/loopindexgenerator/LoopIndexGenerator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/loopinticks/LoopInTicks.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/quest/quest/Quest.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/quest/quest/Quest.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/AddQuestion.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/DataMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/QuestMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/QuestionManager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/QuestionManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/parse/ParseCSV.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/parse/ParseInputData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/parse/ParseYaml.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/types.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/RunCommands.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/RunCommands.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Player.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Player.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Recorder.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Recorder.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/StepRunner.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/StepRunner.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/CSVScenario.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/CSVScenario.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/InstMem.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/BaseCmd.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/CmdHandlers.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/CustomCmd.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/ExitCmd.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/GotoCmd.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/IfCmd.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/LabelCmd.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/WaitCmd.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/managers/Managers.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/managers/Managers.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/sequence/Sequence.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/sequence/Sequence.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Player.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Player.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Recorder.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Recorder.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManagerBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManagerBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/waitevents/WaitEvents.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/logic/waitevents/WaitEvents.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/loopinticks-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/loopinticks.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/lzstring-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/lzstring-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/lzstring.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/lzstring.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/markedeventsheets-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/markedeventsheets-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/markedeventsheets.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/markedeventsheets.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/expressionparser/ExpressionParser.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/expressionparser/ExpressionParser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/expressionparser/GetProperty.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/export-parser.bat create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/export.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/grammar.jison create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/parser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/expressionparser/utils/Compile.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/expressionparser/utils/Complile.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/BuildFuzzyModule.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/BuildFuzzyModule.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/FuzzyModule.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/FuzzyModule.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/BuildFuzzyRule.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/BuildFuzzyRules.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/FuzzyRule.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyAND.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyCompositeTerm.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyFAIRLY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyOR.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyVERY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/GetVariableName.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/IsInvalidLine.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/Parse.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/export-parser.bat create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/export.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/grammar.jison create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/parser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/BuildFuzzySet.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/BuildFuzzyVariable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/BuildFuzzyVariables.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/FuzzyVariable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/GetAllFuzzySets.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/FuzzySet.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/LeftSCurveFuzzySet.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/LeftShoulderFuzzySet.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/NormalDistFuzzySet.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/RightSCurveFuzzySet.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/RightShoulderFuzzySet.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/SingletonFuzzySet.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/TriangularFuzzySet.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/gashapon/Gashapon.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/gashapon/Gashapon.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/raycaster/GetLineToPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/raycaster/GetLineToPolygon.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/raycaster/Obstacles.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/raycaster/Raycaster.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/math/raycaster/Raycaster.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/maxdelta-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/modal-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/modal-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/modal.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/modal.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/mousewheelscroller-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/mousewheelscroller-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/mousewheelscroller.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/mousewheelscroller.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/moveto-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/moveto-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/moveto.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/moveto.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/ninepatch-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/ninepatch.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/ninepatch.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/ninepatch2-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/ninepatch2.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/ninepatch2.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/objectpool-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/objectpool.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/outlineeffectlayer-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/outlineeffectlayer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/outlinepipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/outlinepipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/outlinepipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/outlinepipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/ObjectFactory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/itemtable/DeleteMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/itemtable/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/itemtable/GetItemCount.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/itemtable/GetQuery.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/itemtable/ItemTable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/itemtable/LoadMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/itemtable/LoadRandomItems.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/itemtable/Save.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/itemtable/SaveItems.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/Const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/DeleteMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetQueryMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetRank.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetScore.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetTime.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/LeaderBoard.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/LoadMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/Post.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/pageloader/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/pageloader/LoadMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/pageloader/PageLoader.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/quicklogin/QuickLogin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/utils/DataToItem.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/utils/InitialTable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/utils/SetOwnerAccessMode.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/utils/preload/LoaderCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/utils/preload/Preload.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/utils/query/Delete.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/utils/query/FindFirst.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/utils/query/Load.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/parse/utils/query/Query.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/particlesalongbounds-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/particlesalongbounds-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/particlesalongbounds.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/particlesalongbounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/pathfollower-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/pathfollower-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/pathfollower.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/pathfollower.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/perlin-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/perlin-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/perlin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/perlin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/perlingrivatywell-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/perlingrivatywell.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/persistenceeffect-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/persistenceeffect.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/perspectiveimage-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/perspectiveimage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/perspectiveimage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/pinch-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/pinch.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/pixelationpipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/pixelationpipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/pixelationpipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/pixelationpipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/pngappender-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/pngappender-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/pngappender.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/pngappender.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/polarcoordinate-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/polarcoordinate-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/polarcoordinate.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/polarcoordinate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/pool.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/popup.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/popup.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/postfxpipelinebehavior.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/quadimage-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/quadimage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/quadimage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/quest-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/quest-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/quest.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/quest.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/randomplace-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/randomplace-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/randomplace.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/randomplace.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/raycaster-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/raycaster-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/raycaster.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/raycaster.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/realtimetimers-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/realtimetimers-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/realtimetimers.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/realtimetimers.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/requestdrag.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/requestdrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/restorabledata-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/restorabledata-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/restorabledata.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/restorabledata.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/rhombus-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/rhombus.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/rotate-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/rotate-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/rotate.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/rotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/rotateto-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/rotateto-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/rotateto.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/rotateto.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/roundrectangle-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/roundrectangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/roundrectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/roundrectanglecanvas-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/roundrectanglecanvas.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/roundrectanglecanvas.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/runcommands-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/runcommands-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/runcommands.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/runcommands.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale-down-destroy.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale-down-destroy.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/CheckScaleMode.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/GetInnerViewport.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/GetOuterViewport.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/GetScaleOuterCameraParameters.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/ScaleOuter.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/ScaleOuter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/ShrinkSizeByRatio.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scaleouter-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/scaleouter-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scaleouter.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/scaleouter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scripttagloader-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scripttagloader.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/scripttagloader.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scroller-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/scroller-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/scroller.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/scroller.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/sequence-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/sequence-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/sequence.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/sequence.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/barrel/BarrelPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/barrel/BarrelPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/barrel/barrel-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/colorreplace/ColorReplacePostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/colorreplace/ColorReplacePostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/colorreplace/colorreplace-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/crossstitching/CrossStitchingPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/crossstitching/CrossStitchingPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/crossstitching/crossstitching-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/dissolve/DissolvePostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/dissolve/DissolvePostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/dissolve/dissolve-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/DropShadowPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/DropShadowPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/ShadowDrawer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/dropshadow-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/fisheye/FishEyePostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/fisheye/FishEyePostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/fisheye/fisheye-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter/GlowFilterPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter/GlowFilterPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter/glowfilter-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter2/GlowFilterPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter2/GlowFilterPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter2/glowfilter-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/grayscale/GrayScalePostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/grayscale/GrayScalePostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/grayscale/grayscale-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipelineBehavior.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipelineBehavior.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/horrifi-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/BloonConfigurationMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/CRTConfigurationMethod.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/ChromaticConfigurationMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/NoiseConfigurationMethod.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/ScanlinesConfigurationMethod.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/SetEnable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/VHSConfigurationMethod.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/VignetteConfigurationMethod.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/hsladjust/HslAdjustPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/hsladjust/HslAdjustPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/hsladjust/hslAdjust-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/inverse/InversePostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/inverse/InversePostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/inverse/inverse-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/GenerateKernels.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/KawaseBlurDrawer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/KawaseBlurFilterPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/KawaseBlurFilterPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/kawaseblurFilter-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/outline/OutlinePostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/outline/OutlinePostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/outline/outline-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/pixelation/PixelationPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/pixelation/PixelationPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/pixelation/pixelation-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/shockwave/ShockwavePostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/shockwave/ShockwavePostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/shockwave/shockwave-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/split/SplitPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/split/SplitPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/split/split-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/swirl/SwirlPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/swirl/SwirlPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/swirl/swirl-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/toonify/ToonifyPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/toonify/ToonifyPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/toonify/toonify-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/AvgRGB.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/HSLToRGB.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/HSVToRGB.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/HUEToRGB.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/IsEdge.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/RGBToHSL.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/RGBToHSV.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/drawer/Drawer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/drawer/alpha/AlphaDrawer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/drawer/alpha/alpha-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/noise/Perlin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/noise/Turbulence.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/utils/spritefxcontrol/Base.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipelineBehavior.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipelineBehavior.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shaders/warp/warp-postfxfrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shakeposition-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shakeposition-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shakeposition.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shakeposition.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shatterimage-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shatterimage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shatterimage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/ship-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/ship-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/ship.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/ship.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shockwavepipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shockwavepipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/shockwavepipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/shockwavepipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/slider-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/slider-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/slider.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/slider.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/soundfade-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/soundfade-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/soundfade.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/soundfade.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/spiralcurve-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/spiralcurve-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/spiralcurve.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/spiralcurve.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/splitpipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/splitpipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/splitpipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/splitpipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/statemanager-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/statemanager-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/statemanager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/statemanager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/step-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/step-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/step.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/step.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Clear.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/DataProcessMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Delete.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Files.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Files.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/GetKey.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Load.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/LoadHeaders.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Save.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localforage/utils/GetItems.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localforage/utils/RemoveItems.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localforage/utils/SetItems.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localstorage/data/AddCallbacks.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localstorage/data/DataManager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localstorage/data/DataManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localstorage/data/Extend.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localstorage/data/Extend.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localstorage/data/GetDefaultValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localstorage/data/Load.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localstorage/data/StorageMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/storage/localstorage/utils/StorageMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/string/lzstring/LZString.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/string/lzstring/LZString.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/string/stringtemplate/StringTemplate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/string/stringtemplate/utils/Complile.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/string/stringtemplate/utils/Render.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/string/xor/Decrypt.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/string/xor/Decrypt.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/string/xor/Encrypt.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/string/xor/Encrypt.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/stringtemplate-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/stringtemplate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/swirlpipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/swirlpipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/swirlpipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/swirlpipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/tagplayer-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/tagplayer-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/tagplayer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/tagplayer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/tagtext-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/tagtext.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/tagtext.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/tcrp-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/tcrp-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/tcrp.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/tcrp.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/textedit-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/textedit.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/textedit.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/textpage-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/textpage-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/textpage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/textpage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/textplayer-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/textplayer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/textplayer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texttranslation-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/texttranslation-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texttranslation.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/texttranslation.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texttyping-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/texttyping-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texttyping.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/texttyping.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/CanvasFrameManager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/CanvasFrameManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/AddEmptyFrame.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/AddToBitmapFont.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/Draw.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/Paste.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/charactercache/CharacterCache.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/charactercache/CharacterCache.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/BitmapTextMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/CharacterQueryMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Clear.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/CreateCharacterCollection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/CreateFrameManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/GetAllData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Load.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Unlock.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/awaytime/AwayTime.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/awaytime/AwayTime.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/clock/ArcadeStepClock.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/clock/ArcadeStepClock.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/clock/BaseClock.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/clock/BaseClock.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/clock/Clock.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/clock/Clock.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/clock/GameClock.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/lifetime/LifeTime.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/lifetime/LifeTime.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/progresses/Timeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/progresses/Timer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/progresses/TimerPool.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/realtimetimers/RealTimeTimers.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/time/realtimetimers/RealTimeTimers.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/tintrgb-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/tintrgb-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/tintrgb.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/tintrgb.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/toggleswitch-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/toggleswitch.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/toggleswitch.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/toggleswitchshape.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/toggleswitchshape.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/toonifypipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/toonifypipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/toonifypipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/toonifypipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/touchcursor-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/touchcursor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/toucheventstop-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/toucheventstop-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/toucheventstop.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/toucheventstop.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/touchhelper-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/touchstate-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/touchstate.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/touchstate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/transitionimage-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/transitionimage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/transitionimage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/triangle-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/triangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/triangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/uniqueitemlist-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/uniqueitemlist-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/uniqueitemlist.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/uniqueitemlist.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/actions/AlignConst.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/actions/AlignIn.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/actions/GlobZone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/actions/RotateObjectAround.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/actions/SortByDisplayOrder.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/BottomCenter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/BottomLeft.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/BottomRight.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/Center.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/LeftCenter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/QuickSet.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/RightCenter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/TopCenter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/TopLeft.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/TopRight.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/BottomCenter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/BottomLeft.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/BottomRight.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/LeftBottom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/LeftCenter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/LeftTop.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/RightBottom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/RightCenter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/RightTop.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/TopCenter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/TopLeft.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/TopRight.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/CenterOn.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetBottom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetCenterX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetCenterY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetLeft.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetOffsetX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetOffsetY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetRight.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetTop.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetBottom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetCenterX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetCenterY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetLeft.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetRight.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetTop.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/align/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/arcade/BuildArcadeObject.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/arcade/BuildArcadeObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/arcade/Helpers.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/array/Add.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/array/CSVToArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/array/Copy.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/array/Fill.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/array/GetRandom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/array/Remove.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/array/Shuffle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/array/SpliceOne.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/BooleanBuffer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/ByteBuffer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/FourBytesBuffer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/Uint8ArrayReader.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/Uint8ArrayWriter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/audio/howlersoundmanager/HowlerSound.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/audio/howlersoundmanager/HowlerSoundManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/audio/ogg-opus-decoder/ogg-opus-decoder.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/audio/ogg-opus-decoder/ogg-opus-decoder.min.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/SoundManager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/SoundManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/BackgroundMusic2Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/BackgroundMusicMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/SoundEffects2Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/SoundEffectsMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/bitmaptext/IsBitmapTextGameObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/bitmaptext/WrapText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/blob-util/blob-util.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/blob-util/blob-util.min.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/bounds/BoundsToPath.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/bounds/BoundsToPolygon.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/bounds/DrawBounds.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/bounds/DrawBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/bounds/GetBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/bounds/GetBoundsConfig.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/bounds/GetBoundsOfGameObjects.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/bounds/IsPointInBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/buff/Buff.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/canvas/AddPolygonPath.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/canvas/AddRoundRectanglePath.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawCircle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawHSVPalette.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawPolygon.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawRoundRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/canvas/GetStyle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/canvas/GetTextSize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/color/ColorNameToInteger.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/color/ColorStringToInteger.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/color/GetHexColorString.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/color/GetRGB.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/color/GrayScale.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/color/MixColor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/color/SetColor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/commandhub/CommandHub.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/ComponentBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/ComponentBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/SceneUpdateTickTask.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/SceneUpdateTickTask.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/TickTask.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/TickTask.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/timerticktask/Timer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/timerticktask/TimerTask.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/timerticktask/TimerTask.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/EaseValueTaskBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/EaseValueTaskBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/TweenTask.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/TweenTask.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/dat.gui/dat.gui.min.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/data/DataManagerMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/data/DataMethods.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/data/DataMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/datetime/GetTime.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ease/EaseValueMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ease/EaseValueTask.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ease/EaseValueTask.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/EventEmitter.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/EventEmitter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/EventEmitterMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/eventemitter/HasListener.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/function/Override.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddEvent.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddEvent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddSceneEvent.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddSceneEvent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddUpdateEvent.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/addevent/AddUpdateEvent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/GOManager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/GOManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/BobBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/BobBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/CallMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/DataMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/bobbase/PropertyMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/AddMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/CallMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/DataMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/DrawGameObjectsBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/FadeMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/PropertyMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/gameobject/gomanager/methods/RemoveMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Area.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Circle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Circle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Circumference.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/CircumferencePoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Clone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Contains.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/ContainsPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/ContainsRect.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/CopyFrom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Equals.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/GetBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/GetPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/GetPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Offset.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/OffsetPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/Random.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/circle/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Area.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Circumference.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/CircumferencePoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Clone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Contains.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/ContainsPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/ContainsRect.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/CopyFrom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Ellipse.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Ellipse.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Equals.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/GetBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/GetPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/GetPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Offset.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/OffsetPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/Random.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/ellipse/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/CircleToCircle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/CircleToRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetCircleToCircle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetCircleToRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetLineToCircle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetLineToRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetRectangleIntersection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetRectangleToRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetRectangleToTriangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetTriangleToCircle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetTriangleToLine.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/GetTriangleToTriangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/LineToCircle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/LineToLine.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/LineToRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/PointToLine.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/PointToLineSegment.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/RectangleToRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/RectangleToTriangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/RectangleToValues.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/TriangleToCircle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/TriangleToLine.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/TriangleToTriangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/intersects/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Angle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/BresenhamPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/CenterOn.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Clone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/CopyFrom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Equals.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Extend.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetMidPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetNearestPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetNormal.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/GetShortestDistance.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Height.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Length.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Line.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Line.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/NormalAngle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/NormalX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/NormalY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Offset.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/PerpSlope.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Random.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/ReflectAngle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Rotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/RotateAroundPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/RotateAroundXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/SetToAngle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Slope.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/Width.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/line/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Ceil.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Clone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/CopyFrom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Equals.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Floor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetCentroid.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetMagnitude.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetMagnitudeSq.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/GetRectangleFromPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Interpolate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Invert.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Negative.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Point.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Point.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/Project.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/ProjectUnit.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/SetMagnitude.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/point/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Clone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Contains.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/ContainsPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Earcut.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/GetAABB.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/GetNumberArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/GetPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Perimeter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Polygon.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Polygon.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Reverse.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/Smooth.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/polygon/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Area.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Ceil.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/CeilAll.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/CenterOn.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Clone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Contains.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/ContainsPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/ContainsRect.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/CopyFrom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Decompose.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Equals.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FitInside.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FitOutside.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Floor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FloorAll.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/FromPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetAspectRatio.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetCenter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/GetSize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Inflate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Intersection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MarchingAnts.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MergePoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MergeRect.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/MergeXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Offset.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/OffsetPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Overlaps.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Perimeter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/PerimeterPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Random.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/RandomOutside.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Rectangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Rectangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/SameDimensions.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Scale.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/Union.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/rectangle/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Area.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/BuildEquilateral.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/BuildFromPolygon.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/BuildRight.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CenterOn.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Centroid.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CircumCenter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CircumCircle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Clone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Contains.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/ContainsArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/ContainsPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/CopyFrom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Decompose.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Equals.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/GetPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/GetPoints.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/InCenter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Offset.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Perimeter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Random.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Rotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/RotateAroundPoint.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/RotateAroundXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Triangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/Triangle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/triangle/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/geom/types.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/CubeTransfer.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/DeltaTileXYToDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/DirectionBetween.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/DirectionToDeltaTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetDistance.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetNeighborTileDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetNeighborTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetOppositeDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetParity.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileXYAtDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetTileY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetWorldX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetWorldXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/GetWorldY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Hexagon.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Mirror.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Offset.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/RingToTileXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/Rotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/hexagon/const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/DeltaTileXYToDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/DirectionBetween.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/DistanceToDeltaTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetDistance.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetNeighborTileDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetNeighborTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetOppositeDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileXYAtDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetTileY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetWorldX.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetWorldXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/GetWorldY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Mirror.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Offset.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Quad.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/RingToTileXYArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/grid/quad/Rotate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/input/CursorKeys.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/input/HitTest.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/input/InputCandidate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/input/IsPointerInBounds.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/input/IsPointerInBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/input/IsPointerInHitArea.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/input/KeyMap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/input/RequestDrag.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/input/RequestDrag.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/input/SetCursorStyle.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/input/VectorToCursorKeys.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/js-interpreter/js-interpreter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/LICENSE create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/README.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/convert/dmp.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/convert/xml.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/array.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/base.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/character.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/css.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/json.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/line.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/sentence.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/diff/word.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/index.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/apply.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/create.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/merge.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/patch/parse.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/release-notes.md create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/util/array.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/util/distance-iterator.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/jsdiff/util/params.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/loader/BinaryToTextureCache.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/loader/BinaryToTextureCache.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/loader/FileObjectToCache.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadBinaryAndImage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadBinaryAndImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadScript.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/loader/LoadScriptPromise.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/lokijs/SerializeMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/lokijs/loki-indexed-adapter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/lokijs/lokijs.min.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/lzstring/lz-string.min.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/DestroyManagers.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/Extend.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/GameObjectManagerMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/GameObjectMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/GetTimeScale.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/InitManagers.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/Managers.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/Managers.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/SetTimeScale.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitAny.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitCameraMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitEventManager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitEventManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitGameObjectMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitInputMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/WaitMusicMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/managers/waiteventmanager/const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/marked/marked.min.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/mask/MaskToGameObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/mask/defaultmaskgraphics/DefaultMaskGraphics.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/mask/defaultmaskgraphics/DrawShape.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/Bernstein.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/Between.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/ByteArrayToUint32.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/CatmullRom.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/Clamp.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/DegToRad.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/Factorial.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/FromPercent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/Linear.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/MapRange.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/RadToDeg.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/RotateAround.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/RoundUpPowerOf2.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/SmoothStep.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/SmootherStep.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/Sum.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/Uint32ToByteArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/Vector2.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/Wrap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/Yoyo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/angle/Between.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/angle/Normalize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/angle/ShortestBetween.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/angle/Wrap.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/angle/angletodirections/AngleToDirections.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/angle/angletodirections/Const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/angle/angletodirections/RotationToDirection.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/color/Color32Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/color/InterpolateColor32.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/const.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/coordinate/PolarToCartesian.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/distance/DistanceBetween.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/Ceil.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/Equal.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/Floor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/GreaterThan.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/fuzzy/LessThan.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/BezierInterpolation.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/CatmullRomInterpolation.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/CubicBezierInterpolation.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/LinearInterpolation.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/QuadraticBezierInterpolation.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/SmoothStepInterpolation.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/interpolation/SmootherStepInterpolation.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/noise/Perlin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/noise/Perlin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/noise/Simplex1.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/triangulate/ShatterRectangleToTriangles.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/triangulate/Triangulate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/math/triangulate/delaunay.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/midi-parser/midi-parser.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/minmaxbounds/MinMaxBounds.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/movement/MoveTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/movement/Movement.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/movement/SlowDown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/mustache/mustache.min.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/NinePatch.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/NinePatch.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/GetStretchMode.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetBaseTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetGetFrameNameCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetMaxFixedPartScale.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetPreserveRatio.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/SetStretchMode.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/texture/UpdateTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/ninepatch/utils/IsEdge.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/AreValuesEqual.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/Class.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/Clear.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/Clone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/CopyProperty.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/DeepClone.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/DeepMerge.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/ExtractByPrefix.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/ForEachLeafNode.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/GetAdvancedValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/GetFastValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/GetPartialData.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/GetValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/GetValueFromAliasKeys.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/HasAll.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/HasAny.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/HasValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/IndexOf.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/IsArray.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/IsEmpty.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/IsFunction.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/IsKeyValueEqual.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/IsPlainObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/Merge.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/MergeRight.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/NOOP.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/RemoveItem.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/object/SetValue.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/origin/ChangeOrigin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/padding/PaddingMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/position/GameObjectLocalXYToWorldXY.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/position/GameObjectLocalXYToWorldXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/position/ScreenXYToWorldXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/position/WorldXYToGameObjectLocalXY.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/position/WorldXYToGameObjectLocalXY.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/progressbase/ProgressBase.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/progressvalue/ProgressValueMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/promise/Delay.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/promise/Delay.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/promise/DelaySceneTick.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/promise/WaitEvent.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/promise/WaitEvent.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/proxy/createproxycontext/CreateProxyContext.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/proxy/createproxycontext/CreateProxyContext.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/AddDataMonitor.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/AddDataMonitor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/AddMonitor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/EmitEvents.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/proxy/datamonitor/GetPropertyPath.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/AddPostFxPipelineInstance.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/BasePostFxPipelineBehavior.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/BasePostFxPipelineBehavior.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/GetPostFxPipelineInstance.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/RegisterPostPipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/renderer/postfxpipeline/RemovePostFxPipelineInstance.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/CreateDynamicTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/FitToViewport.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/Snapshot.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/rendertexture/Snapshot.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/size/FitTo.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/size/FitTo.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/size/GetDisplaySize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/size/ResizeGameObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/size/SetDisplaySize.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/speedmonitor/SpeedMonitor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/SpriteBob.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/SpriteManager.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/SpriteManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/methods/AnimationMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/sprite/spritemanager/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/string/EscapeRegex.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/string/GetRandomWord.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/string/GetTimeStamp.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/string/NumberToColorString.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/string/TypeConvert.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/string/UUID.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/struct/Stack.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/struct/Tree.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/struct/Tree.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/GetCache.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/GetEventEmitter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/GetGLTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/GetGame.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/GetGameObjectByName.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/GetSceneObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/GetSoundManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/GetTickDelta.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/GetViewport.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/GetViewport.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/IsCameraObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/IsEventEmitter.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/IsGame.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/IsGameObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/IsSceneObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/IsSoundObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/LogMaxDelta.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/MaxDelta.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/OS.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/system/SortGameObjectsByDepth.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/AppendText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/FullFill.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/GetTextObjectType.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/GetWrapText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/IsTextGameObject.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/SetNoWrapText.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/TextToLines.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/setfontsizetofitwidth/SetFontSizeToFitWidth.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/setfontsizetofitwidth/SetFontSizeToFitWidth.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/TextBob.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/TextManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/text/textmanager/methods/SetTextMethods.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/CopyCanvasToTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateCircleTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateDashedTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/CreatePolygonTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateRectangleTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateRoundRectangleTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/CreateTriangleTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/DrawFrameToCanvas.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/HasTexture.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/LocalXYToColor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/ToBase64.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/gridcut/GetFrameNameCallback.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/gridcut/GridCut.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/imagemanager/AddImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/imagemanager/DrawImage.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/texture/imagemanager/ImageManager.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/time/GetPeriodMS.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/time/NextTick.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/time/PostStepDelayCall.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/time/PostUpdateDelayCall.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/time/PreUpdateDelayCall.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/time/UpdateDelayCall.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/time/cooldown/Cooldown.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/tween/AutoRemoveTween.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/types/CanvasGameObjectBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/webfontloader/webfontloader.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/utils/yaml/ParseYaml.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/video-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/video.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/viewportcoordinate-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/viewportcoordinate-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/viewportcoordinate.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/viewportcoordinate.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/virtualjoystick-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/virtualjoystick-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/virtualjoystick.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/virtualjoystick.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/waitevents-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/waitevents-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/waitevents.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/waitevents.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/warppipeline-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/warppipeline-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/warppipeline.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/warppipeline.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/warppipelinebehavior.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/warppipelinebehavior.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/webfontloader-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/webfontloader.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/webfontloader.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/xor-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/xor-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/xor.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/xor.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/ymlachievements-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/ymlachievements-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/ymlachievements.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/ymlachievements.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/ymlconditionstable-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/ymlconditionstable-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/ymlconditionstable.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/ymlconditionstable.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/youtubeplayer-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/plugins/youtubeplayer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/plugins/youtubeplayer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/Bejeweled.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/Bejeweled.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/actions/EliminateChess.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/actions/FallingAllChess.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/actions/SelectChess.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/actions/SwapChess.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/board/Board.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/board/BreakMatch3.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/board/Fill.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/board/Init.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/board/PreTest.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/board/Reset.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/board/chess/CreateChess.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/board/chess/RandomSymobl.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/AnyMatch.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/GetAllMatch.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/GetMatchN.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/board/match/RefreshSymbolCache.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/input/Input.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/methods/BoardMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/methods/InputMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/methods/WaitEventMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/states/BaseState.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/states/MainState.js create mode 100644 ui/src/phaser3-rex-plugins/templates/bejeweled/states/MatchState.js create mode 100644 ui/src/phaser3-rex-plugins/templates/dialog-quest/DataMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/dialog-quest/DialogQuest.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/dialog-quest/DialogQuest.js create mode 100644 ui/src/phaser3-rex-plugins/templates/dialog-quest/QuestMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/ObjectFactory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/audio/Audio.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/audio/Audio.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/audio/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/audio/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/ball/Ball.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/ball/Ball.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/ball/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/ball/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/bars/Bars.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/bars/Bars.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/bars/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/bars/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/base/Base.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/base/Base.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/base/EaseValueMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/box/Box.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/box/Box.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/box/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/box/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/clock/Clock.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/clock/Clock.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/clock/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/clock/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/cube/Cube.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/cube/Cube.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/cube/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/cube/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/custom/Custom.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/custom/Custom.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/custom/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/custom/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/dots/Dots.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/dots/Dots.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/dots/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/dots/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/facebook/Facebook.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/facebook/Facebook.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/facebook/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/facebook/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/grid/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/grid/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/grid/Grid.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/grid/Grid.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/los/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/los/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/los/Los.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/los/Los.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/orbit/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/orbit/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/orbit/Orbit.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/orbit/Orbit.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/oval/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/oval/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/oval/Oval.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/oval/Oval.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/pie/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/pie/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/pie/Pie.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/pie/Pie.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/puff/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/puff/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/puff/Puff.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/puff/Puff.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/radio/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/radio/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/radio/Radio.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/radio/Radio.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/rings/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/rings/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/rings/Rings.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/rings/Rings.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/spinner-components.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/spinner-components.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/spinner-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/spinner-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/spinner/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/spinner/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/spinner/Spinner.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/spinner/Spinner.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/utils/Geoms.js create mode 100644 ui/src/phaser3-rex-plugins/templates/spinner/utils/Yoyo.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ObjectFactory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/AlphaMaskImage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/AlphaMaskImage.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/alphamaskimage/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/anchor/Anchor.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/anchor/Anchor.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/anchor/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/anchor/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/badgelabel/BadgeLabel.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/badgelabel/BadgeLabel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/badgelabel/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/badgelabel/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/AddChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/AddChildrenMap.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/BaseSizer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/BaseSizer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/BroadcastEvent.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/ClickMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/ClickOutsideMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/DrawBounds.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/EaseDataMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/EaseMoveMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/FadeMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetAllChildrenSizers.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildrenHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildrenSizers.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetChildrenWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetElement.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetExpandedChildHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetExpandedChildWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetParentSizerMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetShownChildrenMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/GetSizerConfig.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/HideMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/IsInTouching.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/Layout.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/LayoutBackgrounds.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/LayoutChildren.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/ModalMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/PaddingMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/PointToChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/PostLayout.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/PostResolveSize.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/PreLayout.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/PushIntoBounds.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/RemoveChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/RemoveChildrenMap.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/ResolveChildrenWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/ResolveHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/ResolveWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/RunLayout.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/RunWidthWrap.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/ScaleMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/SetAnchor.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/SetChildrenInteractive.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/SetDraggable.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/ShakeMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/TouchingMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/AddChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/CheckSize.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/ClearChildren.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/LayoutChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/PreLayoutChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/basesizer/utils/RemoveChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/BBCodeText.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/BBCodeText.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/bbcodetext/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/buttons/AddChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/buttons/Buttons.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/buttons/Buttons.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/buttons/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/buttons/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/buttons/RemoveChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/canvas/Canvas.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/canvas/Canvas.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/canvas/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/canvas/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/canvasinput/CanvasInput.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/canvasinput/CanvasInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/canvasinput/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/canvasinput/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/chart/Chart.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/chart/Chart.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/chart/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/chart/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/chart/GetChartData.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/chart/GetChartDataset.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/chart/SetChart.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/chart/SetChartData.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/chart/UpdateChart.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/checkbox/Checkbox.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/checkbox/Checkbox.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/checkbox/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/checkbox/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/CircleMaskImage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/CircleMaskImage.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/circlemaskimage/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/circularprogress/CircularProgress.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/circularprogress/CircularProgress.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/circularprogress/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/circularprogress/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/CircularProgressCanvas.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/CircularProgressCanvas.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/circularprogresscanvas/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/click/Click.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/click/Click.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/click/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/click/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/clickoutside/ClickOutside.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/clickoutside/ClickOutside.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/clickoutside/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/clickoutside/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/ColorComponents.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/ColorComponents.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorcomponents/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/ColorInput.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/ColorInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/ColorPicker.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/ConfigurationMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/CreateColorPicker.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinput/methods/OpenColorPicker.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/ColorInputBase.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/ColorInputBase.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/methods/CreateSwatch.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorinputbase/methods/SetSwatchColor.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/ColorPicker.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/ColorPicker.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/HPalette.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/HPaletteCanvas.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/SVPalette.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/SVPaletteCanvas.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/colorinput/colorpicker/methods/Transform.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/ConfirmDialog.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/ConfirmDialog.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/CreateContent.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/Modal.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/RegisterEvents.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/confirmdialog/methods/ResetDisplayContent.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/container/Container.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/container/Container.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/container/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/container/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/customprogress/CustomProgress.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/customprogress/CustomProgress.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/customprogress/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/customprogress/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/customshapes/CustomShapes.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/customshapes/CustomShapes.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/customshapes/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/customshapes/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dialog/Dialog.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dialog/Dialog.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dialog/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dialog/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dialog/methods/ButtonMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dialog/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dialog/methods/ModalMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/drag/Drag.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/drag/Drag.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/drag/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/drag/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dropdown/DropDown.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/DropDownList.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/DropDownList.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/CloseListPanel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/ConfigurationMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/CreateListPanel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/OpenListPanel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dropdownlist/methods/listpanel/ToggleListPanel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dynamictext/DynamicText.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dynamictext/DynamicText.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dynamictext/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/dynamictext/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/easemove/EaseMove.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/easemove/EaseMove.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fade/Fade.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fade/Fade.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/filechooser/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/filechooser/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/filechooser/FileChooser.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/filechooser/FileChooser.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/filedropzone/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/filedropzone/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/filedropzone/FileDropZone.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/filedropzone/FileDropZone.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/FileChooserMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/FileSelectorButton.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fileselectorbutton/FileSelectorButton.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/AddChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/FixWidthButtons.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/FixWidthButtons.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthbuttons/RemoveChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/AddChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/FixWidthSizer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/FixWidthSizer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetChildrenHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetChildrenSizers.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetChildrenWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetMaxChildHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetMaxChildWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/GetNearestChildIndex.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/LayoutChildren.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/PreLayout.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/RemoveChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/RunChildrenWrap.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fixwidthsizer/RunWidthWrap.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/flip/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/flip/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/flip/Flip.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/flip/Flip.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/folder/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/folder/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/folder/Folder.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/folder/Folder.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/folder/methods/ChildTransition.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/folder/methods/ConfigurationMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/folder/methods/ExpandMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/FullWindowRectangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/fullwindowrectangle/FullWindowRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/AddChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/GridButtons.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/GridButtons.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridbuttons/RemoveChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/AddChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetChildrenHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetChildrenSizers.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetChildrenWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetExpandedChildHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetExpandedChildWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetTotalColumnProportions.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GetTotalRowProportions.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GridSizer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/GridSizer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/InsertEmptyColumn.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/InsertEmptyRow.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/LayoutChildren.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/PreLayout.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/RemoveChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResetGrid.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResolveChildrenWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResolveHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/ResolveWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridsizer/RunWidthWrap.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/GridTable.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/GridTable.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/InjectProperties.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/ScrollMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/SetItems.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/TableOnCellVisible.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/ClickCell.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/EmitCellEvent.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/OverCell.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/PointerUpDownCell.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/PressCell.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/SwipeCell.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/TableSetInteractive.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/gridtable/input/TapCell.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/HiddenEdit.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/hiddenedit/HiddenEdit.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/holygrail/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/holygrail/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/holygrail/HolyGrail.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/holygrail/HolyGrail.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/Build.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/CreatExpandContainer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/GetAddChildConfig.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode0.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode1.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode2.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/holygrail/methods/LayoutMode3.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/imagebox/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/imagebox/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/imagebox/ImageBox.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/imagebox/ImageBox.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/inputtext/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/inputtext/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/inputtext/InputText.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/inputtext/InputText.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/intouching/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/intouching/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/intouching/InTouching.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/intouching/InTouching.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/knob/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/knob/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/knob/Knob.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/knob/Knob.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/knob/TextObjectMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/knob/input/IsLocalPointInKnob.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/knob/input/OnPanPad.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/knob/input/OnTouchPad.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/label/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/label/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/label/Label.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/label/Label.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/label/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/label/methods/ResetDisplayContent.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/lineprogress/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/lineprogress/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/lineprogress/LineProgress.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/lineprogress/LineProgress.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/LineProgressCanvas.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/lineprogresscanvas/LineProgressCanvas.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/Make.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/Make.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/Maker.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/Maker.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/YAMLMake.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/YAMLMake.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/Builders.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/Builders.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateBBCodeText.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateBadgeLabel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateButtons.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateCanvas.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateCircleMaskImage.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateDialog.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateFixWidthButtons.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateFixWidthSizer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateGridButtons.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateGridSizer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateHolyGrail.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateImage.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateKnob.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateLabel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateMenu.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateNinePatch.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateNinePatch2.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateNumberBar.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateOverlapSizer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreatePages.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateRoundRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateScrollBar.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateScrollablePanel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateSizer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateSlider.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateSpace.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateSprite.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateText.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateTextArea.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateTextBox.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateToast.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/CreateVideo.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/utils/CreateAnyImage.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/utils/CreateAnyLabel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/utils/CreateAnySizer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/utils/CreateChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/utils/CreateChildren.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/utils/GetTypeName.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/utils/MergeStyle.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/utils/ReplaceChildrenConfig.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/utils/ReplaceSliderConfig.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/builders/utils/SetTextureProperties.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/index.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/index.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/BBCodeText.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/BadgeLabel.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Buttons.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Canvas.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/CircleMaskImage.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Dialog.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/FixWidthButtons.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/FixWidthSizer.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/GridButtons.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/GridSizer.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/HolyGrail.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Image.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Knob.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Label.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Menu.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/NinePatch.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/NinePatch2.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/NumberBar.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/OverlapSizer.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Pages.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/RoundRectangle.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/ScrollBar.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/ScrollablePanel.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Sizer.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Slider.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Space.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Sprite.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Text.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/TextArea.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/TextBox.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Toast.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/schemas/Video.yml create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/maker/utils/ParseYAML.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/Menu.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/Menu.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/methods/Collapse.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/methods/CollapseSubMenu.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/methods/CreateBackground.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/methods/CreateButtons.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/methods/DelayCallMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/methods/Expand.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/methods/ExpandSubMenu.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/methods/GetEaseConfig.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/methods/MenuSetInteractive.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/methods/ParseEaseConfig.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/menu/methods/SetTransitCallbackMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/modal/Modal.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/modal/Modal.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/namevaluelabel/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/namevaluelabel/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/namevaluelabel/NameValueLabel.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/namevaluelabel/NameValueLabel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/namevaluelabel/methods/Build.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/namevaluelabel/methods/SetValueMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ninepatch/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ninepatch/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ninepatch/NinePatch.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ninepatch/NinePatch.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ninepatch2/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ninepatch2/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ninepatch2/NinePatch.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ninepatch2/NinePatch.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/numberbar/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/numberbar/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/numberbar/NumberBar.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/numberbar/NumberBar.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/AddChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/GetChildrenHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/GetChildrenSizers.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/GetChildrenWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/GetExpandedChildHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/GetExpandedChildWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/LayoutChildren.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/OverlapSizer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/OverlapSizer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/overlapsizer/RemoveChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pages/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pages/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pages/Pages.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pages/Pages.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pages/methods/AddChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pages/methods/GetPage.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pages/methods/HasPage.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pages/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pages/methods/SwapPage.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pan/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pan/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pan/Pan.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pan/Pan.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/perspective/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/perspective/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/perspective/Perspective.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/perspective/Perspective.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/perspectivecard/CreatePerspectiveCardMesh.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/perspectivecard/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/perspectivecard/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/perspectivecard/PerspectiveCard.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/perspectivecard/PerspectiveCard.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/perspectivecard/PerspectiveMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pinch/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pinch/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pinch/Pinch.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/pinch/Pinch.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/press/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/press/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/press/Press.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/press/Press.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/rotate/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/rotate/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/rotate/Rotate.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/rotate/Rotate.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/roundrectangle/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/roundrectangle/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/roundrectangle/RoundRectangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/roundrectangle/RoundRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/roundrectanglecanvas/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/roundrectanglecanvas/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/roundrectanglecanvas/RoundRectangleCanvas.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/roundrectanglecanvas/RoundRectangleCanvas.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollablepanel/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollablepanel/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollablepanel/ScrollablePanel.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollablepanel/ScrollablePanel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollablepanel/scrollableblock/GetChildrenHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollablepanel/scrollableblock/GetChildrenSizers.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollablepanel/scrollableblock/GetChildrenWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollablepanel/scrollableblock/LayoutChildren.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollablepanel/scrollableblock/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollablepanel/scrollableblock/ResetChildPosition.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollablepanel/scrollableblock/ScrollableBlock.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollbar/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollbar/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollbar/ScrollBar.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/scrollbar/ScrollBar.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/shake/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/shake/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/shake/Shake.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/shake/Shake.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sides/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sides/ShowChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sides/Sides.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sides/childbehaviors/Fade.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sides/childbehaviors/Move.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sides/childbehaviors/Visible.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sides/childbehaviors/index.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sides/defaultcallbacks/FadeCallbacks.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sides/defaultcallbacks/GetDefaultCallbacks.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sides/defaultcallbacks/MoveCallbacks.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sides/defaultcallbacks/MovePanelCallbacks.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sides/defaultcallbacks/VisibleCallbacks.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/simpledropdownlist/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/simpledropdownlist/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/simpledropdownlist/SimpleDropDownList.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/simpledropdownlist/SimpleDropDownList.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/simplelabel/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/simplelabel/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/simplelabel/SimpleLabel.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/simplelabel/SimpleLabel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/AddChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/AlignMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/ExpandMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/GetChildrenHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/GetChildrenProportion.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/GetChildrenSizers.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/GetChildrenWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/GetExpandedChildHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/GetExpandedChildWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/GetNearestChildIndex.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/LayoutChildren.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/PostResolveSize.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/PreLayout.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/ProportionMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/RemoveChildMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/ResolveHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/ResolveWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/Sizer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/sizer/Sizer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/skew/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/skew/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/skew/Skew.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/skew/Skew.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/GetEndPoint.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/GetStartPoint.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/GetThumbAlignPoint.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/OnDragThumb.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/OnTouchTrack.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/PercentToPosition.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/PositionToPercent.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/Slider.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/Slider.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/UpdateIndicator.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/slider/UpdateThumb.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/space/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/space/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/space/Space.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/space/Space.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/statesroundrectangle/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/statesroundrectangle/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/statesroundrectangle/StatesRoundRectangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/statesroundrectangle/StatesRoundRectangle.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/statesroundrectangle/methods/ExtractStyle.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/statesroundrectangle/methods/SetStateMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/swipe/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/swipe/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/swipe/Swipe.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/swipe/Swipe.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabpages/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabpages/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabpages/TabPages.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabpages/TabPages.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabpages/methods/AddPage.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabpages/methods/GetPage.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabpages/methods/GetPageIndexByKey.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabpages/methods/GetPageKeyByIndex.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabpages/methods/GetTab.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabpages/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabpages/methods/RemovePageMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabpages/methods/SwapPageMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabs/ButtonMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabs/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabs/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabs/Tabs.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tabs/Tabs.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tagtext/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tagtext/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tagtext/TagText.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tagtext/TagText.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tap/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tap/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tap/Tap.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tap/Tap.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/InjectProperties.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/ScrollMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/SetTextMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/TextArea.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/TextArea.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/textblock/GetLines.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/textblock/LayoutChildren.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/textblock/LinesCountToTextHeight.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/textblock/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/textblock/PreLayout.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/textblock/ResetTextObjectPosition.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/textblock/ResizeText.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/textblock/SetText.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/textblock/TextBlock.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/textblock/TextHeightToLinesCount.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textarea/textblock/UpdateTextObject.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textbox/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textbox/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textbox/TextBox.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textbox/TextBox.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textedit/Edit.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textedit/Edit.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textedit/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textedit/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textedit/TextEdit.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textedit/TextEdit.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textpage/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textpage/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textpage/TextPage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textpage/TextPage.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textplayer/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textplayer/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textplayer/TextPlayer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/textplayer/TextPlayer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/texttyping/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/texttyping/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/texttyping/TextTyping.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/texttyping/TextTyping.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/titlelabel/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/titlelabel/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/titlelabel/TitleLabel.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/titlelabel/TitleLabel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toast/DefaultTransitCallbacks.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toast/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toast/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toast/Toast.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toast/Toast.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toggleswitch/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toggleswitch/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toggleswitch/ToggleSwitch.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toggleswitch/ToggleSwitch.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toucheventstop/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toucheventstop/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toucheventstop/TouchEventStop.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/toucheventstop/TouchEventStop.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/transitionimage/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/transitionimage/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/transitionimage/TransitionImage.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/transitionimage/TransitionImage.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/triangle/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/triangle/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/triangle/Triangle.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/triangle/Triangle.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/Factory.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/Factory.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/Tweaker.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/Tweaker.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/TweakerShell.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateBackground.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateButtons.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateButtonsInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateCheckboxInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateColorInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateFolder.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateInputField.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateInputRow.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateListInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateNumberInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateRangeInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateTab.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateTextInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateTitleLabel.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/builders/CreateToggleSwitchInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/folder/BindingTargetMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/folder/Folder.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/folder/InputRowTitleWidthMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputfield/ButtonsInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputfield/CheckboxInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputfield/ColorInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputfield/InputFieldBase.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputfield/ListInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputfield/NumberInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputfield/RangeInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputfield/TextInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputfield/ToggleSwitchInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputrow/BindingTargetMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputrow/InputRow.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputrow/MinTitleWidthMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/inputrow/MonitorTargetMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/label/FolderTitle.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/label/Title.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/tabpages/BindingTargetMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/tabpages/InputRowTitleWidthMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/tabpages/TabPages.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/utils/CreateButtons.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/utils/CreateCheckbox.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/utils/CreateSlider.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/utils/CreateToggleSwitch.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/utils/CreateTweaker.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/utils/CreateWrapButtons.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/gameobjects/utils/SetButtonsActiveState.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/methods/AddButtons.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/methods/AddFolder.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/methods/AddInput.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/methods/AddSeparator.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/methods/AddTab.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/methods/GetMaxInputRowTitleWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/methods/Methods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/methods/SetBindingTarget.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/methods/SetInputRowTitleWidth.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/utils/OptionsMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/utils/inputs/GetInputType.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/tweaker/utils/inputs/InputTypes.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ui-components.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ui-components.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ui-plugin.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/ui-plugin.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/AlignConst.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/ContainsPoint.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/CopyState.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/GetBoundsConfig.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/GetChildPrevState.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/GetGameObjectByName.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/GetOrientationMode.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/GetParentSizer.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/GetParentSizer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/GetScrollMode.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/GetSizerConfig.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/Hide.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/Hide.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/ScrollModeConst.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/WaitEvent.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/WaitEvent.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/buttongroup/AddMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/buttongroup/ButtonGroup.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/buttongroup/ButtonMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/buttongroup/ButtonStateMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/buttongroup/Buttons.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/buttongroup/ButtonsTypeMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/buttongroup/FireEvent.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/buttongroup/InjectSelectedProperty.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/buttongroup/OnButtonStateChange.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/buttongroup/RemoveMethods.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/fontsizeexpandtext/FontSizeExpandText.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/fontsizeexpandtext/FontSizeExpandText.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/scrollable/CreateScrollableSizer.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/scrollable/ResizeController.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/scrollable/Scrollable.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/scrollable/Scrollable.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/scrollable/Slider.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/scrollable/UpdateController.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/setchildreninteractive/ClickChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/setchildreninteractive/DownChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/setchildreninteractive/EmitChildEvent.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/setchildreninteractive/OverChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/setchildreninteractive/PointToChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/setchildreninteractive/PressChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/setchildreninteractive/SetChildrenInteractive.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/setchildreninteractive/SetChildrenInteractive.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/setchildreninteractive/SwipeChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/setchildreninteractive/TapChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/setchildreninteractive/UpChild.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/wrapexpandtext/BitmapTextRunWidthWrap.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/wrapexpandtext/DynamicTextRunWidthWrap.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/wrapexpandtext/TextRunWidthWrap.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/wrapexpandtext/WrapExpandText.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/utils/wrapexpandtext/WrapExpandText.js create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/yaml/yaml.d.ts create mode 100644 ui/src/phaser3-rex-plugins/templates/ui/yaml/yaml.js delete mode 100644 ui/src/scenes/message/message.ts diff --git a/ui/dist/assets/tilemaps/json/town.json b/ui/dist/assets/tilemaps/json/town.json index f12ae5176..038ddbcf7 100644 --- a/ui/dist/assets/tilemaps/json/town.json +++ b/ui/dist/assets/tilemaps/json/town.json @@ -242,7 +242,7 @@ { "columns":8, "firstgid":1, - "image":"..\/tiles\/tileset.png", + "image":"..\/..\/..\/..\/..\/..\/agentverse-phaser\/ui\/dist\/assets\/tilemaps\/tiles\/tileset.png", "imageheight":15968, "imagewidth":128, "margin":0, @@ -1474,7 +1474,7 @@ { "name":"collides", "type":"bool", - "value":false + "value":true }] }, { @@ -1537,7 +1537,7 @@ { "name":"collides", "type":"bool", - "value":false + "value":true }] }, { @@ -1546,7 +1546,7 @@ { "name":"collides", "type":"bool", - "value":false + "value":true }] }, @@ -1610,7 +1610,7 @@ { "name":"collides", "type":"bool", - "value":false + "value":true }] }, { @@ -1619,7 +1619,7 @@ { "name":"collides", "type":"bool", - "value":false + "value":true }] }, { @@ -1682,7 +1682,7 @@ { "name":"collides", "type":"bool", - "value":false + "value":true }] }, { @@ -1691,7 +1691,7 @@ { "name":"collides", "type":"bool", - "value":false + "value":true }] }, { @@ -1745,7 +1745,7 @@ { "name":"collides", "type":"bool", - "value":false + "value":true }] }, { @@ -1754,7 +1754,7 @@ { "name":"collides", "type":"bool", - "value":false + "value":true }] }, { @@ -1763,7 +1763,7 @@ { "name":"collides", "type":"bool", - "value":false + "value":true }] }, { diff --git a/ui/package-lock.json b/ui/package-lock.json index a79e5783d..b8c3b639a 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -21,6 +21,7 @@ "rollup": "^3.20.2", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-serve": "^2.0.2", + "tslib": "^2.5.0", "typescript": "^5.0.3", "vite-plugin-static-copy": "^0.15.0" } @@ -1639,12 +1640,10 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true, - "optional": true, - "peer": true + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==", + "dev": true }, "node_modules/typescript": { "version": "5.0.3", @@ -2825,12 +2824,10 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true, - "optional": true, - "peer": true + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==", + "dev": true }, "typescript": { "version": "5.0.3", diff --git a/ui/package.json b/ui/package.json index 868605769..c9c7ee281 100644 --- a/ui/package.json +++ b/ui/package.json @@ -29,7 +29,8 @@ "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-serve": "^2.0.2", "typescript": "^5.0.3", - "vite-plugin-static-copy": "^0.15.0" + "vite-plugin-static-copy": "^0.15.0", + "tslib": "^2.5.0" }, "dependencies": { "phaser3-rex-plugins": "^1.60.1" diff --git a/ui/rollup.config.dev.mjs b/ui/rollup.config.dev.mjs index c14d34c28..9f54404f8 100644 --- a/ui/rollup.config.dev.mjs +++ b/ui/rollup.config.dev.mjs @@ -39,6 +39,7 @@ export default { // Parse our .ts source files nodeResolve({ + browser: true, extensions: [ '.ts', '.tsx' ] }), @@ -71,4 +72,4 @@ export default { }) ] -}; \ No newline at end of file +}; diff --git a/ui/src/classes/event_center.ts b/ui/src/classes/event_center.ts new file mode 100644 index 000000000..ff4cbb288 --- /dev/null +++ b/ui/src/classes/event_center.ts @@ -0,0 +1,5 @@ +import { Events } from "phaser"; + +const eventsCenter = new Events.EventEmitter(); + +export default eventsCenter; diff --git a/ui/src/classes/player.ts b/ui/src/classes/player.ts index a96e0437c..01641856a 100644 --- a/ui/src/classes/player.ts +++ b/ui/src/classes/player.ts @@ -7,11 +7,9 @@ export class Player extends Actor { constructor(scene: Phaser.Scene, x: number, y: number) { super(scene, x, y, "player"); - // KEYS - this.keyW = this.scene.input.keyboard!.addKey("W"); - this.keyA = this.scene.input.keyboard!.addKey("A"); - this.keyS = this.scene.input.keyboard!.addKey("S"); - this.keyD = this.scene.input.keyboard!.addKey("D"); + + // Keys + this.initKeyboard(); // PHYSICS this.getBody().setSize(14, 20); @@ -56,26 +54,26 @@ export class Player extends Actor { this.getBody().setVelocity(0); var pressed_flag = false; - if (this.keyW?.isDown) { + if (this.keyW.enabled && this.keyW?.isDown) { this.getBody().setVelocityY(-110); this.anims.play("walk-up", true); pressed_flag = true; } - if (this.keyA?.isDown) { + if (this.keyA.enabled && this.keyA?.isDown) { // this.getBody().setOffset(48, 15); this.getBody().setVelocityX(-110); this.anims.play("walk-left", true); pressed_flag = true; } - if (this.keyS?.isDown) { + if (this.keyS.enabled && this.keyS?.isDown) { this.getBody().setVelocityY(110); this.anims.play("walk-down", true); pressed_flag = true; } - if (this.keyD?.isDown) { + if (this.keyD.enabled && this.keyD?.isDown) { this.getBody().setVelocityX(110); this.anims.play("walk-right", true); // this.getBody().setOffset(15, 15); @@ -86,4 +84,11 @@ export class Player extends Actor { this.anims.setCurrentFrame(this.anims.currentAnim!.frames[0]); } } + + public initKeyboard(): void { + this.keyW = this.scene.input.keyboard!.addKey("W"); + this.keyA = this.scene.input.keyboard!.addKey("A"); + this.keyS = this.scene.input.keyboard!.addKey("S"); + this.keyD = this.scene.input.keyboard!.addKey("D"); + } } diff --git a/ui/src/classes/textbox.ts b/ui/src/classes/textbox.ts deleted file mode 100644 index 579506c56..000000000 --- a/ui/src/classes/textbox.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { GameObjects } from "phaser"; -const TILE_SIZE = 16; - -export class TextBox extends GameObjects.Container { - backgroundColor: number; - textPadding: number; - textElement: GameObjects.Text; - - constructor({ scene, x, y, width, text }) { - super(scene, 0, 0, []); - - this.backgroundColor = 0x006363; - this.textPadding = 20; - - // Init text first, so box can adapt to its height - this._initText(width, text); - const height = this.textElement.height + this.textPadding * 2; - - this._initBox(height, width); - - // Add text after box, so its on the box - this.add(this.textElement); - - this.setPosition(x, y); - this.setSize(width, height); - - scene.add.existing(this); - } - - _initBox(height: number, width: number) { - const background = this.scene.add - .rectangle(0, 0, width, height, this.backgroundColor) - .setOrigin(0, 0); - this.add(background); - - // Init corners - const topLeft = this._addSprite(0, 0, 0); - const topRight = this._addSprite(width - TILE_SIZE, 0, 2); - const bottomLeft = this._addSprite(0, height - TILE_SIZE, 6); - const bottomRight = this._addSprite( - width - TILE_SIZE, - height - TILE_SIZE, - 8 - ); - - // Init middle parts - - // Init top middle - this._addSprite(topLeft.getRightCenter().x!, 0, 1, topRight.x - TILE_SIZE); - - // Init middle left - this._addSprite( - 0, - topLeft.getBottomCenter().y!, - 3, - null, - bottomLeft.y - TILE_SIZE - ); - - // Init middle right - this._addSprite( - topRight.x, - topRight.getBottomCenter().y!, - 5, - null, - bottomRight.y - TILE_SIZE - ); - - // Init bottom middle - this._addSprite( - bottomLeft.getRightCenter().x!, - bottomLeft.y, - 7, - bottomRight.x - TILE_SIZE - ); - } - - _addSprite( - x: number, - y: number, - frame: number, - width: number | null = null, - height: number | null = null - ) { - const sprite = this.scene.add - .sprite(x, y, "textbox", frame) - .setOrigin(0, 0); - - if (width !== null) { - sprite.displayWidth = width; - } - - if (height !== null) { - sprite.displayHeight = height; - } - - this.add(sprite); - - return sprite; - } - - _initText(width: number, text: string) { - const textWidth = width - this.textPadding * 2; - - this.textElement = this.scene.add.text( - this.textPadding, - this.textPadding, - text, - this._getTextStyle(textWidth) - ); - } - - _getTextStyle(width: number) { - return { - fontSize: 20, - lineSpacing: 10, - wordWrap: { width }, - }; - } -} diff --git a/ui/src/index.ts b/ui/src/index.ts index 719c91dad..957e46157 100644 --- a/ui/src/index.ts +++ b/ui/src/index.ts @@ -1,6 +1,7 @@ import { Game, Scale, Types, WEBGL } from "phaser"; -import { TownScene, LoadingScene, TextboxScene } from "./scenes"; +import { TownScene, LoadingScene } from "./scenes"; +import UIPlugin from "./phaser3-rex-plugins/templates/ui/ui-plugin"; declare global { interface Window { @@ -39,7 +40,19 @@ export const gameConfig: Types.Core.GameConfig = { audio: { disableWebAudio: false, }, - scene: [LoadingScene, TownScene, TextboxScene], + scene: [LoadingScene, TownScene], + dom: { + createContainer: true, + }, + plugins: { + scene: [ + { + key: "rexUI", + plugin: UIPlugin, + mapping: "rexUI", + }, + ], + }, }; window.sizeChanged = () => { diff --git a/ui/src/phaser3-rex-plugins/plugins/achievements-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/achievements-plugin.d.ts new file mode 100644 index 000000000..0f267e5ac --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/achievements-plugin.d.ts @@ -0,0 +1,6 @@ +import Achievements from './achievements'; + +export default class AchievementsPlugin extends Phaser.Plugins.BasePlugin { + add(): Achievements; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/achievements-plugin.js b/ui/src/phaser3-rex-plugins/plugins/achievements-plugin.js new file mode 100644 index 000000000..51fb3c289 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/achievements-plugin.js @@ -0,0 +1,18 @@ +import Achievements from './achievements.js' + +class AchievementsPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add() { + return new Achievements(); + } +} + +export default AchievementsPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/achievements.d.ts b/ui/src/phaser3-rex-plugins/plugins/achievements.d.ts new file mode 100644 index 000000000..d8841896b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/achievements.d.ts @@ -0,0 +1,2 @@ +import Achievements from './logic/achievements/csvachievements/Achievements'; +export default Achievements; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/achievements.js b/ui/src/phaser3-rex-plugins/plugins/achievements.js new file mode 100644 index 000000000..e586f235d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/achievements.js @@ -0,0 +1,2 @@ +import Achievements from './logic/achievements/csvachievements/Achievements.js'; +export default Achievements; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/actions/GridCutImage.d.ts b/ui/src/phaser3-rex-plugins/plugins/actions/GridCutImage.d.ts new file mode 100644 index 000000000..84a0eb289 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/actions/GridCutImage.d.ts @@ -0,0 +1,34 @@ +export default GridCutImage; + +declare namespace GridCutImage { + interface IConfig { + columns?: number, + rows?: number, + + onCreateImage?: ( + scene: Phaser.Scene, + key: Phaser.Textures.Texture, + frame: string + ) => T, + ImageClass?: T, + + originX?: number, + originY?: number, + add?: boolean, + align?: boolean, + + objectPool?: T[], + } +} + +declare function GridCutImage( + gameObject: Phaser.GameObjects.GameObject, + columns: number, + rows: number, + config?: GridCutImage.IConfig +): void; + +declare function GridCutImage( + gameObject: Phaser.GameObjects.GameObject, + config?: GridCutImage.IConfig +): void; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/actions/GridCutImage.js b/ui/src/phaser3-rex-plugins/plugins/actions/GridCutImage.js new file mode 100644 index 000000000..e06c23c18 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/actions/GridCutImage.js @@ -0,0 +1,81 @@ +import GridCut from '../utils/texture/gridcut/GridCut.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const DefaultImageClass = Phaser.GameObjects.Image; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const RotateAround = Phaser.Math.RotateAround; + +var GridCutImage = function (gameObject, columns, rows, config) { + if (IsPlainObject(columns)) { + config = columns; + columns = GetValue(config, 'columns', 1); + rows = GetValue(config, 'rows', 1); + } + + var createImageCallback = GetValue(config, 'onCreateImage'); + if (!createImageCallback) { + var ImageClass = GetValue(config, 'ImageClass', DefaultImageClass); + createImageCallback = function (scene, key, frame) { + return new ImageClass(scene, 0, 0, key, frame); + } + } + + var originX = GetValue(config, 'originX', 0.5); + var originY = GetValue(config, 'originY', 0.5); + var addToScene = GetValue(config, 'add', true); + var align = GetValue(config, 'align', addToScene); + var imageObjectPool = GetValue(config, 'objectPool', undefined); + + var scene = gameObject.scene; + var texture = gameObject.texture; + var frame = gameObject.frame; + + var result = GridCut(scene, texture, frame, columns, rows); + var getFrameNameCallback = result.getFrameNameCallback; + var scaleX = gameObject.scaleX, + scaleY = gameObject.scaleY; + var rotation = gameObject.rotation; + var topLeft = gameObject.getTopLeft(), + startX = topLeft.x, + startY = topLeft.y; + + var cellGameObjects = []; + var cellWidth = result.cellWidth * scaleX, + cellHeight = result.cellHeight * scaleY; + for (var y = 0; y < rows; y++) { + for (var x = 0; x < columns; x++) { + var cellGameObject; + + var frameName = getFrameNameCallback(x, y); + if (imageObjectPool && (imageObjectPool.length > 0)) { + cellGameObject = (imageObjectPool.pop()).setTexture(texture, frameName); + } else { + cellGameObject = createImageCallback(scene, texture, frameName); + } + + if (addToScene) { + scene.add.existing(cellGameObject); + } + + var cellTLX = startX + (cellWidth * x); + var cellTLY = startY + (cellHeight * y); + var cellX = cellTLX + (originX * cellWidth); + var cellY = cellTLY + (originY * cellHeight); + + if (align) { + cellGameObject + .setOrigin(originX, originY) + .setPosition(cellX, cellY) + .setScale(scaleX, scaleY) + .setRotation(rotation); + RotateAround(cellGameObject, startX, startY, rotation); + } + + cellGameObjects.push(cellGameObject); + } + } + + return cellGameObjects; +} + +export default GridCutImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/actions/HexagonGridAlign.js b/ui/src/phaser3-rex-plugins/plugins/actions/HexagonGridAlign.js new file mode 100644 index 000000000..f1baf19ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/actions/HexagonGridAlign.js @@ -0,0 +1,76 @@ +import HexagonGrid from '../utils/grid/hexagon/Hexagon.js'; +import GlobZone from '../utils/actions/GlobZone.js'; +import AlignIn from '../utils/align/align/in/QuickSet.js'; + +const GetFastValue = Phaser.Utils.Objects.GetFastValue; + +var globHexagonGrid = new HexagonGrid(); + +/** + * @typedef {object} GridAlignConfig + * + * @property {integer} [width=-1] - The width of the grid in items (not pixels). -1 means lay all items out horizontally, regardless of quantity. + * If both this value and height are set to -1 then this value overrides it and the `height` value is ignored. + * @property {integer} [height=-1] - The height of the grid in items (not pixels). -1 means lay all items out vertically, regardless of quantity. + * If both this value and `width` are set to -1 then `width` overrides it and this value is ignored. + * @property {integer} [cellWidth=1] - The width of the cell, in pixels, in which the item is positioned. + * @property {integer} [cellHeight=1] - The height of the cell, in pixels, in which the item is positioned. + * @property {integer} [position=6] - The alignment position. One of the Phaser.Display.Align consts such as `TOP_LEFT` or `RIGHT_CENTER`. + * @property {number} [x=0] - Optionally place the top-left of the final grid at this coordinate. + * @property {number} [y=0] - Optionally place the top-left of the final grid at this coordinate. + */ + +var GridAlign = function (items, options) { + if (options === undefined) { + options = {}; + } + + var width = GetFastValue(options, 'width', -1); + var height = GetFastValue(options, 'height', -1); + var cellWidth = GetFastValue(options, 'cellWidth', 1); + var cellHeight = GetFastValue(options, 'cellHeight', cellWidth); + var staggeraxis = GetFastValue(options, 'staggeraxis', 1); + var staggerindex = GetFastValue(options, 'staggerindex', 1); + var position = GetFastValue(options, 'position', Phaser.Display.Align.CENTER); + var x = GetFastValue(options, 'x', 0); + var y = GetFastValue(options, 'y', 0); + + globHexagonGrid + .setOriginPosition(x, y) + .setCellSize(cellWidth, cellHeight) + .setType(staggeraxis, staggerindex); + + GlobZone.setSize(cellWidth, cellHeight); + + var lastRowIdx = height - 1, + lastColIdx = width - 1, + rowIdx = 0, + colIdx = 0; + + for (var i = 0, cnt = items.length; i < cnt; i++) { + globHexagonGrid.getWorldXY(colIdx, rowIdx, GlobZone); + AlignIn(items[i], GlobZone, position); + + if (width === -1) { + rowIdx++; + } else if (height === -1) { + colIdx++; + } else { + if (colIdx === lastColIdx) { + if (rowIdx === lastRowIdx) { + break; + } else { + colIdx = 0; + rowIdx++; + } + } else { + colIdx++; + } + } + } + + return items; +}; + + +export default GridAlign; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/actions/QuadGridAlign.js b/ui/src/phaser3-rex-plugins/plugins/actions/QuadGridAlign.js new file mode 100644 index 000000000..fd63e07a4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/actions/QuadGridAlign.js @@ -0,0 +1,75 @@ +import QuadGrid from '../utils/grid/quad/Quad.js'; +import GlobZone from '../utils/actions/GlobZone.js'; +import AlignIn from '../utils/align/align/in/QuickSet.js'; + +const GetFastValue = Phaser.Utils.Objects.GetFastValue; + +var globQuadGrid = new QuadGrid(); + +/** + * @typedef {object} GridAlignConfig + * + * @property {integer} [width=-1] - The width of the grid in items (not pixels). -1 means lay all items out horizontally, regardless of quantity. + * If both this value and height are set to -1 then this value overrides it and the `height` value is ignored. + * @property {integer} [height=-1] - The height of the grid in items (not pixels). -1 means lay all items out vertically, regardless of quantity. + * If both this value and `width` are set to -1 then `width` overrides it and this value is ignored. + * @property {integer} [cellWidth=1] - The width of the cell, in pixels, in which the item is positioned. + * @property {integer} [cellHeight=1] - The height of the cell, in pixels, in which the item is positioned. + * @property {integer} [position=6] - The alignment position. One of the Phaser.Display.Align consts such as `TOP_LEFT` or `RIGHT_CENTER`. + * @property {number} [x=0] - Optionally place the top-left of the final grid at this coordinate. + * @property {number} [y=0] - Optionally place the top-left of the final grid at this coordinate. + */ + +var GridAlign = function (items, options) { + if (options === undefined) { + options = {}; + } + + var width = GetFastValue(options, 'width', -1); + var height = GetFastValue(options, 'height', -1); + var cellWidth = GetFastValue(options, 'cellWidth', 1); + var cellHeight = GetFastValue(options, 'cellHeight', cellWidth); + var type = GetFastValue(options, 'type', 0); + var position = GetFastValue(options, 'position', Phaser.Display.Align.CENTER); + var x = GetFastValue(options, 'x', 0); + var y = GetFastValue(options, 'y', 0); + + globQuadGrid + .setOriginPosition(x, y) + .setCellSize(cellWidth, cellHeight) + .setType(type); + + GlobZone.setSize(cellWidth, cellHeight); + + var lastRowIdx = height - 1, + lastColIdx = width - 1, + rowIdx = 0, + colIdx = 0; + + for (var i = 0, cnt = items.length; i < cnt; i++) { + globQuadGrid.getWorldXY(colIdx, rowIdx, GlobZone); + AlignIn(items[i], GlobZone, position); + + if (width === -1) { + rowIdx++; + } else if (height === -1) { + colIdx++; + } else { + if (colIdx === lastColIdx) { + if (rowIdx === lastRowIdx) { + break; + } else { + colIdx = 0; + rowIdx++; + } + } else { + colIdx++; + } + } + } + + return items; +}; + + +export default GridAlign; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/actions/RandomPlace.d.ts b/ui/src/phaser3-rex-plugins/plugins/actions/RandomPlace.d.ts new file mode 100644 index 000000000..1bdb91e9f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/actions/RandomPlace.d.ts @@ -0,0 +1,29 @@ +export default RandomPlace; + +declare namespace RandomPlace { + + type Vec2Type = { x: number, y: number }; + type GetPositionCallback = (out?: Vec2Type) => Vec2Type + + type AreaType = { + getRandomPoint: GetPositionCallback + } + + interface IConfig { + radius?: number, + getPositionCallback?: GetPositionCallback + area?: AreaType, + } +} + +declare function RandomPlace( + gameObjects: Phaser.GameObjects.GameObject, + config: RandomPlace.IConfig +): Phaser.GameObjects.GameObject; + +declare function RandomPlace( + config: { + gameObjects: Phaser.GameObjects.GameObject, + radius?: number, + } +): Phaser.GameObjects.GameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/actions/RandomPlace.js b/ui/src/phaser3-rex-plugins/plugins/actions/RandomPlace.js new file mode 100644 index 000000000..09ae313a3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/actions/RandomPlace.js @@ -0,0 +1,69 @@ +import GetViewport from '../utils/system/GetViewport.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const Circle = Phaser.Geom.Circle; +const CircleToCircle = Phaser.Geom.Intersects.CircleToCircle; + +var RandomPlace = function (items, options) { + if (items.length === 0) { + return items; + } + + var getPositionCallback = GetValue(options, 'getPositionCallback', undefined); + if (getPositionCallback === undefined) { + var area = GetValue(options, 'area', undefined); + if (area === undefined) { + var item0 = items[0], gameObject; + if (IsPlainObject(item0)) { + gameObject = item0.gameObject; + } else { + gameObject = item0; + } + area = GetViewport(gameObject.scene); + } + getPositionCallback = area.getRandomPoint.bind(area); + } + var defaultRadius = GetValue(options, 'radius', 0); + + var item, gameObject, radius; + var collisionCircles = []; + for (var i = 0, cnt = items.length; i < cnt; i++) { + item = items[i]; + if (IsPlainObject(item)) { + gameObject = GetValue(item, 'gameObject', undefined); + radius = GetValue(item, 'radius', defaultRadius); + } else { + gameObject = item; + radius = defaultRadius; + } + + if (!gameObject) { + continue; + } + + if (radius <= 0) { + getPositionCallback(gameObject); + } else { + var circle = new Circle(0, 0, radius); + var isOverlapping; + do { + getPositionCallback(circle); + isOverlapping = false; + for (var ci = 0, ccnt = collisionCircles.length; ci < ccnt; ci++) { + isOverlapping = CircleToCircle(circle, collisionCircles[ci]); + if (isOverlapping) { + break; + } + } + } while (isOverlapping) + + collisionCircles.push(circle); + gameObject.setPosition(circle.x, circle.y); + } + } + + return items; +} + +export default RandomPlace; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/alphamaskimage-plugin.js b/ui/src/phaser3-rex-plugins/plugins/alphamaskimage-plugin.js new file mode 100644 index 000000000..d5a43efb6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/alphamaskimage-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/canvas/alphamaskimage/Factory.js'; +import Creator from './gameobjects/canvas/alphamaskimage/Creator.js'; +import AlphaMaskImage from './gameobjects/canvas/alphamaskimage/AlphaMaskImage.js'; +import SetValue from './utils/object/SetValue.js'; + +class AlphaMaskImagePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexAlphaMaskImage', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.AlphaMaskImage', AlphaMaskImage); + +export default AlphaMaskImagePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/alphamaskimage.d.ts b/ui/src/phaser3-rex-plugins/plugins/alphamaskimage.d.ts new file mode 100644 index 000000000..8ee0790c6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/alphamaskimage.d.ts @@ -0,0 +1,2 @@ +import AlphaMaskImage from './gameobjects/canvas/alphamaskimage/AlphaMaskImage'; +export default AlphaMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/alphamaskimage.js b/ui/src/phaser3-rex-plugins/plugins/alphamaskimage.js new file mode 100644 index 000000000..2fcf16ea1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/alphamaskimage.js @@ -0,0 +1,2 @@ +import AlphaMaskImage from './gameobjects/canvas/alphamaskimage/AlphaMaskImage.js'; +export default AlphaMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/anchor-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/anchor-plugin.d.ts new file mode 100644 index 000000000..5a908ac6d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/anchor-plugin.d.ts @@ -0,0 +1,9 @@ +import Anchor from './anchor' + +export default class AnchorPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Anchor.IConfig + ): Anchor; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/anchor-plugin.js b/ui/src/phaser3-rex-plugins/plugins/anchor-plugin.js new file mode 100644 index 000000000..7b722c167 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/anchor-plugin.js @@ -0,0 +1,18 @@ +import Anchor from './anchor.js' + +class AnchorPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Anchor(gameObject, config); + } +} + +export default AnchorPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/anchor.d.ts b/ui/src/phaser3-rex-plugins/plugins/anchor.d.ts new file mode 100644 index 000000000..207d995ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/anchor.d.ts @@ -0,0 +1,2 @@ +import Anchor from './behaviors/anchor/Anchor'; +export default Anchor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/anchor.js b/ui/src/phaser3-rex-plugins/plugins/anchor.js new file mode 100644 index 000000000..6800a82ad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/anchor.js @@ -0,0 +1,2 @@ +import Anchor from './behaviors/anchor/Anchor.js'; +export default Anchor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/arcadestepclock-plugin.js b/ui/src/phaser3-rex-plugins/plugins/arcadestepclock-plugin.js new file mode 100644 index 000000000..97523802e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/arcadestepclock-plugin.js @@ -0,0 +1,20 @@ +import ArcadeStepClock from './ArcadeStepClock.js'; + +class ArcadeStepClockPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new ArcadeStepClock(scene, config); + } + +} + +export default ArcadeStepClockPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/arcadestepclock.js b/ui/src/phaser3-rex-plugins/plugins/arcadestepclock.js new file mode 100644 index 000000000..e882542ce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/arcadestepclock.js @@ -0,0 +1,2 @@ +import ArcadeStepClock from './time/clock/ArcadeStepClock.js'; +export default ArcadeStepClock; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/arcadetcrp-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/arcadetcrp-plugin.d.ts new file mode 100644 index 000000000..ebf6b5fc3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/arcadetcrp-plugin.d.ts @@ -0,0 +1,23 @@ +import Recorder from './logic/runcommands/arcadetcrp/Recorder'; +import Player from './logic/runcommands/arcadetcrp/Player'; +import StepRunner from './logic/runcommands/arcadetcrp/StepRunner' +import RunCommands from './logic/runcommands/RunCommands'; + + +export default class TCRPPlugin extends Phaser.Plugins.BasePlugin { + addRecorder( + parent: Phaser.Scene | Phaser.GameObjects.GameObject, + config?: Recorder.IConfig + ): Recorder; + + addPlayer( + parent: Phaser.Scene | Phaser.GameObjects.GameObject, + config?: Player.IConfig + ): Player + + addStepRunner( + parent: Phaser.Scene | Phaser.GameObjects.GameObject, + ): StepRunner; + + runCommands: typeof RunCommands; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/arcadetcrp-plugin.js b/ui/src/phaser3-rex-plugins/plugins/arcadetcrp-plugin.js new file mode 100644 index 000000000..31aeaa424 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/arcadetcrp-plugin.js @@ -0,0 +1,39 @@ +import TCRP from './arcadetcrp.js'; + +const Recorder = TCRP.Recorder; +const Player = TCRP.Player; +const StepRunner = TCRP.StepRunner; + +class ArcadeTCRPPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + addRecorder(parent, config) { + return new Recorder(parent, config); + } + + addPlayer(parent, config) { + return new Player(parent, config); + } + + addStepRunner(parent) { + return new StepRunner(parent); + } +} + +var methods = { + runCommands: TCRP.RunCommands +} + +Object.assign( + ArcadeTCRPPlugin.prototype, + methods +); + +export default ArcadeTCRPPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/arcadetcrp.d.ts b/ui/src/phaser3-rex-plugins/plugins/arcadetcrp.d.ts new file mode 100644 index 000000000..ba2d30434 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/arcadetcrp.d.ts @@ -0,0 +1,11 @@ +import Recorder from './logic/runcommands/arcadetcrp/Recorder'; +import Player from './logic/runcommands/arcadetcrp/Player'; +import StepRunner from './logic/runcommands/arcadetcrp/StepRunner'; +import RunCommands from './logic/runcommands/RunCommands'; + +declare var ArcadeTCRP: { + Recorder: typeof Recorder, + Player: typeof Player, + StepRunner: typeof StepRunner, + RunCommands: typeof RunCommands, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/arcadetcrp.js b/ui/src/phaser3-rex-plugins/plugins/arcadetcrp.js new file mode 100644 index 000000000..18247f63b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/arcadetcrp.js @@ -0,0 +1,11 @@ +import Recorder from './logic/runcommands/arcadetcrp/Recorder.js'; +import Player from './logic/runcommands/arcadetcrp/Player.js'; +import StepRunner from './logic/runcommands/arcadetcrp/StepRunner.js'; +import RunCommands from './logic/runcommands/RunCommands.js'; + +export default { + Recorder: Recorder, + Player: Player, + StepRunner: StepRunner, + RunCommands: RunCommands +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/audio/fade/Fade.js b/ui/src/phaser3-rex-plugins/plugins/audio/fade/Fade.js new file mode 100644 index 000000000..881416183 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/audio/fade/Fade.js @@ -0,0 +1,93 @@ +import EaseValueTaskBase from '../../utils/componentbase/tweentask/EaseValueTaskBase.js'; +import IsSoundObject from '../../utils/system/IsSoundObject.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const Linear = Phaser.Math.Linear; + +class Fade extends EaseValueTaskBase { + constructor(scene, sound, config) { + if (IsSoundObject(scene)) { + config = sound; + sound = scene; + scene = undefined; + } + + sound.active = true; + sound.scene = scene; + sound.game = sound.manager.game; + + super(sound, config); + // this.parent = parent + // this.timer + + this.volume = {}; + this.resetFromJSON(config); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + this.setMode(GetValue(o, 'mode', 0)); + this.setEnable(GetValue(o, 'enable', true)); + this.setVolumeRange( + GetAdvancedValue(o, 'volume.start', this.parent.volume), + GetAdvancedValue(o, 'volume.end', 0) + ); + return this; + } + + setMode(m) { + if (typeof (m) === 'string') { + m = MODE[m]; + } + this.mode = m; + return this; + } + + setVolumeRange(start, end) { + this.volume.start = start; + this.volume.end = end; + return this; + } + + start() { + if (this.timer.isRunning) { + return this; + } + + this.parent.setVolume(this.volume.start); + + this.timer + .setDelay(this.delay) + .setDuration(this.duration); + + super.start(); + return this; + } + + updateGameObject(parent, timer) { + parent.volume = Linear(this.volume.start, this.volume.end, timer.t); + } + + complete() { + super.complete(); + + switch (this.mode) { + case 1: + this.parent.stop(); + break; + case 2: + this.parent.destroy(); + break; + } + + return this; + } +} + +const MODE = { + stop: 1, + destroy: 2 +} + +export default Fade; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeIn.d.ts b/ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeIn.d.ts new file mode 100644 index 000000000..356ad1aeb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeIn.d.ts @@ -0,0 +1,14 @@ +export default function FadeIn( + sound: string | Phaser.Sound.BaseSound, + duration: number, + endVolume?: number, + startVolume?: number +): Phaser.Sound.BaseSound; + +export default function FadeIn( + scene: Phaser.Scene, + sound: string | Phaser.Sound.BaseSound, + duration: number, + endVolume?: number, + startVolume?: number +): Phaser.Sound.BaseSound; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeIn.js b/ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeIn.js new file mode 100644 index 000000000..0c3821740 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeIn.js @@ -0,0 +1,50 @@ +import Fade from './Fade.js'; +import IsSoundObject from '../../utils/system/IsSoundObject.js'; + +var FadeIn = function (scene, sound, duration, endVolume, startVolume) { + if (IsSoundObject(scene)) { + startVolume = endVolume; + endVolume = duration; + duration = sound; + sound = scene; + scene = undefined; + } + + if (endVolume === undefined) { + endVolume = 1; + } + if (startVolume === undefined) { + startVolume = 0; + } + + var config = { + mode: 0, + volume: { + start: startVolume, + end: endVolume + }, + duration: duration + } + + // create sound instance by key + if (typeof (sound) === 'string') { + sound = scene.sys.sound.add(sound); + } + + var fade; + if (sound.hasOwnProperty('_fade')) { + fade = sound._fade; + fade.stop().resetFromJSON(config); + } else { + fade = new Fade(scene, sound, config); + sound._fade = fade; + } + + fade.start(); + if (!sound.isPlaying) { + sound.setVolume(startVolume).play(); + } + return sound; +}; + +export default FadeIn; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeOut.d.ts b/ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeOut.d.ts new file mode 100644 index 000000000..451268571 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeOut.d.ts @@ -0,0 +1,12 @@ +export default function FadeOut( + sound: Phaser.Sound.BaseSound, + duration: number, + destroy?: boolean +): Phaser.Sound.BaseSound; + +export default function FadeOut( + scene: Phaser.Scene, + sound: Phaser.Sound.BaseSound, + duration: number, + destroy?: boolean +): Phaser.Sound.BaseSound; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeOut.js b/ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeOut.js new file mode 100644 index 000000000..4e83c9750 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/audio/fade/FadeOut.js @@ -0,0 +1,41 @@ +import Fade from './Fade.js'; +import IsSoundObject from '../../utils/system/IsSoundObject.js'; + +var FadeOut = function (scene, sound, duration, destroy) { + if (IsSoundObject(scene)) { + destroy = duration; + duration = sound; + sound = scene; + scene = undefined; + } + + if (destroy === undefined) { + destroy = true; + } + + var config = { + mode: ((destroy) ? 2 : 1), // 1: stop, 2: destroy + volume: { + start: sound.volume, + end: 0 + }, + duration: duration + } + + var fade; + if (sound.hasOwnProperty('_fade')) { + fade = sound._fade; + fade.stop().resetFromJSON(config); + } else { + fade = new Fade(scene, sound, config); + sound._fade = fade; + } + + fade.start(); + if (!sound.isPlaying) { + sound.play(); + } + return sound; +}; + +export default FadeOut; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/audio/midiplayer/MidiPlayer.js b/ui/src/phaser3-rex-plugins/plugins/audio/midiplayer/MidiPlayer.js new file mode 100644 index 000000000..71d5b5953 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/audio/midiplayer/MidiPlayer.js @@ -0,0 +1,93 @@ +import midiParser from '../../utils/midi-parser/midi-parser.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; + +class MidiPlayer { + constructor(config) { + this.resetFromJSON(config); + } + + /** + * Reset status by JSON object + * @param {object} o JSON object + * @returns {object} this object + */ + resetFromJSON(o) { + this.tracks = []; + this.tickPeriod = 0; + this.beatPeriod = 0; + + // -status- + this.IsPlaying = false; + this.playingTrackCnt = 0; + return this; + } + + /** + * Return status in JSON object + * @returns JSON object + */ + toJSON() { + return { + + }; + } + + destroy() { + this.shutdown(); + } + + load(arrayBuffer) { + var midi = midiParser.parse(new Uint8Array(arrayBuffer)); + if (!midi) { + return this; + } + this.clear(); + + this.setTickPeriod(midi); + + // load tracks + this.tracks.length = 0; + var tracks = midi.track; + var i, cnt = tracks.length, + t; + for (var i = 0, len = tracks.length; i < len; i++) { + t = new TrackKlass(this, i); + t.Load(tracks[i]); + this.tracks.push(t); + } + } + + clear() {}; + + setTickPeriod(midi) { + var timeDivision = midi.timeDivision; + if (typeof (timeDivision) === "number") // Pulses per quarter note + this.tickPeriod = this.beatPeriod / timeDivision; + else // Frames per second + this.tickPeriod = 1 / (timeDivision[0] * timeDivision[1]); + }; + + eachNote(callback, scope) { + + } + + play() { + + } + + stop() { + + } + + pause() { + + } + + resume() { + + } +} + +export default MidiPlayer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/audio/midiplayer/Track.js b/ui/src/phaser3-rex-plugins/plugins/audio/midiplayer/Track.js new file mode 100644 index 000000000..e69de29bb diff --git a/ui/src/phaser3-rex-plugins/plugins/awaitloader-plugin.js b/ui/src/phaser3-rex-plugins/plugins/awaitloader-plugin.js new file mode 100644 index 000000000..4ec10cc26 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/awaitloader-plugin.js @@ -0,0 +1,15 @@ +import LoaderCallback from './loader/awaitloader/AwaitLoaderCallback.js'; + +class AwaitLoaderPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + + pluginManager.registerFileType('rexAwait', LoaderCallback); + } + + addToScene(scene) { + scene.sys.load.rexAwait = LoaderCallback; + } +} + +export default AwaitLoaderPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/awaitloader.d.ts b/ui/src/phaser3-rex-plugins/plugins/awaitloader.d.ts new file mode 100644 index 000000000..039dae0b3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/awaitloader.d.ts @@ -0,0 +1,3 @@ +import LoaderCallback from './loader/awaitloader/AwaitLoaderCallback.js'; + +export default LoaderCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/awaitloader.js b/ui/src/phaser3-rex-plugins/plugins/awaitloader.js new file mode 100644 index 000000000..538b2559c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/awaitloader.js @@ -0,0 +1,5 @@ +import LoaderCallback from './loader/awaitloader/AwaitLoaderCallback.js'; + +Phaser.Loader.FileTypesManager.register('rexAwait', LoaderCallback); + +export default LoaderCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/awaytime-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/awaytime-plugin.d.ts new file mode 100644 index 000000000..87fb02767 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/awaytime-plugin.d.ts @@ -0,0 +1,11 @@ +import AwayTime from './awaytime' + +export default class AwayTimePlugin extends Phaser.Plugins.BasePlugin { + add(config?: AwayTime.IConfig): AwayTime; + + readonly awayTime: number; + + setKey(key: string): this; + setPeriod(time: number): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/awaytime-plugin.js b/ui/src/phaser3-rex-plugins/plugins/awaytime-plugin.js new file mode 100644 index 000000000..a43533be5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/awaytime-plugin.js @@ -0,0 +1,46 @@ +import AwayTime from './awaytime.js' + +class AwayTimePlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + destroy() { + if (this._defaultAwayTimer) { + this._defaultAwayTimer.destroy(); + } + super.destroy(); + } + + add(config) { + return new AwayTime(config); + } + + get defaultAwayTimer() { + if (!this._defaultAwayTimer) { + this._defaultAwayTimer = this.add(); + } + return this._defaultAwayTimer; + } + + get awayTime() { + return this.defaultAwayTimer.awayTime; + } + + setKey(key) { + this.defaultAwayTimer.setKey(key); + return this; + } + + setPeriod(time) { + this.defaultAwayTimer.setPeriod(time); + return this; + } +} + +export default AwayTimePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/awaytime.d.ts b/ui/src/phaser3-rex-plugins/plugins/awaytime.d.ts new file mode 100644 index 000000000..5921f1e15 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/awaytime.d.ts @@ -0,0 +1,2 @@ +import AwayTime from './time/awaytime/AwayTime'; +export default AwayTime; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/awaytime.js b/ui/src/phaser3-rex-plugins/plugins/awaytime.js new file mode 100644 index 000000000..3b4a99159 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/awaytime.js @@ -0,0 +1,2 @@ +import AwayTime from './time/awaytime/AwayTime.js'; +export default AwayTime; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bank-plugin.js b/ui/src/phaser3-rex-plugins/plugins/bank-plugin.js new file mode 100644 index 000000000..a656e3c02 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bank-plugin.js @@ -0,0 +1,20 @@ +import Bank from './bank.js'; + +class BankPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new Bank(config); + } + +} + +export default BankPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bank.js b/ui/src/phaser3-rex-plugins/plugins/bank.js new file mode 100644 index 000000000..7bc321a4a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bank.js @@ -0,0 +1,2 @@ +import Bank from './data/bank/Bank.js'; +export default Bank; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/barrelpipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/barrelpipeline-plugin.d.ts new file mode 100644 index 000000000..bb529fe3e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/barrelpipeline-plugin.d.ts @@ -0,0 +1,29 @@ +// import * as Phaser from 'phaser'; +import BarrelPostFxPipeline from './barrelpipeline'; + +export default BarrelPipelinePlugin; + +declare namespace BarrelPipelinePlugin { + + interface IConfig extends BarrelPostFxPipeline.IConfig { + name?: string + } + +} + +declare class BarrelPipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: BarrelPipelinePlugin.IConfig + ): BarrelPostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): BarrelPostFxPipeline | BarrelPostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/barrelpipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/barrelpipeline-plugin.js new file mode 100644 index 000000000..b882e3f44 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/barrelpipeline-plugin.js @@ -0,0 +1,14 @@ +import BarrelPostFxPipeline from './barrelpipeline'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class BarrelPipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(BarrelPostFxPipeline, 'rexBarrelPostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.BarrelPostFx', BarrelPostFxPipeline); + +export default BarrelPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/barrelpipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/barrelpipeline.d.ts new file mode 100644 index 000000000..af8116cc8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/barrelpipeline.d.ts @@ -0,0 +1,2 @@ +import BarrelPostFxPipeline from './shaders/barrel/BarrelPostFxPipeline'; +export default BarrelPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/barrelpipeline.js b/ui/src/phaser3-rex-plugins/plugins/barrelpipeline.js new file mode 100644 index 000000000..af8116cc8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/barrelpipeline.js @@ -0,0 +1,2 @@ +import BarrelPostFxPipeline from './shaders/barrel/BarrelPostFxPipeline'; +export default BarrelPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bbcodetext-plugin.js b/ui/src/phaser3-rex-plugins/plugins/bbcodetext-plugin.js new file mode 100644 index 000000000..5a24446b4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bbcodetext-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/tagtext/bbcodetext/Factory.js'; +import Creator from './gameobjects/tagtext/bbcodetext/Creator.js'; +import BBCodeText from './gameobjects/tagtext/bbcodetext/BBCodeText.js'; +import SetValue from './utils/object/SetValue.js'; + +class BBCodeTextPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexBBCodeText', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.BBCodeText', BBCodeText); + +export default BBCodeTextPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bbcodetext.d.ts b/ui/src/phaser3-rex-plugins/plugins/bbcodetext.d.ts new file mode 100644 index 000000000..6b9c63733 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bbcodetext.d.ts @@ -0,0 +1,2 @@ +import BBCodeText from './gameobjects/tagtext/bbcodetext/BBCodeText' +export default BBCodeText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bbcodetext.js b/ui/src/phaser3-rex-plugins/plugins/bbcodetext.js new file mode 100644 index 000000000..9fe534474 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bbcodetext.js @@ -0,0 +1,2 @@ +import BBCodeText from './gameobjects/tagtext/bbcodetext/BBCodeText.js' +export default BBCodeText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/anchor/Anchor.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/anchor/Anchor.d.ts new file mode 100644 index 000000000..73e8e1423 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/anchor/Anchor.d.ts @@ -0,0 +1,51 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default Anchor; + +declare namespace Anchor { + type OnResizeCallbackType = ( + width: number, + height: number, + gameObject: Phaser.GameObjects.GameObject, + anchor: Anchor + ) => void; + + type OnUpdateViewportCallbackType = ( + viewport: Phaser.Geom.Rectangle, + gameObject: Phaser.GameObjects.GameObject, + anchor: Anchor + ) => void; + + interface IConfig { + left?: string, right?: string, centerX?: string, x?: string, + top?: string, bottom?: string, centerY?: string, y?: string, + + width?: string, height?: string, + + onResizeCallback?: OnResizeCallbackType, + onResizeCallbackScope?: unknown, + + onUpdateViewportCallback?: OnUpdateViewportCallbackType, + onUpdateViewportCallbackScope?: unknown, + + enable?: boolean + } +} + +declare class Anchor extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Anchor.IConfig + ); + + resetFromJSON(config: Anchor.IConfig): this; + + setUpdateViewportCallback( + callback?: Anchor.OnUpdateViewportCallbackType, + scope?: object + ): this; + + anchor(): this; + + autoAnchor(enable?: boolean): this; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/anchor/Anchor.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/anchor/Anchor.js new file mode 100644 index 000000000..ac433ad7c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/anchor/Anchor.js @@ -0,0 +1,276 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import GetViewport from '../../utils/system/GetViewport.js'; + +class Anchor extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, { eventEmitter: false }); + // No event emitter + // this.parent = gameObject; + + this.viewport = undefined; + this.resetFromJSON(config); + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.autoAnchor(false); + + this.viewport = undefined; + this.onUpdateViewportCallback = undefined; + this.onUpdateViewportCallbackScope = undefined; + this.onResizeCallback = undefined; + this.onResizeCallbackScope = undefined; + + super.shutdown(fromScene); + } + + resetFromJSON(o) { + if (o === undefined) { + o = {}; + } + + // Position + var alignX, configX; + if (o.x !== undefined) { + alignX = null; + configX = o.x; + } else if (o.left !== undefined) { + alignX = 0; + configX = o.left; + } else if (o.right !== undefined) { + alignX = 1; + configX = o.right; + } else if (o.centerX !== undefined) { + alignX = 0.5; + configX = o.centerX; + } + + var alignY, configY; + if (o.y !== undefined) { + alignY = null; + configY = o.y; + } else if (o.top !== undefined) { + alignY = 0; + configY = o.top; + } else if (o.bottom !== undefined) { + alignY = 1; + configY = o.bottom; + } else if (o.centerY !== undefined) { + alignY = 0.5; + configY = o.centerY; + } + + var percentageX, offsetX; + if (configX !== undefined) { + configX = configX.replace('left', '0%').replace('right', '100%').replace('center', '50%').split('%'); + percentageX = parseFloat(configX[0]) / 100; + offsetX = (configX[1] === '') ? 0 : parseFloat(configX[1]); + } + var percentageY, offsetY; + if (configY !== undefined) { + configY = configY.replace('top', '0%').replace('bottom', '100%').replace('center', '50%').split('%'); + percentageY = parseFloat(configY[0]) / 100; + offsetY = (configY[1] === '') ? 0 : parseFloat(configY[1]); + } + + // Size + var configWidth = o.width; + var percentageWidth, paddingWidth; + if (configWidth !== undefined) { + configWidth = configWidth.split('%'); + percentageWidth = parseFloat(configWidth[0]) / 100; + paddingWidth = (configWidth[1] === '') ? 0 : parseFloat(configWidth[1]); + } + + var configHeight = o.height; + var percentageHeight, paddingHeight; + if (configHeight !== undefined) { + configHeight = configHeight.split('%'); + percentageHeight = parseFloat(configHeight[0]) / 100; + paddingHeight = (configHeight[1] === '') ? 0 : parseFloat(configHeight[1]); + } + + // Position + this.setAlign(alignX, alignY); + this.setPercentage(percentageX, percentageY); + this.setOffset(offsetX, offsetY); + // Size + this.setSizePercentage(percentageWidth, percentageHeight); + this.setSizePadding(paddingWidth, paddingHeight); + + var onResizeCallback = o.onResizeCallback; + var onResizeCallbackScope = o.onResizeCallbackScope; + if (onResizeCallback !== undefined) { + this.setResizeCallback(onResizeCallback, onResizeCallbackScope); + } + + var onUpdateViewportCallback = o.onUpdateViewportCallback; + var onUpdateViewportCallbackScope = o.onUpdateViewportCallbackScope; + if (onUpdateViewportCallback !== undefined) { + this.setUpdateViewportCallback(onUpdateViewportCallback, onUpdateViewportCallbackScope); + } + + this.autoAnchor(o.enable); + + return this; + } + + autoAnchor(enable) { + if (enable === undefined) { + enable = true; + } + + enable = !!enable; + if (this.autoAnchorEnable === enable) { + return this; + } + + if (enable) { + this.scene.sys.scale.on('resize', this.anchor, this); + this.anchor(); + } else { + this.scene.sys.scale.off('resize', this.anchor, this); + } + + this.autoAnchorEnable = enable; + + return this; + } + + // Position + setAlign(x, y) { + this.alignX = x; + this.alignY = y; + return this; + } + + setPercentage(x, y) { + this.percentageX = x; + this.percentageY = y; + return this; + } + + setOffset(x, y) { + this.offsetX = x; + this.offsetY = y; + return this; + } + + // Size + setSizePercentage(width, height) { + this.percentageWidth = width; + this.percentageHeight = height; + return this; + } + + setSizePadding(width, height) { + this.paddingWidth = width; + this.paddingHeight = height; + return this; + } + + setResizeCallback(callback, scope) { + this.onResizeCallback = callback; + this.onResizeCallbackScope = scope; + return this; + } + + setUpdateViewportCallback(callback, scope) { + this.onUpdateViewportCallback = callback; + this.onUpdateViewportCallbackScope = scope; + return this; + } + + anchor() { + this.updateViewport(); + this.updateSize(); + this.updatePosition(); + return this; + } + + updateSize() { + var callback = this.onResizeCallback, + scope = this.onResizeCallbackScope; + var newWidth = this.anchorWidth, + newHeight = this.anchorHeight; + if (((newWidth === undefined) && (newHeight === undefined)) || !callback) { + return; + } + + var gameObject = this.parent; + if (newWidth === undefined) { + newWidth = gameObject.width; + } + if (newHeight === undefined) { + newHeight = gameObject.height; + } + + if (scope) { + callback.call(scope, newWidth, newHeight, gameObject, this); + } else { + callback(newWidth, newHeight, gameObject, this); + } + } + + updatePosition() { + var gameObject = this.parent; + + if (this.alignX === null) { + gameObject.x = this.anchorX; + } else if (this.alignX !== undefined) { + gameObject.x = this.anchorX + (gameObject.displayWidth * (gameObject.originX - this.alignX)); + } + + if (this.alignY === null) { + gameObject.y = this.anchorY; + } else if (this.alignY !== undefined) { + gameObject.y = this.anchorY + (gameObject.displayHeight * (gameObject.originY - this.alignY)); + } + + return this; + } + + get anchorX() { + return this.viewport.x + (this.viewport.width * this.percentageX) + this.offsetX; + } + + get anchorY() { + return this.viewport.y + (this.viewport.height * this.percentageY) + this.offsetY; + } + + get anchorWidth() { + if (this.percentageWidth === undefined) { + return undefined; + } + return (this.viewport.width * this.percentageWidth) + this.paddingWidth; + } + + get anchorHeight() { + if (this.percentageHeight === undefined) { + return undefined; + } + return (this.viewport.height * this.percentageHeight) + this.paddingHeight; + } + + updateViewport() { + var camera = this.parent.scene.cameras.main; + this.viewport = GetViewport(this.scene, camera, this.viewport); + + var viewport = this.viewport; + var callback = this.onUpdateViewportCallback, + scope = this.onUpdateViewportCallbackScope; + if (callback) { + if (scope) { + callback.call(scope, viewport, this.parent, this); + } else { + callback(viewport, this.parent, this); + } + } + } +} + +export default Anchor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/bitmapzone/BitmapZone.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/bitmapzone/BitmapZone.d.ts new file mode 100644 index 000000000..d690c63b9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/bitmapzone/BitmapZone.d.ts @@ -0,0 +1,28 @@ +export default BitmapZone; + +declare namespace BitmapZone { + interface IConfig { + x?: number, y?: number, + width?: number, height?: number, + + scaleX?: number, scaleY?: number, + offsetX?: number, offsetY?: number, + } +} + +declare class BitmapZone { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: BitmapZone.IConfig + ); + + getRandomPoint: Phaser.Types.GameObjects.Particles.RandomZoneSourceCallback; + + setOffset(offsetX?: number, offsetY?: number): this; + offsetX: number; + offsetY: number; + + setScale(scaleX?: number, scaleY?: number): this; + scaleX: number; + scaleY: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/bitmapzone/BitmapZone.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/bitmapzone/BitmapZone.js new file mode 100644 index 000000000..be73cd18f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/bitmapzone/BitmapZone.js @@ -0,0 +1,87 @@ +const GetRandom = Phaser.Utils.Array.GetRandom; +const GetValue = Phaser.Utils.Objects.GetValue; + +class BitmapZone { + constructor(canvasObject, config) { + this.data = []; + this.setSource(canvasObject, config); + } + + setSource(canvasObject, config) { + var canvas = canvasObject.canvas; + + var x = GetValue(config, 'x', 0); + var y = GetValue(config, 'y', 0); + var width = GetValue(config, 'width', canvas.width - x); + var height = GetValue(config, 'height', canvas.height - y); + + var context = canvas.getContext('2d', { willReadFrequently: true }); + var imgData = context.getImageData(x, y, width, height).data; + var data = this.data; + data.length = 0; + for (var i = 0, cnt = (imgData.length / 4); i < cnt; i++) { + if (imgData[(i * 4) + 3] > 0) { + data.push(i); + } + } + + this.width = width; + this.height = height; + + var scaleX = GetValue(config, 'scaleX', canvasObject); + var scaleY = GetValue(config, 'scaleY', undefined); + this.setScale(scaleX, scaleY); + + var offsetX = GetValue(config, 'offsetX', canvasObject); + var offsetY = GetValue(config, 'offsetY', undefined); + this.setOffset(offsetX, offsetY); + + return this; + } + + setOffset(offsetX, offsetY) { + if (typeof (offsetX) !== 'number') { + var canvasObject = offsetX; + offsetX = -(canvasObject.originX * canvasObject.displayWidth); + offsetY = -(canvasObject.originY * canvasObject.displayHeight); + } + this.offsetX = offsetX; + this.offsetY = offsetY; + return this; + } + + setScale(scaleX, scaleY) { + if (typeof (scaleX) !== 'number') { + var canvasObject = scaleX; + scaleX = canvasObject.scaleX; + scaleY = canvasObject.scaleY; + } + if (scaleY === undefined) { + scaleY = scaleX; + } + this.scaleX = scaleX; + this.scaleY = scaleY; + return this; + } + + getRandomPoint(out) { + if (out === undefined) { + out = {}; + } + if (this.data.length > 0) { + var index = GetRandom(this.data); + var x = index % this.width; + var y = (index - x) / this.width; + out.x = x * this.scaleX; + out.y = y * this.scaleY; + } else { + out.x = 0; + out.y = 0; + } + out.x += this.offsetX; + out.y += this.offsetY; + return out; + } +} + +export default BitmapZone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/AddAlignmentForce.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/AddAlignmentForce.js new file mode 100644 index 000000000..7e742ef52 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/AddAlignmentForce.js @@ -0,0 +1,43 @@ +const Vector2 = Phaser.Math.Vector2; +const Distance = Phaser.Math.Distance.Between; + +var AddAlignmentForce = function (myAgent, neighbors, weight, distanceThreshold, out) { + // Steer towards average heading of neighbors + if (out === undefined) { + out = new Vector2(); + } + if (weight <= 0) { + return out; + } + if ((neighbors.length == 0) || + ((neighbors.length === 1) && (neighbors[0] === myAgent))) { + return out; + } + + var sum = 0, validNeighborsCount = 0; + var agent; + for (var i = 0, cnt = neighbors.length; i < cnt; i++) { + agent = neighbors[i]; + if (agent === myAgent) { + continue; + } + if (Distance(agent.x, agent.y, myAgent.x, myAgent.y) > distanceThreshold) { + continue; + } + + sum += agent.rotation; + validNeighborsCount++; + } + + if (validNeighborsCount === 0) { + return out; + } + var angle = sum / validNeighborsCount; + var p = weight; + out.x += (Math.cos(angle) * p); + out.y += (Math.sin(angle) * p); + + return out; +} + +export default AddAlignmentForce; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/AddCohesionForce.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/AddCohesionForce.js new file mode 100644 index 000000000..363538233 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/AddCohesionForce.js @@ -0,0 +1,54 @@ +const Vector2 = Phaser.Math.Vector2; +const Distance = Phaser.Math.Distance.Between; + +var AddCohesionForce = function (myAgent, neighbors, weight, distanceThreshold, out) { + // Steer towards average position of neighbors (long range attraction) + if (out === undefined) { + out = new Vector2(); + } + if (weight <= 0) { + return out; + } + if ((neighbors.length == 0) || + ((neighbors.length === 1) && (neighbors[0] === myAgent))) { + return out; + } + + centerPosition.reset(); + var agent, validNeighborsCount = 0; + for (var i = 0, cnt = neighbors.length; i < cnt; i++) { + agent = neighbors[i]; + if (agent === myAgent) { + continue; + } + if (Distance(agent.x, agent.y, myAgent.x, myAgent.y) > distanceThreshold) { + continue; + } + + centerPosition.add(agent); + validNeighborsCount++; + } + if (validNeighborsCount === 0) { + return out; + } + centerPosition.scale(1 / validNeighborsCount); + + var dx = centerPosition.x - myAgent.x; + var dy = centerPosition.y - myAgent.y; + var d = Math.sqrt((dx * dx) + (dy * dy)); + + var p = weight; + if (distanceThreshold !== Infinity) { + p *= (d / distanceThreshold); + } + + var angle = Math.atan2(dy, dx); + out.x += (Math.cos(angle) * p); + out.y += (Math.sin(angle) * p); + + return out; +} + +var centerPosition = new Vector2(); + +export default AddCohesionForce; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/AddSeparationForce.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/AddSeparationForce.js new file mode 100644 index 000000000..9010a350d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/AddSeparationForce.js @@ -0,0 +1,43 @@ +const Vector2 = Phaser.Math.Vector2; + +var AddSeparationForce = function (myAgent, neighbors, weight, distanceThreshold, out) { + // Steer to avoid crowding neighbors + if (out === undefined) { + out = new Vector2(); + } + if (weight <= 0) { + return out; + } + if ((neighbors.length == 0) || + ((neighbors.length === 1) && (neighbors[0] === myAgent))) { + return out; + } + + var agent; + var dx, dy, angle, d, p; + for (var i = 0, cnt = neighbors.length; i < cnt; i++) { + agent = neighbors[i]; + if (agent === myAgent) { + continue; + } + + dx = myAgent.x - agent.x; + dy = myAgent.y - agent.y; + d = Math.sqrt((dx * dx) + (dy * dy)); + if (d > distanceThreshold) { // out-of-range + continue; + } + + p = weight; + if (distanceThreshold !== Infinity) { + p *= (distanceThreshold - d) / distanceThreshold; + } + angle = Math.atan2(dy, dx); + out.x += (Math.cos(angle) * p); + out.y += (Math.sin(angle) * p); + } + + return out; +} + +export default AddSeparationForce; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/Boids.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/Boids.d.ts new file mode 100644 index 000000000..b61b5e6ce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/Boids.d.ts @@ -0,0 +1,38 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default Boids; + +declare namespace Boids { + + interface IConfig { + separation?: { + weight?: number, + distance?: number, + }, + + cohesion?: { + weight?: number, + distance?: number, + }, + + alignment?: { + weight?: number, + distance?: number, + }, + } +} + +declare class Boids extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Boids.IConfig + ); + + readonly output: Phaser.Math.Vector2; + + setSeparationParameters(weight: number, distance: number): this; + setCohesionParameters(weight: number, distance: number): this; + setAlignmentParameters(weight: number, distance: number): this; + + update(neighbors: Phaser.GameObjects.GameObject[]): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/Boids.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/Boids.js new file mode 100644 index 000000000..3b8d45792 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/boids/Boids.js @@ -0,0 +1,83 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import AddSeparationForce from './AddSeparationForce.js'; +import AddAlignmentForce from './AddAlignmentForce.js'; +import AddCohesionForce from './AddCohesionForce.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Vector2 = Phaser.Math.Vector2; + +class Boids extends ComponentBase { + constructor(parent, config) { + super(parent, { eventEmitter: false }); + // No event emitter + // this.parent = gameObject; + + this.output = new Vector2(); + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.setSeparationParameters(GetValue(o, 'separation.weight', 0), GetValue(o, 'separation.distance', Infinity)); + this.setCohesionParameters(GetValue(o, 'cohesion.weight', 0), GetValue(o, 'cohesion.distance', Infinity)); + this.setAlignmentParameters(GetValue(o, 'alignment.weight', 0), GetValue(o, 'alignment.distance', Infinity)); + return this; + } + + setSeparationParameters(weight, distance) { + this.separationWeight = weight; + this.separationDistance = distance; + return this; + } + + setCohesionParameters(weight, distance) { + this.cohesionWeight = weight; + this.cohesionDistance = distance; + return this; + } + + setAlignmentParameters(weight, distance) { + this.alignmentWeight = weight; + this.alignmentDistance = distance; + return this; + } + + update(neighbors) { + this.output.reset(); + AddSeparationForce(this.parent, neighbors, this.separationWeight, this.separationDistance, this.output); + AddCohesionForce(this.parent, neighbors, this.cohesionWeight, this.cohesionDistance, this.output); + AddAlignmentForce(this.parent, neighbors, this.alignmentWeight, this.alignmentDistance, this.output); + return this; + } + + addSeparationForce(neighbors, separationWeight, separationDistance, output) { + if (separationWeight === undefined) { + separationWeight = this.separationWeight; + } + if (separationDistance === undefined) { + separationDistance = this.separationDistance; + } + AddSeparationForce(this.parent, neighbors, separationWeight, separationDistance, output); + return this; + } + + addAlignmentForce(neighbors, alignmentWeight, output) { + if (alignmentWeight === undefined) { + alignmentWeight = this.alignmentWeight; + } + AddAlignmentForce(this.parent, neighbors, alignmentWeight, output); + return this; + } + + addCohesionForce(neighbors, cohesionWeight, cohesionDistance, output) { + if (cohesionWeight === undefined) { + cohesionWeight = this.cohesionWeight; + } + if (cohesionDistance === undefined) { + cohesionDistance = this.cohesionDistance; + } + AddCohesionForce(this.parent, neighbors, cohesionWeight, cohesionDistance, output); + return this; + } +} + +export default Boids; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/bounds/Bounds.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/bounds/Bounds.d.ts new file mode 100644 index 000000000..360a15aba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/bounds/Bounds.d.ts @@ -0,0 +1,53 @@ +import TickTask from '../../utils/componentbase/TickTask'; + +export default Bounds; + +declare namespace Bounds { + + type RectangleLikeType = { + x?: number, y?: number, + centerX?: number, centerY?: number, + width?: number, height?: number, + } + + type BoundsEnableType = { + left?: boolean, right?: boolean, + top?: boolean, bottom?: boolean, + } + + type AlignModeType = 0 | 1 | 'bounds' | 'origin'; + + interface IConfig { + target?: Phaser.GameObjects.GameObject; + bounds?: Phaser.Geom.Rectangle | RectangleLikeType; + enable?: boolean | BoundsEnableType; + alignMode?: AlignModeType; + } +} + +declare class Bounds extends TickTask { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Bounds.IConfig + ) + + setBoundsTarget(gameObject?: Phaser.GameObjects.GameObject): this; + boundsTarget: Phaser.GameObjects.GameObject; + + setBounds(bounds: Phaser.Geom.Rectangle | Bounds.RectangleLikeType): this; + bounds: Phaser.Geom.Rectangle; + + setEnable(enable?: boolean | Bounds.BoundsEnableType): this; + enable: boolean; + boundsEnable: { left: boolean, right: boolean, top: boolean, bottom: boolean }; + + setAlignMode(mode: Bounds.AlignModeType): this; + alignMode: number; + + readonly isHitAny: boolean; + readonly isHitLeft: boolean; + readonly isHitRight: boolean; + readonly isHitTop: boolean; + readonly isHitBottom: boolean; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/bounds/Bounds.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/bounds/Bounds.js new file mode 100644 index 000000000..1a94a8465 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/bounds/Bounds.js @@ -0,0 +1,195 @@ +import TickTask from '../../utils/componentbase/SceneUpdateTickTask.js'; +import { GetBounds } from '../../utils/bounds/GetBounds.js'; + +const Rectangle = Phaser.Geom.Rectangle; +const GetValue = Phaser.Utils.Objects.GetValue; + +class Bounds extends TickTask { + constructor(gameObject, config) { + if (config === undefined) { + config = {}; + } + config.tickEventName = 'postupdate'; + super(gameObject, config); + // this.parent = gameObject; + + this.bounds = new Rectangle(); + this.boundsTarget = undefined; + this.boundsEnable = {}; + this.clearHitResult(); + this.resetFromJSON(config); + } + + resetFromJSON(o) { + var target = GetValue(o, 'target'); + if (target) { + this.setBoundsTarget(target); + } else { + this.setBoundsTarget(); + this.setBounds(GetValue(o, 'bounds')); + } + this.setEnable(GetValue(o, 'enable', true)); + this.setAlignMode(GetValue(o, 'alignMode', 0)); + + return this; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + super.shutdown(fromScene); + } + + setBoundsTarget(gameObject) { + this.boundsTarget = gameObject; + return this; + } + + setBounds(boundsConfig) { + if (!boundsConfig) { + return this; + } + + var bounds = this.bounds; + + bounds.setSize( + GetValue(boundsConfig, 'width', 0), + GetValue(boundsConfig, 'height', 0) + ) + if (boundsConfig.hasOwnProperty('centerX')) { + bounds.centerX = boundsConfig.centerX; + } else { + bounds.x = GetValue(boundsConfig, 'x', 0); + } + if (boundsConfig.hasOwnProperty('centerY')) { + bounds.centerY = boundsConfig.centerY; + } else { + bounds.y = GetValue(boundsConfig, 'y', 0); + } + + return this; + } + + setEnable(enable) { + if (enable === undefined) { + enable = true; + } + + var boundsEnable = this.boundsEnable; + if (typeof (enable) === 'boolean') { + boundsEnable.left = enable; + boundsEnable.right = enable; + boundsEnable.top = enable; + boundsEnable.bottom = enable; + } else { + boundsEnable.left = GetValue(enable, 'left', false); + boundsEnable.right = GetValue(enable, 'right', false); + boundsEnable.top = GetValue(enable, 'top', false); + boundsEnable.bottom = GetValue(enable, 'bottom', false); + } + + this.isRunning = this.enable; + + return this; + } + + setAlignMode(mode) { + if (typeof (mode) === 'string') { + mode = AlignMode[mode]; + } + this.alignMode = mode; + return this; + } + + get enable() { + var boundsEnable = this.boundsEnable; + return boundsEnable.left || boundsEnable.right || boundsEnable.top || boundsEnable.bottom; + } + + set enable(value) { + this.setEnable(value); + } + + update(time, delta) { + var gameObject = this.parent; + this.clearHitResult(); + if (!this.enable) { + return this; + } + + var target = this.boundsTarget; + if (target) { + GetBounds(target, this.bounds); + } + + var bounds = this.bounds; + var boundsEnable = this.boundsEnable; + + var alignToGOBound = (this.alignMode === 0); + var gameObjectBounds = (alignToGOBound) ? GetBounds(gameObject, true) : undefined; + + if (boundsEnable.left) { + var left = (alignToGOBound) ? gameObjectBounds.left : gameObject.x; + var dx = bounds.left - left; + if (dx > 0) { + gameObject.x += dx; + this.isHitAny = true; + this.isHitLeft = true; + this.emit('hitleft', this.parent, this); + } + } + if (boundsEnable.right) { + var right = (alignToGOBound) ? gameObjectBounds.right : gameObject.x; + var dx = bounds.right - right; + if (dx < 0) { + gameObject.x += dx; + this.isHitAny = true; + this.isHitRight = true; + this.emit('hitright', this.parent, this); + } + } + if (boundsEnable.top) { + var top = (alignToGOBound) ? gameObjectBounds.top : gameObject.y; + var dy = bounds.top - top; + if (dy > 0) { + gameObject.y += dy; + this.isHitAny = true; + this.isHitTop = true; + this.emit('hittop', this.parent, this); + } + } + if (boundsEnable.bottom) { + var bottom = (alignToGOBound) ? gameObjectBounds.bottom : gameObject.y; + var dy = bounds.bottom - bottom; + if (dy < 0) { + gameObject.y += dy; + this.isHitAny = true; + this.isHitBottom = true; + this.emit('hitbottom', this.parent, this); + } + } + + if (this.isHitAny) { + this.emit('hitany', this.parent, this); + } + } + + clearHitResult() { + this.isHitAny = false; + this.isHitLeft = false; + this.isHitRight = false; + this.isHitTop = false; + this.isHitBottom = false; + return this; + } +} + +const AlignMode = { + bounds: 0, + origin: 1 +} + +export default Bounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/bullet/Bullet.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/bullet/Bullet.d.ts new file mode 100644 index 000000000..621f20311 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/bullet/Bullet.d.ts @@ -0,0 +1,35 @@ +import TickTask from '../../utils/componentbase/TickTask'; + +export default Bullet; + +declare namespace Bullet { + + interface IConfig { + speed?: number, + enable?: boolean, + wrap?: boolean, + padding?: number, + + angle?: number, + rotation?: number, + } +} + +declare class Bullet extends TickTask { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Bullet.IConfig + ) + + setEnable(enable?: boolean): this; + enable: boolean; + + setSpeed(speed: number): this; + speed: number; + + setAngle(angle?: number): this; + angle: number | undefined; + + setRotation(rotation?: number): this; + rotation: number | undefined; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/bullet/Bullet.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/bullet/Bullet.js new file mode 100644 index 000000000..1e8d0f439 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/bullet/Bullet.js @@ -0,0 +1,122 @@ +import TickTask from '../../utils/componentbase/SceneUpdateTickTask.js'; +import { SetVelocity } from '../../utils/arcade/Helpers.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const DegToRad = Phaser.Math.DegToRad; +const RadToDeg = Phaser.Math.RadToDeg; + +class Bullet extends TickTask { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + if (!this.parent.body) { + this.scene.physics.add.existing(this.parent, false); + } + this.setWrapMode(GetValue(o, 'wrap', false), GetValue(o, 'padding', 0)); + this.setEnable(GetValue(o, 'enable', true)); + this.setSpeed(GetValue(o, 'speed', 200)); + + var angle = GetValue(o, 'angle'); + if (angle !== undefined) { + this.setAngle(angle); + var rotation = GetValue(o, 'rotation'); + if (rotation !== undefined) { + this.setRotation(rotation); + } + } + + return this; + } + + get enable() { + return this.isRunning; + } + + set enable(value) { + this.isRunning = value; + if (!value) { + SetVelocity(this.parent, 0, 0); + } + } + + setEnable(e) { + if (e == undefined) { + e = true; + } + this.enable = e; + return this; + } + + setSpeed(speed) { + this.speed = speed; + return this; + } + + setWrapMode(wrap, padding) { + if (wrap === undefined) { + wrap = true; + } + this.wrap = wrap; + this.padding = padding; + return this; + } + + setAngle(angle) { + this.angle = angle; + return this; + } + + setRotation(rotation) { + this.rotation = rotation; + return this; + } + + set angle(value) { + if (typeof (value) === 'number') { + value = DegToRad(value); + } + this.rotation = value; + } + + get angle() { + var value = this.rotation; + if (typeof (value) === 'number') { + value = RadToDeg(value); + } + return value; + } + + update(time, delta) { + var gameObject = this.parent; + if (!this.enable) { + SetVelocity(gameObject, 0, 0); + return this; + } + + if (!gameObject.active) { + return this; + } + + var rotation = this.rotation; + if (rotation == null) { + rotation = gameObject.rotation; + } + var vx = this.speed * Math.cos(rotation); + var vy = this.speed * Math.sin(rotation); + SetVelocity(gameObject, vx, vy); + + if (this.wrap) { + gameObject.body.world.wrap(gameObject, this.padding); + } + + return this; + } +} + +export default Bullet; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/containerperspective/ContainerPerspective.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/containerperspective/ContainerPerspective.d.ts new file mode 100644 index 000000000..1c2c8c6d0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/containerperspective/ContainerPerspective.d.ts @@ -0,0 +1,23 @@ +import RenderTexture from '../../gameobjects/mesh/perspective/rendertexture/RenderTexture'; +import ContainerLite from '../../gameobjects/container/containerlite/ContainerLite'; + +export default ContainerPerspective; + +declare namespace ContainerPerspective { + + interface IConfig { + useParentBounds?: boolean, + } + +} + +declare class ContainerPerspective extends RenderTexture { + constructor( + parentContainer: ContainerLite, + config?: ContainerPerspective.IConfig + ); + + enter(): this; + exit(): this; + readonly perspectiveState: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/containerperspective/ContainerPerspective.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/containerperspective/ContainerPerspective.js new file mode 100644 index 000000000..7b2271700 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/containerperspective/ContainerPerspective.js @@ -0,0 +1,10 @@ +import MeshRenderTextureBase from '../../gameobjects/container/containerlite/rendertexture/MeshRenderTextureBase.js'; +import RenderTexture from '../../gameobjects/mesh/perspective/rendertexture/RenderTexture.js'; + +class ContainerPerspective extends MeshRenderTextureBase(RenderTexture) { + get perspectiveState() { + return this.isRunning; + } +} + +export default ContainerPerspective; diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/containerskew/ContainerSkew.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/containerskew/ContainerSkew.d.ts new file mode 100644 index 000000000..c2317927c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/containerskew/ContainerSkew.d.ts @@ -0,0 +1,23 @@ +import RenderTexture from '../../gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture'; +import ContainerLite from '../../gameobjects/container/containerlite/ContainerLite'; + +export default ContainerSkew; + +declare namespace ContainerSkew { + + interface IConfig { + useParentBounds?: boolean, + } + +} + +declare class ContainerSkew extends RenderTexture { + constructor( + parentContainer: ContainerLite, + config?: ContainerSkew.IConfig + ); + + enter(): this; + exit(): this; + readonly skewState: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/containerskew/ContainerSkew.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/containerskew/ContainerSkew.js new file mode 100644 index 000000000..5d86618c6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/containerskew/ContainerSkew.js @@ -0,0 +1,10 @@ +import MeshRenderTextureBase from '../../gameobjects/container/containerlite/rendertexture/MeshRenderTextureBase.js'; +import RenderTexture from '../../gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture.js'; + +class ContainerSkew extends MeshRenderTextureBase(RenderTexture) { + get skewState() { + return this.isRunning; + } +} + +export default ContainerSkew; diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/dropdown/DropDown.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/dropdown/DropDown.js new file mode 100644 index 000000000..2f9db45d2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/dropdown/DropDown.js @@ -0,0 +1,99 @@ +import OpenCloseTransition from '../openclosetransition/OpenCloseTransition.js'; +import PopUp from '../../popup.js'; +import ScaleDown from '../scale/ScaleDown.js'; +import SetPosition from './SetPosition.js'; +import IsPointInBounds from '../../utils/bounds/IsPointInBounds.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class DropDown extends OpenCloseTransition { + constructor(gameObject, config) { + if (config === undefined) { + config = {}; + } + if (config.transitIn == null) { + config.transitIn = function (gameObject, duration) { + PopUp(gameObject, duration, 'y', 'Cubic') + }; + } + if (config.transitOut == null) { + config.transitOut = function (gameObject, duration) { + // Don't destroy here + ScaleDown(gameObject, duration, 'y', 'Linear') + }; + } + config.manualClose = true; + config.clickOutsideClose = true; + config.destroy = true; + + super(gameObject, config); + // this.parent = gameObject; + // this.scene + + SetPosition(gameObject, config); + + if (gameObject.isRexSizer) { + gameObject.layout(); + } + + // Close conditions: + var touchOutsideClose = GetValue(config, 'touchOutsideClose', false); + var anyTouchClose = GetValue(config, 'anyTouchClose', false); + + if (anyTouchClose) { + touchOutsideClose = false; + } + + // Registet touch-close event after opened + if (anyTouchClose) { + this.once('open', this.anyTouchClose, this); + } else if (touchOutsideClose) { + this.once('open', this.touchOutsideClose, this); + } + + this.requestOpen(); + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + // Registered in touchOutsideClose() + this.scene.input.off('pointerup', this.touchCloseCallback, this); + + super.shutdown(fromScene); + } + + touchOutsideClose() { + this.scene.input.on('pointerup', this.touchCloseCallback, this); + this.clickOutsideTest = true; + return this; + } + + anyTouchClose() { + this.scene.input.once('pointerup', this.touchCloseCallback, this); + return this; + } + + touchCloseCallback(pointer) { + if (this.clickOutsideTest && IsPointInBounds(this.parent, pointer.worldX, pointer.worldY)) { + return; + } + this.requestClose(); + } + + onOpen() { + this.emit('open', this.parent, this); + super.onOpen(); + } + + onClose() { + this.emit('close', this.parent, this); + super.onClose(); + } + +} + +export default DropDown; diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/dropdown/Dropdown.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/dropdown/Dropdown.d.ts new file mode 100644 index 000000000..9a95236ef --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/dropdown/Dropdown.d.ts @@ -0,0 +1,45 @@ +export default DropDown; + +declare namespace DropDown { + type TransitCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + duration: number + ) => void; + + interface IConfig { + + duration?: { + in?: number, + out?: number, + }, + + transitIn?: TransitCallbackType, + transitOut?: TransitCallbackType, + + expandDirection?: 0 | 1 | 'down' | 'up', + + alignTarget?: Phaser.GameObjects.GameObject, + alignTargetX?: Phaser.GameObjects.GameObject, + alignTargetY?: Phaser.GameObjects.GameObject, + alignOffsetX?: number, + alignOffsetY?: number, + alignSide?: string, + + bounds?: Phaser.Geom.Rectangle, + + touchOutsideClose?: boolean, + + anyTouchClose?: boolean, + + destroy?: boolean, + } +} + +declare class DropDown extends Phaser.Events.EventEmitter { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: DropDown.IConfig, + ) + + requestClose(closeEventData: any): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/dropdown/SetPosition.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/dropdown/SetPosition.js new file mode 100644 index 000000000..7a3e23b22 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/dropdown/SetPosition.js @@ -0,0 +1,65 @@ +import GetValueFromAlias from '../../utils/object/GetValueFromAliasKeys.js'; +import GetViewport from '../../utils/system/GetViewport.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var SetPosition = function (gameObject, config) { + var expandDirection = GetValue(config, 'expandDirection', undefined); + if (typeof (expandDirection) === 'string') { + expandDirection = ExpandDirections[expandDirection]; + } + var alignTargetX = GetValueFromAlias(config, 'alignTarget', 'alignTargetX'); + var alignTargetY = GetValue(config, 'alignTargetY', alignTargetX); + var alignOffsetX = GetValue(config, 'alignOffsetX', 0); + var alignOffsetY = GetValue(config, 'alignOffsetY', 0); + var alignSide = GetValue(config, 'alignSide', ''); + var alignRight = alignSide.includes('right'); + + var positionBounds = GetValue(config, 'bounds'); + + // Expand direction + var isExpandDown = (expandDirection === 0); + var isExpandUp = (expandDirection === 1); + var flexExpand = !isExpandDown && !isExpandUp; + + var originX = (alignRight) ? 1 : 0; + var originY = (isExpandDown || flexExpand) ? 0 : 1; + gameObject.setOrigin(originX, originY); + + var x, y; + if (alignRight) { + x = alignTargetX.getTopRight().x; + } else { + x = alignTargetX.getTopLeft().x; + } + + y = alignTargetY.getBottomLeft().y; + gameObject.setPosition( + x + alignOffsetX, + y + alignOffsetY + ); + + var bounds = positionBounds; + if (!bounds) { + bounds = GetViewport(gameObject.scene); + } + + if (flexExpand && (gameObject.getBottomLeft().y > bounds.bottom)) { + // Out of bounds, can't put list-panel below parent + y = alignTargetY.getTopLeft().y; + gameObject + .setOrigin(0, 1) + .setPosition( + x + alignOffsetX, + y + alignOffsetY + ); + } + +} + +const ExpandDirections = { + down: 0, + up: 1 +} + +export default SetPosition; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/easedata/EaseData.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/easedata/EaseData.d.ts new file mode 100644 index 000000000..1cda512bd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/easedata/EaseData.d.ts @@ -0,0 +1,53 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default EaseData; + +declare namespace EaseData { + interface IConfig extends ComponentBase.IConfig { + } +} + +declare class EaseData extends ComponentBase { + easeTo( + key: string, + value: number, + duration?: number, + ease?: string + ): this; + + easeTo( + config: { + key: string, + value: number, + duration?: number, + ease?: string, + speed?: number + } + ): this; + + easeFrom( + key: string, + value: number, + duration?: number, + ease?: string + ): this; + + easeFrom( + config: { + key: string, + value: number, + duration?: number, + ease?: string, + speed?: number + } + ): this; + + stopEase( + key: string, + toEnd?: boolean + ): this; + + stopAll( + toEnd?: boolean + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/easedata/EaseData.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/easedata/EaseData.js new file mode 100644 index 000000000..a0fd8c48a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/easedata/EaseData.js @@ -0,0 +1,123 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import EaseValueTask from '../../utils/ease/EaseValueTask.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +class EaseData extends ComponentBase { + constructor(parent, config) { + super(parent, config); + + this.parent.setDataEnabled(); + this.easeTasks = {}; + } + + complete(key) { + this.emit(`complete-${key}`, this.parent, this); + this.emit('complete', key, this.parent, this); + } + + getEaseTask(key) { + var easeTask = this.easeTasks[key]; + if (easeTask === undefined) { + easeTask = new EaseValueTask(this.parent); + this.easeTasks[key] = easeTask; + + easeTask + .setTarget(this.parent.data.values) + .on('complete', function () { + this.complete(key); + }, this); + } + return easeTask; + } + + easeTo(key, value, duration, ease) { + if (IsPlainObject(key)) { + var config = key; + key = config.key; + value = config.value; + duration = config.duration; + ease = config.ease; + + var speed = config.speed; + if ((duration === undefined) && (speed !== undefined)) { + duration = (Math.abs(value - this.parent.data.values[key]) / speed) * 1000; + } + } + + if (duration === undefined) { + duration = 1000; + } + if (ease === undefined) { + ease = 'Linear'; + } + + var easeTask = this.getEaseTask(key); + easeTask.restart({ + key: key, + to: value, + duration: duration, + ease: ease + }); + + return this; + } + + easeFrom(key, value, duration, ease) { + if (IsPlainObject(key)) { + var config = key; + key = config.key; + value = config.value; + duration = config.duration; + ease = config.ease; + + var speed = config.speed; + if ((duration === undefined) && (speed !== undefined)) { + duration = (Math.abs(value - this.parent.data.values[key]) / speed) * 1000; + } + } + + if (duration === undefined) { + duration = 1000; + } + if (ease === undefined) { + ease = 'Linear'; + } + + var easeTask = this.getEaseTask(key); + easeTask.restart({ + key: key, + from: value, + duration: duration, + ease: ease + }); + + return this; + } + + stopEase(key, toEnd) { + if (toEnd === undefined) { + toEnd = true; + } + + var easeTask = this.easeTasks[key]; + if (easeTask) { + easeTask.stop(toEnd); + } + + return this; + } + + stopAll(toEnd) { + if (toEnd === undefined) { + toEnd = true; + } + + for (var key in this.easeTasks) { + this.stopEase(key, toEnd); + } + return this; + } +} + +export default EaseData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMove.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMove.d.ts new file mode 100644 index 000000000..846172719 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMove.d.ts @@ -0,0 +1,48 @@ +import EaseValueTaskBase from "../../utils/componentbase/tweentask/EaseValueTaskBase"; + +export default EaseMove; + +declare namespace EaseMove { + type ModeType = 0 | 1 | 2 | 'stop' | 'destroy' | 'yoyo'; + + interface IConfig { + mode?: ModeType, + + x?: number, y?: number, + startX?: number, startY?: number, + endX?: number, endY?: number, + + duration?: number, + delay?: number, + ease?: string + } + + namespace Events { + type CompleteCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + easeMove: EaseMove + ) => void; + } +} + +declare class EaseMove extends EaseValueTaskBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: EaseMove.IConfig + ) + + setMode(mode: EaseMove.ModeType): this; + mode: number; + + setTargetPosition(x: number, y: number): this; + setTargetPosition( + config?: { + startX?: number, startY?: number, + endX?: number, endY?: number, + } + ): this; + startX: number; + startY: number; + endX: number; + endY: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMove.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMove.js new file mode 100644 index 000000000..36c0f63e5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMove.js @@ -0,0 +1,116 @@ +import EaseValueTaskBase from '../../utils/componentbase/tweentask/EaseValueTaskBase.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const Linear = Phaser.Math.Linear; + +class EaseMove extends EaseValueTaskBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + // this.timer + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + + this.setMode(GetValue(o, 'mode', 0)); + + if (o && (o.hasOwnProperty('x') || o.hasOwnProperty('y'))) { + var endX = GetAdvancedValue(o, 'x', undefined); + var endY = GetAdvancedValue(o, 'y', undefined); + this.setTargetPosition(endX, endY); + } else { + this.setTargetPosition(o); + } + + return this; + } + + setMode(m) { + if (typeof (m) === 'string') { + m = MODE[m]; + } + this.mode = m; + return this; + } + + setTargetPosition(x, y) { + if ((typeof (x) === 'number') || (typeof (y) === 'number')) { + // endX, endY + // x,y : a number, or undefined + this.startX = this.parent.x; + this.startY = this.parent.y; + this.endX = x; + this.endY = y; + } else { + var config = x; + this.startX = GetAdvancedValue(config, 'startX', undefined); + this.startY = GetAdvancedValue(config, 'startY', undefined); + this.endX = GetAdvancedValue(config, 'endX', undefined); + this.endY = GetAdvancedValue(config, 'endY', undefined); + } + + this.hasMoveX = (this.startX !== undefined) && (this.endX !== undefined); + this.hasMoveY = (this.startY !== undefined) && (this.endY !== undefined); + return this; + } + + start() { + if (this.timer.isRunning) { + return this; + } + + var gameObject = this.parent; + if (this.hasMoveX) { + gameObject.x = this.startX; + } + if (this.hasMoveY) { + gameObject.y = this.startY; + } + + this.timer + .setDelay(this.delay) + .setDuration(this.duration) + .setRepeat((this.mode === 2) ? -1 : 0); + + super.start(); + return this; + } + + updateGameObject(gameObject, timer) { + var t = timer.t; + if (timer.isOddIteration) { // Yoyo + t = 1 - t; + } + t = this.easeFn(t); + + if (this.hasMoveX) { + gameObject.x = Linear(this.startX, this.endX, t); + } + if (this.hasMoveY) { + gameObject.y = Linear(this.startY, this.endY, t); + } + } + + complete() { + super.complete(); + + if (this.mode === 1) { + this.parent.destroy(); + // Will also destroy this behavior + } + return this; + } +} + +const MODE = { + stop: 0, + destroy: 1, + yoyo: 2 +} + +export default EaseMove; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveFrom.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveFrom.d.ts new file mode 100644 index 000000000..801400d93 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveFrom.d.ts @@ -0,0 +1,22 @@ +import EaseMove from './EaseMove'; + +declare function EaseMoveFrom( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + startX: number | string | undefined, + startY: number | string | undefined, + ease?: string, + destroyMode?: boolean, + easeMove?: EaseMove +): EaseMove; + +declare function EaseMoveFrom( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + startX: number | string | undefined, + startY: number | string | undefined, + ease?: string, + easeMove?: EaseMove +): EaseMove; + +export default EaseMoveFrom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveFrom.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveFrom.js new file mode 100644 index 000000000..6ebe2439c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveFrom.js @@ -0,0 +1,37 @@ +import EaseMove from './EaseMove.js'; +import ParseValue from './ParseValue.js'; + +var EaseMoveFrom = function (gameObject, duration, startX, startY, ease, destroyMode, easeMove) { + if (destroyMode instanceof EaseMove) { + easeMove = destroyMode; + destroyMode = undefined; + } + + if (destroyMode === undefined) { + destroyMode = false; + } + + var config = {}; + config.mode = (destroyMode) ? 1 : 0; + if (startX !== undefined) { + config.startX = ParseValue(startX, gameObject.x); + config.endX = gameObject.x; + } + if (startY !== undefined) { + config.startY = ParseValue(startY, gameObject.y); + config.endY = gameObject.y; + } + config.duration = duration; + config.ease = (ease === undefined) ? 'Linear' : ease; + + if (easeMove === undefined) { + easeMove = new EaseMove(gameObject, config); + } else { + easeMove.resetFromJSON(config); + } + easeMove.restart(); + + return easeMove; +} + +export default EaseMoveFrom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveTo.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveTo.d.ts new file mode 100644 index 000000000..e36748437 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveTo.d.ts @@ -0,0 +1,22 @@ +import EaseMove from './EaseMove'; + +declare function EaseMoveTo( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + endX: number | string | undefined, + endY: number | string | undefined, + ease?: string, + destroyMode?: boolean, + easeMove?: EaseMove +): EaseMove; + +declare function EaseMoveTo( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + endX: number | string | undefined, + endY: number | string | undefined, + ease?: string, + easeMove?: EaseMove +): EaseMove; + +export default EaseMoveTo; diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveTo.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveTo.js new file mode 100644 index 000000000..29622dad8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/EaseMoveTo.js @@ -0,0 +1,37 @@ +import EaseMove from './EaseMove.js'; +import ParseValue from './ParseValue.js'; + +var EaseMoveTo = function (gameObject, duration, endX, endY, ease, destroyMode, easeMove) { + if (destroyMode instanceof EaseMove) { + easeMove = destroyMode; + destroyMode = undefined; + } + + if (destroyMode === undefined) { + destroyMode = false; + } + + var config = {}; + config.mode = (destroyMode) ? 1 : 0; + if (endX !== undefined) { + config.startX = gameObject.x; + config.endX = ParseValue(endX, gameObject.x); + } + if (endY !== undefined) { + config.startY = gameObject.y; + config.endY = ParseValue(endY, gameObject.y); + } + config.duration = duration; + config.ease = (ease === undefined) ? 'Linear' : ease; + + if (easeMove === undefined) { + easeMove = new EaseMove(gameObject, config); + } else { + easeMove.resetFromJSON(config); + } + easeMove.restart(); + + return easeMove; +}; + +export default EaseMoveTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/ParseValue.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/ParseValue.js new file mode 100644 index 000000000..afd7f7491 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/easemove/ParseValue.js @@ -0,0 +1,17 @@ +var ParseValue = function (propertyValue, startValue) { + // propertyValue : number or string + if (typeof (propertyValue) === 'number') { + return propertyValue; + } else { + var op = propertyValue[0]; + var num = parseFloat(propertyValue.substr(2)); + switch (op) { + case '+': return startValue + num; + case '-': return startValue - num; + case '*': return startValue * num; + case '/': return startValue / num; + } + } +} + +export default ParseValue; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/eightdirection/EightDirection.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/eightdirection/EightDirection.d.ts new file mode 100644 index 000000000..24c8c89f0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/eightdirection/EightDirection.d.ts @@ -0,0 +1,60 @@ +import TickTask from '../../utils/componentbase/TickTask'; + +export default EightDirection; + +declare namespace EightDirection { + + type DirectionModeType = 0 | 1 | 2 | 3 | 'up&down' | 'left&right' | '4dir' | '8dir'; + type CursorKeys = { + up: Phaser.Input.Keyboard.Key, + down: Phaser.Input.Keyboard.Key, + left: Phaser.Input.Keyboard.Key, + right: Phaser.Input.Keyboard.Key + } + + interface IConfig { + speed?: number, + dir?: DirectionModeType, + rotateToDirection?: boolean, + enable?: boolean, + wrap?: boolean, + padding?: number, + cursorKeys?: CursorKeys + } +} + +declare class EightDirection extends TickTask { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: EightDirection.IConfig + ) + + setEnable(enable?: boolean): this; + enable: boolean; + + setCursorKeys( + cursorKeys: EightDirection.CursorKeys + ): this; + cursorKeys: EightDirection.CursorKeys; + + setSpeed(speed: number): this; + speed: number; + + setRotateToTarget(enable?: boolean): this; + rotateToTarget: boolean; + + setDirMode(dir: EightDirection.DirectionModeType): this; + dirMode: number; + + setWrapMode( + wrap?: boolean, + padding?: number + ): this; + wrap: boolean; + padding: number; + + readonly isLeft: boolean; + readonly isRight: boolean; + readonly isUp: boolean; + readonly isDown: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/eightdirection/EightDirection.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/eightdirection/EightDirection.js new file mode 100644 index 000000000..56085779a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/eightdirection/EightDirection.js @@ -0,0 +1,172 @@ +import TickTask from '../../utils/componentbase/SceneUpdateTickTask.js'; +import { + SetVelocity +} from '../../utils/arcade/Helpers.js'; +import DegToRad from '../../utils/math/DegToRad.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class EightDirection extends TickTask { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + if (!this.parent.body) { + this.scene.physics.add.existing(this.parent, false); + } + this.setEnable(GetValue(o, 'enable', true)); + this.setDirMode(GetValue(o, 'dir', '8dir')); + this.setSpeed(GetValue(o, 'speed', 200)); + this.setRotateToDirection(GetValue(o, 'rotateToDirection', false)); + this.setWrapMode(GetValue(o, 'wrap', false), GetValue(o, 'padding', 0)); + this.setCursorKeys(GetValue(o, 'cursorKeys', undefined)); + return this; + } + + get enable() { + return this.isRunning; + } + + set enable(value) { + this.isRunning = value; + if (!value) { + SetVelocity(this, 0, 0); + } + } + + setEnable(e) { + if (e == undefined) { + e = true; + } + this.enable = e; + if (e && (this.body === undefined)) { + this.scene.physics.add.existing(this.parent, false); + } + return this; + } + + setDirMode(m) { + if (typeof (m) === 'string') { + m = DIRMODE[m]; + } + this.dirMode = m; + return this; + } + + setSpeed(speed) { + this.speed = speed; + return this; + } + + setRotateToDirection(rotateToDirection) { + this.rotateToDirection = rotateToDirection; + return this; + } + + setWrapMode(wrap, padding) { + if (wrap === undefined) { + wrap = true; + } + this.wrap = wrap; + this.padding = padding; + return this; + } + + setCursorKeys(cursorKeys) { + if (cursorKeys === undefined) { + cursorKeys = this.scene.input.keyboard.createCursorKeys(); + } + this.cursorKeys = cursorKeys; + return this; + } + + get isLeft() { + return (this.enable) ? this.cursorKeys.left.isDown : false; + } + + get isRight() { + return (this.enable) ? this.cursorKeys.right.isDown : false; + } + + get isUp() { + return (this.enable) ? this.cursorKeys.up.isDown : false; + } + + get isDown() { + return (this.enable) ? this.cursorKeys.down.isDown : false; + } + + update(time, delta) { + var gameObject = this.parent; + if (!this.enable) { + SetVelocity(gameObject, 0, 0); + return this; + } + + if (!gameObject.active) { + return this; + } + + var dy = ((this.isUp) ? -1 : 0) + ((this.isDown) ? 1 : 0), + dx = ((this.isLeft) ? -1 : 0) + ((this.isRight) ? 1 : 0); + if ((dx === 0) && (dy === 0)) { + SetVelocity(gameObject, 0, 0); + return this; + } + switch (this.dirMode) { + case 0: // up&down + dx = 0; + break; + case 1: // left&right + dy = 0; + break; + case 2: // 4dir + if (dy !== 0) { + dx = 0; + } + break; + } + + var rotation, vx, vy; + if (dy === 0) { // dx !== 0 + vx = this.speed * dx; + vy = 0; + rotation = (dx === 1) ? RAD0 : RAD180; + } else if (dx === 0) { // dy !== 0 + vx = 0; + vy = this.speed * dy; + rotation = (dy === 1) ? RAD90 : RAD270; + } else { // (dx !== 0) && (dy !== 0) + rotation = Math.atan2(dy, dx); + vx = this.speed * Math.cos(rotation); + vy = this.speed * Math.sin(rotation); + } + SetVelocity(gameObject, vx, vy); + if (this.rotateToDirection && (rotation !== undefined)) { + gameObject.rotation = rotation; + } + + if (this.wrap) { + gameObject.body.world.wrap(gameObject, this.padding); + } + return this; + } +} + +const DIRMODE = { + 'up&down': 0, + 'left&right': 1, + '4dir': 2, + '8dir': 3 +}; +const RAD0 = DegToRad(0); +const RAD90 = DegToRad(90); +const RAD180 = DegToRad(180); +const RAD270 = DegToRad(270); + +export default EightDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/fade/Fade.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/fade/Fade.d.ts new file mode 100644 index 000000000..b61b06fb3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/fade/Fade.d.ts @@ -0,0 +1,36 @@ +import EaseValueTaskBase from "../../utils/componentbase/tweentask/EaseValueTaskBase"; + +export default Fade; + +declare namespace Fade { + type ModeType = 0 | 1 | 2 | 'stop' | 'destroy' | 'yoyo'; + + interface IConfig { + mode?: ModeType, + start?: number, + end?: number, + duration?: number, + delay?: number + } + + namespace Events { + type CompleteCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + fade: Fade + ) => void; + } +} + +declare class Fade extends EaseValueTaskBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Fade.IConfig + ) + + setMode(mode: Fade.ModeType): this; + mode: number; + + setAlphaRange(start: number, end: number): this; + alphaStart: number; + alphaEnd: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/fade/Fade.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/fade/Fade.js new file mode 100644 index 000000000..27972541f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/fade/Fade.js @@ -0,0 +1,85 @@ +import EaseValueTaskBase from '../../utils/componentbase/tweentask/EaseValueTaskBase.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const Linear = Phaser.Math.Linear; + +class Fade extends EaseValueTaskBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + // this.timer + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + + this.setMode(GetValue(o, 'mode', 0)); + this.setAlphaRange( + GetAdvancedValue(o, 'start', this.parent.alpha), + GetAdvancedValue(o, 'end', 0) + ); + return this; + } + + setMode(m) { + if (typeof (m) === 'string') { + m = MODE[m]; + } + this.mode = m; + return this; + } + + setAlphaRange(start, end) { + this.alphaStart = start; + this.alphaEnd = end; + return this; + } + + start() { + if (this.timer.isRunning) { + return this; + } + + var gameObject = this.parent; + gameObject.setAlpha(this.alphaStart); + + this.timer + .setDelay(this.delay) + .setDuration(this.duration) + .setRepeat((this.mode === 2) ? -1 : 0); + + super.start(); + return this; + } + + updateGameObject(gameObject, timer) { + var t = timer.t; + if (timer.isOddIteration) { // Yoyo + t = 1 - t; + } + + gameObject.alpha = Linear(this.alphaStart, this.alphaEnd, t); + } + + complete() { + super.complete(); + if (this.mode === 1) { + this.parent.destroy(); + // Will also destroy this behavior + } + return this; + } + +} + +const MODE = { + stop: 0, + destroy: 1, + yoyo: 2 +} + +export default Fade; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/filechooser/CreateFileInput.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/filechooser/CreateFileInput.js new file mode 100644 index 000000000..3c4e35416 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/filechooser/CreateFileInput.js @@ -0,0 +1,20 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +var CreateFileInput = function (config) { + var fileInput = document.createElement('input'); + fileInput.type = 'file'; + + var accept = GetValue(config, 'accept', ''); + var multiple = GetValue(config, 'multiple', false); + + fileInput.setAttribute('accept', accept); + if (multiple) { + fileInput.setAttribute('multiple', ''); + } else { + fileInput.removeAttribute('multiple'); + } + + return fileInput; +} + +export default CreateFileInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/filechooser/Open.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/filechooser/Open.d.ts new file mode 100644 index 000000000..7bc43551f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/filechooser/Open.d.ts @@ -0,0 +1,19 @@ +export default Open; + +declare namespace Open { + + interface IConfig { + accept?: string, + multiple?: boolean + closeDelay?: number + } + + interface IResult { + files: File[] + } +} + +declare function Open( + game: Phaser.Game | Phaser.Scene | Phaser.GameObjects.GameObject, + config?: Open.IConfig +): Promise; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/filechooser/Open.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/filechooser/Open.js new file mode 100644 index 000000000..1e7e0e776 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/filechooser/Open.js @@ -0,0 +1,20 @@ +// Note: Not working in iOS9+ + +import CreateFileInput from './CreateFileInput.js'; +import ClickPromise from '../../gameobjects/dom/filechooser/ClickPromise.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var Open = function (game, config) { + // game: game, scene, or game object + var closeDelay = GetValue(config, 'closeDelay', 200); + var fileInput = CreateFileInput(config); + fileInput.click(); + return ClickPromise({ game, fileInput, closeDelay }) + .then(function (result) { + fileInput.remove(); + return Promise.resolve(result); + }) +} + +export default Open; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/flash/Flash.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/flash/Flash.d.ts new file mode 100644 index 000000000..39c8de0ea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/flash/Flash.d.ts @@ -0,0 +1,40 @@ +import TickTask from '../../utils/componentbase/TickTask'; + +export default Flash; + +declare namespace Flash { + + interface IConfig { + duration?: number, + repeat?: number, + } + + namespace Events { + type CompleteCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + flash: Flash + ) => void; + } +} + +declare class Flash extends TickTask { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Flash.IConfig + ) + + setEnable(enable?: boolean): this; + enable: boolean; + + flash(duration?: number, repeat?: number): this; + flash(config: { + duration?: number, + repeat?: number, + }): this; + + setDuration(duration: number): this; + duration: number; + + setRepeat(repeat: number): this; + repeat: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/flash/Flash.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/flash/Flash.js new file mode 100644 index 000000000..d01cfb5c5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/flash/Flash.js @@ -0,0 +1,109 @@ +import TickTask from '../../utils/componentbase/timerticktask/TimerTask'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Flash extends TickTask { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + // this.timer + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.timer.resetFromJSON(GetValue(o, 'timer')); + this.isRunning = GetValue(o, 'isRunning', false); + this.setEnable(GetValue(o, 'enable', true)); + this.setDuration(GetValue(o, 'duration', 500)); + this.setRepeat(GetValue(o, 'repeat', 2)); + return this; + } + + toJSON() { + return { + timer: this.timer.toJSON(), + enable: this.enable, + isRunning: this.isRunning, + duration: this.duration, + repeat: this.repeat, + }; + } + + setEnable(e) { + if (e == undefined) { + e = true; + } + this.enable = e; + return this; + } + + setDuration(duration) { + this.duration = duration; + return this; + } + + setRepeat(repeat) { + this.repeat = repeat; + return this; + } + + start(duration, repeat) { + if (typeof (duration) !== 'number') { + var config = duration; + duration = GetValue(config, 'duration', undefined); + repeat = GetValue(config, 'repeat', undefined); + } + if (duration !== undefined) { + this.setDuration(duration); + } + if (repeat !== undefined) { + this.setRepeat(repeat); + } + + this.timer + .setDuration(this.duration) + .setRepeat(this.repeat); + + if (this.isRunning) { + // pend task + this.timer.repeatCounter = -1; + } else { + super.start(); + } + return this; + } + + flash(duration, repeat) { + this.start(duration, repeat); + return this; + } + + stop() { + this.parent.setVisible(true); + super.stop(); + return this; + } + + update(time, delta) { + if ((!this.isRunning) || (!this.enable)) { + return this; + } + + var gameObject = this.parent; + if (!gameObject.active) { + return this; + } + + this.timer.update(time, delta); + gameObject.setVisible((this.timer.t > 0.5)); + + if (this.timer.isDone) { + this.complete(); + } + return this; + } +} + +export default Flash; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/flip/Flip.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/flip/Flip.d.ts new file mode 100644 index 000000000..0ec7d3332 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/flip/Flip.d.ts @@ -0,0 +1,62 @@ +// import * as Phaser from 'phaser'; +import EaseValueTaskBase from "../../utils/componentbase/tweentask/EaseValueTaskBase"; + +export default Flip; + +declare namespace Flip { + + type FaceTypes = 0 | 1 | 'front' | 'back'; + + type FaceDefTypes = string | + { key?: string, frame?: string } | + ((gameObject: Phaser.GameObjects.GameObject) => void); + + type OrientationTypes = 0 | 1 | 'x' | 'y' | 'horizontal' | 'vertical'; + + interface IConfig { + face?: FaceTypes, + front?: FaceDefTypes, + back?: FaceDefTypes, + + orientation?: OrientationTypes, + duration?: number, + delay?: number, + ease?: string, + + eventEmitter?: boolean | Phaser.Events.EventEmitter + } + + namespace Events { + type CompleteCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + flip: Flip + ) => void; + } + +} + +declare class Flip extends EaseValueTaskBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Flip.IConfig + ) + + flip(duration?: number): this; + + setFace( + face: 0 | 1 | 'front' | 'back' + ): this; + toggleFace(): this; + face: number; + + setFrontFace( + key: string | + ((gameObject: Phaser.GameObjects.GameObject) => void), + frame?: string + ): this; + setBackFace( + key: string | + ((gameObject: Phaser.GameObjects.GameObject) => void), + frame?: string + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/flip/Flip.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/flip/Flip.js new file mode 100644 index 000000000..10e30e749 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/flip/Flip.js @@ -0,0 +1,139 @@ +import EaseValueTaskBase from '../../utils/componentbase/tweentask/EaseValueTaskBase.js'; +import GetFaceUpdatingCallback from './GetFaceUpdatingCallback.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const Linear = Phaser.Math.Linear; + +class Flip extends EaseValueTaskBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + // this.timer + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + this.setDuration(GetAdvancedValue(o, 'duration', 500)); + this.setEase(GetValue(o, 'ease', 'Sine')); + + this.setOrientation(GetValue(o, 'orientation', 0)); + this.setFrontFace(GetValue(o, 'front', undefined)); + this.setBackFace(GetValue(o, 'back', undefined)); + this.setFace(GetValue(o, 'face', 0)); + return this; + } + + setOrientation(orientation) { + if (typeof (orientation) === 'string') { + orientation = ORIENTATIONMODE[orientation]; + } + this.orientation = orientation; + return this; + } + + get face() { + return this._face; + } + + set face(face) { + if (typeof (face) === 'string') { + face = FACEMODE[face]; + } + this._face = face; + if ((face === 0) && this.frontFaceCallback) { + this.frontFaceCallback(this.parent); + } else if ((face === 1) && this.backFaceCallback) { + this.backFaceCallback(this.parent); + } + } + + setFace(face) { + this.face = face; + return this; + } + + toggleFace() { + var newFace = (this.face === 0) ? 1 : 0; + this.setFace(newFace); + return this; + } + + setFrontFace(key, frame) { + this.frontFaceCallback = GetFaceUpdatingCallback(key, frame, this.parent); + return this; + } + + setBackFace(key, frame) { + this.backFaceCallback = GetFaceUpdatingCallback(key, frame, this.parent); + return this; + } + + start() { + if (this.timer.isRunning) { + return this; + } + + var gameObject = this.parent; + if (this.orientation === 0) { + this.scale0 = gameObject.scaleX; + } else { + this.scale0 = gameObject.scaleY; + } + + this.timer + .setDelay(this.delay) + .setDuration(this.duration / 2) + .setRepeat(1); // 2 times + + super.start(); + return this; + } + + flip(duration) { + if (this.isRunning) { + return this; + } + if (duration !== undefined) { + this.setDuration(duration); + } + this.start(); + return this; + } + + updateGameObject(gameObject, timer) { + if (timer.justRestart) { + this.toggleFace(); + } + + var t = timer.t; + if (timer.isOddIteration) { // Yoyo + t = 1 - t; + } + t = this.easeFn(t); + + var value = Linear(this.scale0, 0, t); + if (this.orientation === 0) { + gameObject.scaleX = value; + } else { + gameObject.scaleY = value; + } + } +} + +const ORIENTATIONMODE = { + x: 0, + horizontal: 0, + y: 1, + vertical: 1, +} + +const FACEMODE = { + front: 0, + back: 1, +} + +export default Flip; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/flip/GetFaceUpdatingCallback.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/flip/GetFaceUpdatingCallback.js new file mode 100644 index 000000000..fefe2ee67 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/flip/GetFaceUpdatingCallback.js @@ -0,0 +1,26 @@ +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +var GetFrameUpdatingCallback = function (key, frame, gameObject) { + var callback; + if (key === undefined) { + key = gameObject.texture.key; + frame = gameObject.frame.name; + } else if (IsPlainObject(key)) { + var config = key; + key = GetValue(config, 'key', gameObject.texture.key); + frame = GetValue(config, 'frame', gameObject.frame.name); + } else if (typeof (key) === 'string') { + } else { + callback = key; + } + + if (callback === undefined) { + callback = function (gameObject) { + gameObject.setTexture(key, frame); + } + } + return callback; +} + +export default GetFrameUpdatingCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEdit.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEdit.d.ts new file mode 100644 index 000000000..13e730f79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEdit.d.ts @@ -0,0 +1,31 @@ +import HiddenTextEditBase from './HiddenTextEditBase'; + +export default HiddenTextEdit; + +declare namespace HiddenTextEdit { + interface IConfig extends HiddenTextEditBase.IConfig { + cursor?: string; + cursorFlashDuration?: number; + } + + type UpdateTextCallbackType = ( + newText: string, + hiddenInputText: HiddenTextEdit, + ) => string; +} + +declare class HiddenTextEdit extends HiddenTextEditBase { + constructor( + textObject: Phaser.GameObjects.GameObject, + config?: HiddenTextEdit.IConfig + ); + + setCursor( + s: string + ): this; + readonly cursor: string; + + setCursorFlashDuration( + duration: number + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEdit.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEdit.js new file mode 100644 index 000000000..3cdad8e9b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEdit.js @@ -0,0 +1,97 @@ +import HiddenTextEditBase from './HiddenTextEditBase.js'; +import NumberInputUpdateCallback from './defaultcallbacks/NumberInputUpdateCallback.js'; +import GetTickDelta from '../../utils/system/GetTickDelta.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Wrap = Phaser.Math.Wrap; + +class HiddenTextEdit extends HiddenTextEditBase { + constructor(gameObject, config) { + if (config === undefined) { + config = {}; + } + + if (config.onUpdate === 'number') { + config.onUpdate = NumberInputUpdateCallback; + } + + super(gameObject, config); + // this.parent = gameObject; + + this.setCursor(GetValue(config, 'cursor', '|')); + this.setCursorFlashDuration(GetValue(config, 'cursorFlashDuration', 1000)); + this.cursorFlashTimer = 0; + + } + + initText() { + this.cursorFlashTimer = 0; + this.prevCursorPosition = undefined; + this.setText(this.parent.text); + this.setCursorPosition(); + + return this; + } + + updateText() { + var textObject = this.parent; + var text = this.text; + + if (this.onUpdateCallback) { + var newText = this.onUpdateCallback(text, textObject, this); + if (newText != null) { + text = newText; + } + } + + if (this.isOpened && this.hasCursor) { + // Insert Cursor + var cursorPosition = this.cursorPosition; + text = text.substring(0, cursorPosition) + this.cursor + text.substring(cursorPosition); + + if (this.prevCursorPosition !== cursorPosition) { + // console.log(cursorPosition); + this.prevCursorPosition = cursorPosition; + } + } + + if (textObject.text !== text) { + textObject.setText(text); + this.emit('textchange', text, textObject, this); + } + + return this; + } + + setCursor(s) { + this._cursor = s; + this.hasCursor = s && (s !== ''); + return s; + } + + setCursorFlashDuration(duration) { + this.cursorFlashDuration = duration; + return this; + } + + get cursor() { + if (!this._isFocused) { + return this._cursor; + } + + // Flash Cursor + var cursor; + if (this.cursorFlashTimer < (this.cursorFlashDuration / 2)) { + cursor = this._cursor; + } else { + cursor = ' '; + } + + var timerValue = this.cursorFlashTimer + GetTickDelta(this.scene); + this.cursorFlashTimer = Wrap(timerValue, 0, this.cursorFlashDuration); + return cursor; + } + +} + +export default HiddenTextEdit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEditBase.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEditBase.d.ts new file mode 100644 index 000000000..467e4810e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEditBase.d.ts @@ -0,0 +1,123 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default HiddenTextEditBase; + +declare namespace HiddenTextEditBase { + type OnOpenCallbackType = ( + textObject: Phaser.GameObjects.GameObject, + hiddenInputText: HiddenTextEditBase, + ) => void; + + type OnCloseCallbackType = ( + textObject: Phaser.GameObjects.GameObject, + hiddenInputText: HiddenTextEditBase, + ) => void; + + type OnUpdateCallbackType = ( + text: string, + textObject: Phaser.GameObjects.GameObject, + hiddenInputText: HiddenTextEditBase, + ) => void | string; + + + interface IConfig { + enterClose?: boolean; + + onOpen: OnOpenCallbackType; + + onClose: OnCloseCallbackType; + + onUpdate: OnUpdateCallbackType | 'number'; + + // Copy from InputText + type?: string, + + // Element properties + id?: string, + text?: string, + maxLength?: number, + minLength?: number, + placeholder?: string, + tooltip?: string, + readOnly?: boolean, + spellCheck?: boolean, + autoComplete?: 'on' | 'off', + + // Style properties + align?: string, + paddingLeft?: string, + paddingRight?: string, + paddingTop?: string, + paddingBottom?: string, + fontFamily?: string, + fontSize?: string, + color?: string, + border?: number, + backgroundColor?: string, + borderColor?: string, + outline?: string, + + selectAll?: boolean, + } + + type UpdateTextCallbackType = ( + newText: string, + hiddenInputText: HiddenTextEditBase, + ) => string; +} + +declare class HiddenTextEditBase extends ComponentBase { + constructor( + textObject: Phaser.GameObjects.GameObject, + config?: HiddenTextEditBase.IConfig + ); + + setEnterClose( + value?: boolean + ): this; + + open(): this; + close(): this; + readonly isOpened: boolean; + + // Copy from InputText + setText(text: string): this; + text: string; + + selectText( + selectionStart?: number, + selectionEnd?: number + ): this; + selectAll(): this; + readonly selectionStart: number; + readonly selectionEnd: number; + readonly selectedText: string; + + setCursorPosition(value: number): this; + cursorPosition: number; + + scrollToBottom(): this; + + getStyle(key: string): string; + + setStyle(key: string, value?: number | string): this; + + setFocus(): this; + setBlur(): this; + readonly isFocused: boolean; + + setFontColor(color: string): this; + fontColor: string; + + setMaxLength(value: number): this; + maxLength: number; + + setMinLength(value: number): this; + minLength: number; + + setPlaceholder(value: string): this; + placeholder: string; + + setTooltip(value: string): this; + tooltip: string; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEditBase.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEditBase.js new file mode 100644 index 000000000..5aeb06ea5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/HiddenTextEditBase.js @@ -0,0 +1,353 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import CopyElementConfig from './methods/CopyElementConfig.js'; +import IsPointerInHitArea from '../../utils/input/IsPointerInHitArea.js'; +import Methods from './methods/Methods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class HiddenTextEditBase extends ComponentBase { + constructor(gameObject, config) { + super(gameObject); + // this.parent = gameObject; + + var textType = GetValue(config, 'inputType', undefined); + if (textType === undefined) { + textType = GetValue(config, 'type', 'text'); + } + + this.setEnterCloseEnable(GetValue(config, 'enterClose', (textType !== 'textarea'))); + + var onOpen = GetValue(config, 'onOpen', undefined); + if (!onOpen) { + onOpen = GetValue(config, 'onFocus', undefined); + } + this.onOpenCallback = onOpen; + + var onClose = GetValue(config, 'onClose', undefined); + if (!onClose) { + onClose = GetValue(config, 'onBlur', undefined); + } + this.onCloseCallback = onClose; + + this.onUpdateCallback = GetValue(config, 'onUpdate', undefined); + + this.isOpened = false; + + gameObject + .on('pointerdown', function () { + this.open(); + }, this) + .setInteractive() + + this.nodeConfig = CopyElementConfig(config); + // Create/remove input text element when opening/closing editor + this.node = undefined; + } + + destroy() { + // this.parent.off('pointerdown', this.open, this); + + this.close(); + + super.destroy(); + } + + onClickOutside(pointer) { + if (!IsPointerInHitArea(this.parent, pointer)) { + this.close(); + } + } + + setEnterCloseEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.enterCloseEnable = enable; + return this; + } + + // Override + initText() { + } + + // Override + updateText() { + } + + // Copy from InputText class + get text() { + if (!this.node) { + return ''; + } + return this.node.value; + } + + set text(value) { + if (!this.node) { + return; + } + this.node.value = value; + } + + setText(value) { // Override + this.text = value; + return this; + } + + get maxLength() { + return this.nodeConfig.maxLength; + } + + set maxLength(value) { + this.nodeConfig.maxLength = value; + + if (this.node) { + this.node.maxLength = value; + } + } + + setMaxLength(value) { + this.maxLength = value; + return this; + } + + get minLength() { + return this.nodeConfig.minLength; + } + + set minLength(value) { + this.nodeConfig.minLength = value; + + if (this.node) { + this.node.minLength = value; + } + } + + setMinLength(value) { + this.minLength = value; + return this; + } + + get placeholder() { + return this.node.placeholder; + } + + set placeholder(value) { + if (!this.node) { + return; + } + this.node.placeholder = value; + } + + setPlaceholder(value) { + this.placeholder = value; + return this; + } + + selectText(selectionStart, selectionEnd) { + if (!this.node) { + return this; + } + if (selectionStart === undefined) { + this.node.select(); + } else { + this.node.setSelectionRange(selectionStart, selectionEnd); + } + return this; + } + + selectAll() { + this.selectText(); + return this; + } + + get selectionStart() { + if (!this.node) { + return 0; + } + return this.node.selectionStart; + } + + get selectionEnd() { + if (!this.node) { + return 0; + } + return this.node.selectionEnd; + } + + get selectedText() { + if (!this.node) { + return ''; + } + var node = this.node; + return node.value.substring(node.selectionStart, node.selectionEnd); + } + + get cursorPosition() { + if (!this.node) { + return 0; + } + return this.node.selectionStart; + } + + set cursorPosition(value) { + if (!this.node) { + return; + } + this.node.setSelectionRange(value, value); + } + + setCursorPosition(value) { + if (value === undefined) { + value = this.text.length; + } else if (value < 0) { + value = this.text.length + value; + } + + this.cursorPosition = value; + return this; + } + + get tooltip() { + if (!this.node) { + return ''; + } + return this.node.title; + } + + set tooltip(value) { + if (!this.node) { + return this; + } + this.node.title = value; + } + + setTooltip(value) { + this.tooltip = value; + return this; + } + + setTextChangedCallback(callback) { + this.onTextChanged = callback; + return this; + } + + get readOnly() { + return this.nodeConfig.readOnly; + } + + set readOnly(value) { + this.nodeConfig.readOnly = value; + + if (this.node) { + this.node.readOnly = value; + } + } + + setReadOnly(value) { + if (value === undefined) { + value = true; + } + this.readOnly = value; + return this; + } + + get spellCheck() { + if (!this.node) { + return ''; + } + return this.node.spellcheck; + } + + set spellCheck(value) { + if (!this.node) { + return; + } + this.node.spellcheck = value; + } + + setSpellCheck(value) { + this.spellCheck = value; + return this; + } + + get fontColor() { + if (!this.node) { + return undefined; + } + return this.node.style.color; + } + + set fontColor(value) { + if (!this.node) { + return; + } + this.node.style.color = value; + } + + setFontColor(value) { + this.fontColor = value; + return this; + } + + setStyle(key, value) { + if (!this.node) { + return this; + } + this.node.style[key] = value; + return this; + } + + getStyle(key) { + if (!this.node) { + return undefined; + } + return this.node.style[key]; + } + + scrollToBottom() { + if (!this.node) { + return this; + } + this.node.scrollTop = this.node.scrollHeight; + return this; + } + + setEnabled(enabled) { + if (!this.node) { + return this; + } + if (enabled === undefined) { + enabled = true; + } + this.node.disabled = !enabled; + return this; + } + + setBlur() { + if (!this.node) { + return this; + } + this.node.blur(); + return this; + } + + setFocus() { + if (!this.node) { + return this; + } + this.node.focus(); + return this; + } + + get isFocused() { + return this.isOpened; + } +} + +Object.assign( + HiddenTextEditBase.prototype, + Methods, +) + +export default HiddenTextEditBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/defaultcallbacks/NumberInputUpdateCallback.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/defaultcallbacks/NumberInputUpdateCallback.js new file mode 100644 index 000000000..a626b8288 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/defaultcallbacks/NumberInputUpdateCallback.js @@ -0,0 +1,23 @@ +var NumberInputUpdateCallback = function (text, textObject, hiddenInputText) { + text = text.replace(' ', ''); + var previousText = hiddenInputText.previousText; + if (text === previousText) { + return text; + } + + if (isNaN(text)) { + // Enter a NaN character, back to previous text + hiddenInputText.emit('nan', text, hiddenInputText); + + text = previousText; + var cursorPosition = hiddenInputText.cursorPosition - 1; + hiddenInputText.setText(text); + hiddenInputText.setCursorPosition(cursorPosition); + } else { + // New number text, update previous texr + hiddenInputText.previousText = text; + } + + return text; +} +export default NumberInputUpdateCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/Close.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/Close.js new file mode 100644 index 000000000..82fe9a001 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/Close.js @@ -0,0 +1,35 @@ +import { CloseLastOpenEditor } from './LastOpenedEditor.js'; +import RemoveElement from './RemoveElement.js'; + +var Close = function () { + // Already closed + if (!this.isOpened) { + return this; + } + + CloseLastOpenEditor(this); + + this.setBlur(); + + this.isOpened = false; + + this.updateText(); + + this.scene.sys.events.off('postupdate', this.updateText, this); + + this.scene.input.off('pointerdown', this.onClickOutside, this); + + if (this.onCloseCallback) { + this.onCloseCallback(this.parent, this); + } + + // Remove input text element when closing editor + RemoveElement(this.node); + this.node = undefined; + + this.emit('close', this); + + return this; +} + +export default Close; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/CopyElementConfig.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/CopyElementConfig.js new file mode 100644 index 000000000..e50c6d15e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/CopyElementConfig.js @@ -0,0 +1,22 @@ +import { + ElementProperties, + StyleProperties, +} from './InputTextProperties.js'; +import CopyProperty from '../../../utils/object/CopyProperty.js'; + +var CopyElementConfig = function (from) { + if (from === undefined) { + from = {}; + } + var to = {}; + + CopyProperty(from, to, 'inputType'); + CopyProperty(from, to, 'type'); + CopyProperty(from, to, 'style'); + CopyProperty(from, to, StyleProperties); + CopyProperty(from, to, ElementProperties); + + return to; +} + +export default CopyElementConfig; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/CreateElement.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/CreateElement.js new file mode 100644 index 000000000..54717b4bd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/CreateElement.js @@ -0,0 +1,49 @@ +import { + ElementProperties, + StyleProperties, +} from './InputTextProperties.js'; +import SetPrpoerties from '../../../gameobjects/dom/utils/SetProperties.js'; +import StopPropagationTouchEvents from '../../../gameobjects/dom/utils/StopPropagationTouchEvents.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var CreateElement = function (parent, config) { + var element; + var textType = GetValue(config, 'inputType', undefined); + if (textType === undefined) { + textType = GetValue(config, 'type', 'text'); + } + if (textType === 'textarea') { + element = document.createElement('textarea'); + element.style.resize = 'none'; + } else { + element = document.createElement('input'); + element.type = textType; + } + + var style = GetValue(config, 'style', undefined); + // Apply other style properties + var elementStyle = element.style; + SetPrpoerties(StyleProperties, style, elementStyle); + // Set style + elementStyle.position = 'absolute'; + elementStyle.opacity = 0; + elementStyle.pointerEvents = 'none'; + elementStyle.zIndex = 0; + // hide native blue text cursor on iOS + elementStyle.transform = 'scale(0)'; + + SetPrpoerties(ElementProperties, config, element); + + // Don't propagate touch/mouse events to parent(game canvas) + StopPropagationTouchEvents(element); + + // Attach element to fullscreenTarget in full screen mode + var scaleManager = parent.scene.sys.scale; + var parentElement = (scaleManager.isFullscreen) ? scaleManager.fullscreenTarget : document.body; + parentElement.appendChild(element); + + return element; +} + +export default CreateElement; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/InputTextProperties.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/InputTextProperties.js new file mode 100644 index 000000000..c6dff76f3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/InputTextProperties.js @@ -0,0 +1,14 @@ +const ElementProperties = { + maxLength: ['maxLength', undefined], + minLength: ['minLength', undefined], + readOnly: ['readOnly', false], +}; + +const StyleProperties = { + direction: ['direction', undefined] +}; + +export { + ElementProperties, + StyleProperties, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/LastOpenedEditor.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/LastOpenedEditor.js new file mode 100644 index 000000000..ee0f6f509 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/LastOpenedEditor.js @@ -0,0 +1,27 @@ +var LastOpenedEditor = undefined; + +var SetLastOpenedEditor = function (editor) { + if (editor === LastOpenedEditor) { + return; + } + + if (LastOpenedEditor !== undefined) { + LastOpenedEditor.close(); + } + + LastOpenedEditor = editor; +} + +var CloseLastOpenEditor = function (editor) { + if (editor !== LastOpenedEditor) { + return; + } + + // Don't call `LastOpenedEditor.close()` + LastOpenedEditor = undefined; +} + +export { + SetLastOpenedEditor, + CloseLastOpenEditor +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/Methods.js new file mode 100644 index 000000000..f5647826d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/Methods.js @@ -0,0 +1,9 @@ +import Open from './Open.js'; +import Close from './Close.js'; + +var Methods = { + open: Open, + close: Close, +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/Open.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/Open.js new file mode 100644 index 000000000..ffcd4d7c4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/Open.js @@ -0,0 +1,46 @@ +import { SetLastOpenedEditor } from './LastOpenedEditor.js'; +import CreateElement from './CreateElement.js'; + +var Open = function () { + // Already opened + if (this.isOpened) { + return this; + } + // Read only + if (this.readOnly) { + return this; + } + + SetLastOpenedEditor(this); + + this.isOpened = true; + + if (!this.node) { + // Create input text element when opening editor + this.node = CreateElement(this, this.nodeConfig); + } + + this.setFocus(); + + this.initText(); + + if (this.enterCloseEnable) { + this.scene.input.keyboard.once('keydown-ENTER', this.close, this); + } + + // There is no cursor-position-change event, + // so updating cursor position every tick + this.scene.sys.events.on('postupdate', this.updateText, this); + + this.scene.input.on('pointerdown', this.onClickOutside, this); + + if (this.onOpenCallback) { + this.onOpenCallback(this.parent, this); + } + + this.emit('open', this); + + return this; +} + +export default Open; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/RemoveElement.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/RemoveElement.js new file mode 100644 index 000000000..cb9f2e91e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/hiddentextedit/methods/RemoveElement.js @@ -0,0 +1,12 @@ +var RemoveElement = function (element) { + if (!element) { + return; + } + + var parentElement = element.parentElement; + if (parentElement) { + parentElement.removeChild(element); + } +} + +export default RemoveElement; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/interception/Interception.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/interception/Interception.d.ts new file mode 100644 index 000000000..77b05c40d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/interception/Interception.d.ts @@ -0,0 +1,29 @@ +import TickTask from '../../utils/componentbase/TickTask'; + +export default Interception; + +declare namespace Interception { + + interface IConfig { + target?: Phaser.GameObjects.GameObject, + enable?: boolean + } +} + +declare class Interception extends TickTask { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Interception.IConfig + ) + + setEnable(enable?: boolean): this; + enable: boolean; + + setTarget( + gameObject?: Phaser.GameObjects.GameObject + ): this; + target: Phaser.GameObjects.GameObject | undefined; + + readonly predictedPosition: { x: number, y: number }; + readonly predictedAngle: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/interception/Interception.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/interception/Interception.js new file mode 100644 index 000000000..3e0d931d1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/interception/Interception.js @@ -0,0 +1,144 @@ +import TickTask from '../../utils/componentbase/SceneUpdateTickTask.js'; +import SpeedMonitor from '../../utils/speedmonitor/SpeedMonitor.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Vector2 = Phaser.Math.Vector2; +const Distance = Phaser.Math.Distance.Between; +const AngleBetweenPoint = Phaser.Math.Angle.BetweenPoints; + +class Interception extends TickTask { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this._target = undefined; + this.mySpeedMonitor = new SpeedMonitor(); + this.targetSpeedMonitor = new SpeedMonitor(); + this.predictedPosition = new Vector2(); + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.isRunning = GetValue(o, 'isRunning', false); + this.setEnable(GetValue(o, 'enable', true)); + this.setTarget(GetValue(o, 'target', undefined)); + return this; + } + + toJSON() { + return { + isRunning: this.isRunning, + duration: this.duration, + tickingMode: this.tickingMode + }; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.setTarget(); + + super.shutdown(fromScene); + } + + setEnable(e) { + if (e == undefined) { + e = true; + } + this.enable = e; + return this; + } + + get target() { + return this._target; + } + + set target(target) { + if (target == null) { + target = undefined; + } + + if (this.target === target) { + // Do nothing + } else { + // Remove previous target + if (this.target) { + this.target.off('destroy', this.onTargetDestroy, this); + } + + // Add new target + if (target) { + target.once('destroy', this.onTargetDestroy, this); + } + this._target = target; + } + + if (this.isRunning) { + if (this.target === undefined) { + super.stop(); + } + } else { // !this.isRunning + if (this.target !== undefined) { + // Start speed monitor + this.mySpeedMonitor.init(this.parent.x, this.parent.y); + this.targetSpeedMonitor.init(this.target.x, this.target.y); + super.start(); + } + } + return this; + } + + setTarget(target) { + this.target = target; + return this; + } + + onTargetDestroy(target, fromScene) { + this.setTarget(); + return this; + } + + update(time, delta) { + if (!this.isRunning) { + return this; + } + + if (this.target === undefined) { + return this; + } else if (!this.enable) { + this.predictedPosition.copy(this.target); + return this; + } + + var gameObject = this.parent; + delta /= 1000; // delta in sec + this.mySpeedMonitor.update(gameObject.x, gameObject.y, delta); + this.targetSpeedMonitor.update(this.target.x, this.target.y, delta); + + var relatedVelocityX = this.targetSpeedMonitor.velocity.x - this.mySpeedMonitor.velocity.x; + var relatedVelocityY = this.targetSpeedMonitor.velocity.y - this.mySpeedMonitor.velocity.y; + if ((relatedVelocityX === 0) && (relatedVelocityY === 0)) { + // Target and mine is moving parallelly + this.predictedPosition.copy(this.target); + } else { + var relatedSpeed = Distance(0, 0, relatedVelocityX, relatedVelocityY); + var distanceToTarget = Distance(this.target.x, this.target.y, gameObject.x, gameObject.y); + var time = distanceToTarget / relatedSpeed; + this.predictedPosition.set( + this.target.x + (this.targetSpeedMonitor.velocity.x * time), + this.target.y + (this.targetSpeedMonitor.velocity.y * time), + ) + } + return this; + } + + get predictedAngle() { + return AngleBetweenPoint(this.parent, this.predictedPosition); + } +} + +export default Interception; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/loadingprogress/GetProgress.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/loadingprogress/GetProgress.js new file mode 100644 index 000000000..cb9e95af5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/loadingprogress/GetProgress.js @@ -0,0 +1,9 @@ +var GetProgress = function (scene) { + var loader = scene.load; + var total = loader.totalToLoad - 1; + var remainder = loader.list.size + loader.inflight.size - 1; + var progress = 1 - (remainder / total); + return progress; +} + +export default GetProgress; diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/loadingprogress/LoadingProgress.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/loadingprogress/LoadingProgress.d.ts new file mode 100644 index 000000000..5e75c87a8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/loadingprogress/LoadingProgress.d.ts @@ -0,0 +1,33 @@ +export default LoadingProgress; + +declare namespace LoadingProgress { + type ProgressCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + progress: number + ) => void; + + type TransitCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + duration: number + ) => void; + + interface IConfig { + duration?: { + in?: number, + out?: number, + }, + + progress?: ProgressCallbackType, + + transitIn?: TransitCallbackType, + + transitOut?: TransitCallbackType, + } +} + +declare class LoadingProgress extends Phaser.Events.EventEmitter { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: LoadingProgress.IConfig + ); +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/loadingprogress/LoadingProgress.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/loadingprogress/LoadingProgress.js new file mode 100644 index 000000000..5f5895269 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/loadingprogress/LoadingProgress.js @@ -0,0 +1,74 @@ +import OpenCloseTransition from '../openclosetransition/OpenCloseTransition.js'; +import PopUp from '../../popup.js'; +import ScaleDown from '../scale/ScaleDown.js'; +import NOOP from '../../utils/object/NOOP.js'; +import AwaitLoader from '../../awaitloader.js'; +import GetProgress from './GetProgress.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class LoadingProgress extends OpenCloseTransition { + constructor(gameObject, config) { + if (config === undefined) { + config = {}; + } + if (!config.hasOwnProperty('transitIn')) { + config.transitIn = PopUp; + } + if (!config.hasOwnProperty('transitOut')) { + config.transitOut = ScaleDown; + } + + config.destroy = true; + + super(gameObject, config); + // this.parent = gameObject; + // this.scene + + this.setProgressCallback(GetValue(config, 'progress')); + + this.start(); + } + + setProgressCallback(callback) { + if (!callback) { + callback = NOOP; + } + + this.progressCallback = callback; + return this; + } + + start() { + var self = this; + AwaitLoader.call(this.scene.load, function (successCallback, failureCallback) { + self.once('close', successCallback); + }) + + this.requestOpen(); + } + + onOpen() { + this.scene.load.on('progress', this.onProgress, this); + this.emit('open', this.parent, this); + super.onOpen(); + this.onProgress(); // Might requestClose if progress === 1 + } + + onClose() { + this.scene.load.off('progress', this.onProgress, this); + this.emit('close', this.closeEventData); + super.onClose(); + } + + onProgress() { + var progress = GetProgress(this.scene); + this.progressCallback(this.parent, progress); + this.emit('progress', progress); + if (progress === 1) { + this.requestClose(); + } + } +} + +export default LoadingProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/CreateCover.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/CreateCover.js new file mode 100644 index 000000000..c8732dac3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/CreateCover.js @@ -0,0 +1,24 @@ +import Cover from '../../gameobjects/shape/cover/Cover.js'; + +var CreateCover = function (gameObject, config) { + var scene = gameObject.scene; + var cover = new Cover(scene, config); + scene.add.existing(cover); + + // Put cover behind game object + if (gameObject.isRexContainerLite) { + gameObject.moveDepthBelow(cover); + gameObject.pin(cover, { + syncPosition: false, + syncRotation: false, + syncScale: false, + syncAlpha: false, + syncScrollFactor: false + }); + } else { + scene.children.moveBelow(cover, gameObject); + } + return cover; +} + +export default CreateCover; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/DefaultCoverTransitCallbacks.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/DefaultCoverTransitCallbacks.js new file mode 100644 index 000000000..f1b9b750c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/DefaultCoverTransitCallbacks.js @@ -0,0 +1,21 @@ +import FadeIn from '../../fade-in.js'; +import FadeOutDestroy from '../../fade-out-destroy.js'; + +var DefaultCoverTransitInCallback = function (cover, duration) { + if (cover._modalAlphaSave !== undefined) { + cover.alpha = cover._modalAlphaSave; + } else { + cover._modalAlphaSave = cover.alpha; + } + + FadeIn(cover, duration, cover.alpha); +} + +var DefaultCoverTransitOutCallback = function (cover, duration) { + FadeOutDestroy(cover, duration, false); +} + +export { + DefaultCoverTransitInCallback, + DefaultCoverTransitOutCallback +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/DefaultTransitCallbacks.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/DefaultTransitCallbacks.js new file mode 100644 index 000000000..7b8d1c95f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/DefaultTransitCallbacks.js @@ -0,0 +1,37 @@ +import PopUp from '../../popup.js'; +import ScaleDownDestroy from '../../scale-down-destroy.js'; +import FadeIn from '../../fade-in.js'; +import FadeOutDestroy from '../../fade-out-destroy.js'; + +export default { + popUp(gameObject, duration) { + if (gameObject._modalScaleSave !== undefined) { + gameObject.scaleX = gameObject._modalScaleSave; + gameObject.scaleY = gameObject._modalScaleSave; + } else { + gameObject._modalScaleSave = gameObject.scaleX; + } + + PopUp(gameObject, duration); + }, + + scaleDown(gameObject, duration) { + // Don't destroy here + ScaleDownDestroy(gameObject, duration, undefined, undefined, false); + }, + + fadeIn(gameObject, duration) { + if (gameObject._modalAlphaSave !== undefined) { + gameObject.alpha = gameObject._modalAlphaSave; + } else { + gameObject._modalAlphaSave = gameObject.alpha; + } + + FadeIn(gameObject, duration); + }, + + fadeOut(gameObject, duration) { + // Don't destroy here + FadeOutDestroy(gameObject, duration, false); + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/Modal.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/Modal.d.ts new file mode 100644 index 000000000..b450e020a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/Modal.d.ts @@ -0,0 +1,51 @@ +export default Modal; + +declare namespace Modal { + type TransitCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + duration: number + ) => void; + + interface IConfig { + cover?: { + color?: number, + alpha?: number, + transitIn?: TransitCallbackType | null, + transitOut?: TransitCallbackType | null, + }, + + manualClose?: boolean, + + clickOutsideClose?: boolean, + + anyTouchClose?: boolean, + + duration?: { + in?: number, + hold?: number, + out?: number, + }, + + transitIn?: 0 | 1 | 'popUp' | 'fadeIn' | TransitCallbackType, + + transitOut?: 0 | 1 | 'scaleDown' | 'fadeOut' | TransitCallbackType, + + destroy?: boolean, + + openOnStart?: boolean, + } +} + +declare class Modal extends Phaser.Events.EventEmitter { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Modal.IConfig + ); + + requestClose( + closeEventData?: unknown + ): this; + + setCoverTransitInCallback(callback?: Modal.TransitCallbackType): this; + setCoverTransitOutCallback(callback?: Modal.TransitCallbackType): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/Modal.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/Modal.js new file mode 100644 index 000000000..9e7dcabf9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/Modal.js @@ -0,0 +1,225 @@ +import OpenCloseTransition from '../openclosetransition/OpenCloseTransition.js'; +import CreateCover from './CreateCover.js'; +import DefaultTransitCallbacks from './DefaultTransitCallbacks.js'; +import { + DefaultCoverTransitInCallback, + DefaultCoverTransitOutCallback +} from './DefaultCoverTransitCallbacks.js'; +import IsPointInBounds from '../../utils/bounds/IsPointInBounds.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Modal extends OpenCloseTransition { + constructor(gameObject, config) { + if (config === undefined) { + config = {}; + } + if (config.transitIn == null) { + config.transitIn = TransitionMode.popUp; + } + if (config.transitOut == null) { + config.transitOut = TransitionMode.scaleDown; + } + + config.destroy = GetValue(config, 'destroy', true); + + super(gameObject, config); + // this.parent = gameObject; + // this.scene + + // Cover : key of modal, to block touch input + var coverConfig = GetValue(config, 'cover'); + this.cover = (coverConfig !== false) ? CreateCover(gameObject, coverConfig) : undefined; + if (this.cover) { + this.setCoverTransitInCallback(GetValue(coverConfig, 'transitIn', DefaultCoverTransitInCallback)); + this.setCoverTransitOutCallback(GetValue(coverConfig, 'transitOut', DefaultCoverTransitOutCallback)); + } + + // Close conditions: + var touchOutsideClose = GetValue(config, 'touchOutsideClose', false); + var timeOutDuration = GetValue(config, 'duration.hold', -1); + var timeOutClose = GetValue(config, 'timeOutClose', (timeOutDuration >= 0)); + var anyTouchClose = GetValue(config, 'anyTouchClose', false); + var manualClose = GetValue(config, 'manualClose', false); + + if (manualClose) { + touchOutsideClose = false; + anyTouchClose = false; + timeOutClose = false; + } + + if (anyTouchClose) { + touchOutsideClose = false; + } + + if (timeOutClose) { + this.setDisplayTime(timeOutDuration); + } else { + this.setDisplayTime(-1); + } + + // Registet touch-close event after opened + if (anyTouchClose) { + this.once('open', this.anyTouchClose, this); + } else if (touchOutsideClose) { + this.once('open', this.touchOutsideClose, this); + } + + if (GetValue(config, 'openOnStart', true)) { + // Run this.requestOpen() next tick + // User can register events before this.requestOpen() + this.delayCall(0, this.requestOpen, this); + } + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + // Registered in touchOutsideClose(), or anyTouchClose() + if (!this.cover) { + this.scene.input.off('pointerup', this.touchCloseCallback, this); + } + + if (this.cover && !fromScene) { + this.cover.destroy(); + this.cover = undefined; + } + + super.shutdown(fromScene); + } + + touchOutsideClose() { + if (this.cover) { + this.cover.on('pointerup', this.touchCloseCallback, this); + } else { + this.scene.input.on('pointerup', this.touchCloseCallback, this); + } + this.clickOutsideTest = true; + return this; + } + + anyTouchClose() { + if (this.cover) { + this.cover.once('pointerup', this.touchCloseCallback, this); + } else { + this.scene.input.once('pointerup', this.touchCloseCallback, this); + } + return this; + } + + touchCloseCallback(pointer) { + if (this.clickOutsideTest && IsPointInBounds(this.parent, pointer.worldX, pointer.worldY)) { + return; + } + this.requestClose(); + } + + runTransitionInCallback() { + var duration = super.runTransitionInCallback(); + + var cover = this.cover; + if (cover && this.coverTransitInCallback) { + this.coverTransitInCallback(cover, duration); + } + + return duration; + } + + runTransitionOutCallback() { + var duration = super.runTransitionOutCallback(); + + var cover = this.cover; + if (cover && this.coverTransitOutCallback) { + this.coverTransitOutCallback(cover, duration); + } + + return duration; + } + + onOpen() { + var duration = this.displayTime; + if (duration >= 0) { + this.delayCall( + duration, + this.requestClose, // callback + this // scope + ); + } + + this.emit('open', this.parent, this); + + super.onOpen(); + } + + onClose() { + this.emit('close', this.closeEventData); + + super.onClose(); + } + + setDisplayTime(time) { + this.displayTime = time; + return this; + } + + setTransitInCallback(callback) { + if (typeof (callback) === 'string') { + callback = TransitionMode[callback]; + } + + switch (callback) { + case TransitionMode.popUp: + callback = DefaultTransitCallbacks.popUp; + break; + case TransitionMode.fadeIn: + callback = DefaultTransitCallbacks.fadeIn; + break; + } + + super.setTransitInCallback(callback); + // callback = function(gameObject, duration) {} + return this; + } + + setTransitOutCallback(callback) { + if (typeof (callback) === 'string') { + callback = TransitionMode[callback]; + } + + switch (callback) { + case TransitionMode.scaleDown: + callback = DefaultTransitCallbacks.scaleDown; + break; + case TransitionMode.fadeOut: + callback = DefaultTransitCallbacks.fadeOut; + break; + } + + super.setTransitOutCallback(callback); + // callback = function(gameObject, duration) {} + return this; + } + + setCoverTransitInCallback(callback) { + this.coverTransitInCallback = callback; + return this; + } + + setCoverTransitOutCallback(callback) { + this.coverTransitOutCallback = callback; + return this; + } + +} + +const TransitionMode = { + popUp: 0, + fadeIn: 1, + scaleDown: 0, + fadeOut: 1, +} + +export default Modal; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/ModalPromise.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/ModalPromise.d.ts new file mode 100644 index 000000000..6b36dda9e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/ModalPromise.d.ts @@ -0,0 +1,18 @@ +import ModalBehavior from './Modal'; + +export function Modal( + gameObject: Phaser.GameObjects.GameObject, + config?: ModalBehavior.IConfig +): ModalBehavior; + +export function ModalPromise( + gameObject: Phaser.GameObjects.GameObject, + config?: ModalBehavior.IConfig +): Promise + +export function ModalClose( + gameObject: Phaser.GameObjects.GameObject, + closeEventData?: unknown +): void; + +export { ModalBehavior }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/ModalPromise.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/ModalPromise.js new file mode 100644 index 000000000..7e12f1f43 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/modal/ModalPromise.js @@ -0,0 +1,39 @@ +import ModalBehavior from './Modal.js'; + +var Modal = function (gameObject, config) { + var modalBehavior = new ModalBehavior(gameObject, config); + + // Route modal's 'open', 'close' event + modalBehavior.on('open', function () { + gameObject.emit('modal.open', modalBehavior); + }) + modalBehavior.on('close', function (closeEventData) { + gameObject.emit('modal.close', closeEventData, modalBehavior); + }) + + // Reigster 'modal.requestClose' event for invoking modalBehavior.requestClose() method + gameObject.on('modal.requestClose', modalBehavior.requestClose, modalBehavior); + /* + It is not necessary to turn off gameObject's 'modal.requestClose' event because that : + + - If `config.destroy` is `undefined` (or `true), gameObject and modalBehavior will be destroyed + - If `config.destroy` is `false` (for reusing dialog), keeping gameObject and modalBehavior + */ + + return modalBehavior; +} + +var ModalPromise = function (gameObject, config) { + var modalBehavior = Modal(gameObject, config); + return new Promise(function (resolve, reject) { + modalBehavior.once('close', function (closeEventData) { + resolve(closeEventData); + }); + }); +} + +var ModalClose = function (gameObject, closeEventData) { + gameObject.emit('modal.requestClose', closeEventData); +} + +export { Modal, ModalPromise, ModalClose }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/moveto/MoveTo.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/moveto/MoveTo.d.ts new file mode 100644 index 000000000..a66249ba9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/moveto/MoveTo.d.ts @@ -0,0 +1,48 @@ +import TickTask from '../../utils/componentbase/TickTask'; + +export default MoveTo; + +declare namespace MoveTo { + + interface IConfig { + speed?: number, + rotateToTarget?: boolean + } + + namespace Events { + type CompleteCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + moveTo: MoveTo + ) => void; + } +} + +declare class MoveTo extends TickTask { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: MoveTo.IConfig + ) + + setEnable(enable?: boolean): this; + enable: boolean; + + moveTo(x: number, y: number): this; + moveTo(config: { + x: number, + y: number, + speed?: number + }): this; + moveFrom(x: number, y: number): this; + moveFrom(config: { + x: number, + y: number, + speed?: number + }): this; + moveToward(angle: number, distance: number): this; + + setSpeed(speed: number): this; + speed: number; + + setRotateToTarget(enable?: boolean): this; + rotateToTarget: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/moveto/MoveTo.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/moveto/MoveTo.js new file mode 100644 index 000000000..c01f48a9a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/moveto/MoveTo.js @@ -0,0 +1,144 @@ +import TickTask from '../../utils/componentbase/SceneUpdateTickTask.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const DistanceBetween = Phaser.Math.Distance.Between; +const Lerp = Phaser.Math.Linear; +const AngleBetween = Phaser.Math.Angle.Between; + + +class MoveTo extends TickTask { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.isRunning = GetValue(o, 'isRunning', false); + this.setEnable(GetValue(o, 'enable', true)); + this.timeScale = GetValue(o, 'timeScale', 1); + this.setSpeed(GetValue(o, 'speed', 400)); + this.setRotateToTarget(GetValue(o, 'rotateToTarget', false)); + this.targetX = GetValue(o, 'targetX', 0); + this.targetY = GetValue(o, 'targetY', 0); + return this; + } + + toJSON() { + return { + isRunning: this.isRunning, + enable: this.enable, + timeScale: this.timeScale, + speed: this.speed, + rotateToTarget: this.rotateToTarget, + targetX: this.targetX, + targetY: this.targetY, + tickingMode: this.tickingMode + }; + } + + setEnable(e) { + if (e == undefined) { + e = true; + } + this.enable = e; + return this; + } + + setSpeed(speed) { + this.speed = speed; + return this; + } + + setRotateToTarget(rotateToTarget) { + this.rotateToTarget = rotateToTarget; + return this; + } + + moveTo(x, y) { + if (typeof (x) !== 'number') { + var config = x; + x = config.x; + y = config.y; + } + + this.targetX = x; + this.targetY = y; + super.start(); + this.emit('start', this.parent, this); + return this; + } + + moveFrom(x, y) { + if (typeof (x) !== 'number') { + var config = x; + x = config.x; + y = config.y; + } + + var gameObject = this.parent; + var targetX = gameObject.x; + var targetY = gameObject.y; + + gameObject.setPosition(x, y); + + this.moveTo(targetX, targetY); + + return this; + } + + moveToward(angle, distance) { + var gameObject = this.parent; + var targetX = gameObject.x + Math.cos(angle) * distance; + var targetY = gameObject.y + Math.sin(angle) * distance; + this.moveTo(targetX, targetY); + return this; + } + + update(time, delta) { + if ((!this.isRunning) || (!this.enable)) { + return this; + } + + var gameObject = this.parent; + if (!gameObject.active) { + return this; + } + + var curX = gameObject.x, + curY = gameObject.y; + var targetX = this.targetX, + targetY = this.targetY; + if ((curX === targetX) && (curY === targetY)) { + this.complete(); + return this; + } + + if ((this.speed === 0) || (delta === 0) || (this.timeScale === 0)) { + return this; + } + + var dt = (delta * this.timeScale) / 1000; + var movingDist = this.speed * dt; + var distToTarget = DistanceBetween(curX, curY, targetX, targetY); + var newX, newY; + if (movingDist < distToTarget) { + var t = movingDist / distToTarget; + newX = Lerp(curX, targetX, t); + newY = Lerp(curY, targetY, t); + } else { + newX = targetX; + newY = targetY; + } + + gameObject.setPosition(newX, newY); + if (this.rotateToTarget) { + gameObject.rotation = AngleBetween(curX, curY, newX, newY); + } + return this; + } +} + +export default MoveTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/OpenCloseTransition.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/OpenCloseTransition.d.ts new file mode 100644 index 000000000..f488d27d2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/OpenCloseTransition.d.ts @@ -0,0 +1,55 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default OpenCloseTransition; + +declare namespace OpenCloseTransition { + type TransitCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + duration: number + ) => void; + + interface IConfig { + + duration?: { + in?: number, + out?: number, + }, + + transitIn?: TransitCallbackType, + + transitOut?: TransitCallbackType, + + oneShot?: boolean, + destroy?: boolean, + opened?: boolean, + } +} + +declare class OpenCloseTransition extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: OpenCloseTransition.IConfig + ); + + setTransitInTime(duration: number): this; + transitInTime: number; + + setTransitOutTime(duration: number): this; + transitOutTime: number; + + setTransitInCallback( + callback?: OpenCloseTransition.TransitCallbackType + ): this; + transitInCallback: OpenCloseTransition.TransitCallbackType; + + setTransitOutCallback( + callback?: OpenCloseTransition.TransitCallbackType + ): this; + transitOutCallback: OpenCloseTransition.TransitCallbackType; + + requestOpen(openEventData?: any, duration?: number): this; + onOpen(): void; + + requestClose(closeEventData?: any, duration?: number): this; + onClose(): void; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/OpenCloseTransition.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/OpenCloseTransition.js new file mode 100644 index 000000000..4cdcfcc9a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/OpenCloseTransition.js @@ -0,0 +1,55 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import State from './State.js'; +import Methods from './methods/Methods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class OpenCloseTransition extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + // this.scene + + this.setTransitInTime(GetValue(config, 'duration.in', 200)); + this.setTransitOutTime(GetValue(config, 'duration.out', 200)); + this.setTransitInCallback(GetValue(config, 'transitIn')); + this.setTransitOutCallback(GetValue(config, 'transitOut')); + + this.oneShotMode = GetValue(config, 'destroy', false); + + this.delayCallTimer = undefined; + this._state = new State(this, { + eventEmitter: false, + initState: GetValue(config, 'initState', 'IDLE') + }); + this.openEventData = undefined; + this.closeEventData = undefined; + } + + get state() { + return this._state.state; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.transitInCallback = undefined; + this.transitOutCallback = undefined; + this.openEventData = undefined; + this.closeEventData = undefined; + + this.removeDelayCall(); + + super.shutdown(fromScene); + } +} + +Object.assign( + OpenCloseTransition.prototype, + Methods, +) + +export default OpenCloseTransition; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/State.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/State.js new file mode 100644 index 000000000..957a91198 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/State.js @@ -0,0 +1,105 @@ +import FSM from '../../fsm.js'; + +/* +graph TD + +IDLE --> |"requestOpen()"| TRANS_OPNE["TRAN_OPEN
    runTransitionInCallback()"] +TRANS_OPNE --> |transitInTime| OPEN +OPEN --> |"requestClose()"| TRANS_CLOSE["TRANS_CLOSE
    runTransitionOutCallback()"] +TRANS_CLOSE --> |transitOutTime| CLOSE +CLOSE --> |"requestOpen()"| TRANS_OPNE +*/ + +class State extends FSM { + constructor(parent, config) { + super(config); + this.parent = parent; + + var initState = config.initState || 'IDLE'; + this.start(initState); + } + + init() { + this.start('IDLE'); + } + + // IDLE -> TRANS_OPNE + next_IDLE() { + return 'TRANS_OPNE'; + } + // IDLE + + // TRANS_OPNE -> OPEN + next_TRANS_OPNE() { + return 'OPEN'; + } + enter_TRANS_OPNE() { + var transitionBehavior = this.parent; + if (transitionBehavior.transitInTime > 0) { + var delay = transitionBehavior.runTransitionInCallback(); + transitionBehavior.delayCall(delay, this.next, this); + } else { + this.next(); + } + } + exit_TRANS_OPNE() { + var transitionBehavior = this.parent; + transitionBehavior.removeDelayCall(); + } + // TRANS_OPNE + + // OPEN -> TRANS_CLOSE + next_OPEN() { + return 'TRANS_CLOSE'; + } + enter_OPEN() { + var transitionBehavior = this.parent; + transitionBehavior.onOpen(); + } + exit_OPEN() { + var transitionBehavior = this.parent; + transitionBehavior.removeDelayCall(); + } + // OPEN + + // TRANS_CLOSE -> CLOSE + next_TRANS_CLOSE() { + return 'CLOSE'; + } + enter_TRANS_CLOSE() { + var transitionBehavior = this.parent; + if (transitionBehavior.transitOutTime > 0) { + var delay = transitionBehavior.runTransitionOutCallback(); + transitionBehavior.delayCall(delay, this.next, this); + } else { + this.next(); + } + } + exit_TRANS_CLOSE() { + var transitionBehavior = this.parent; + transitionBehavior.removeDelayCall(); + } + // TRANS_CLOSE + + // CLOSE -> TRANS_OPNE + next_CLOSE() { + return 'TRANS_OPNE'; + } + enter_CLOSE() { + var transitionBehavior = this.parent; + transitionBehavior.onClose(); + } + exit_CLOSE() { + } + // CLOSE + + canOpen() { + return (this.state === 'IDLE') || (this.state === 'CLOSE'); + } + + canClose() { + return (this.state === 'IDLE') || (this.state === 'OPEN'); + } +} + +export default State; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/CloseMethods.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/CloseMethods.js new file mode 100644 index 000000000..a66cdb831 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/CloseMethods.js @@ -0,0 +1,35 @@ +export default { + // Override + runTransitionOutCallback() { + this.transitOutCallback(this.parent, this.transitOutTime); + return this.transitOutTime; + }, + + // Override + onClose() { + // Destroy parent and this behavior + if (this.oneShotMode) { + this.parent.destroy(); + // Will invoke `this.destroy()` + } + }, + + requestClose(closeEventData, duration) { + if (!this._state.canClose) { + return this; + } + + this.closeEventData = (arguments.length > 0) ? closeEventData : this.parent; + + var transitionTimeSave = this.transitOutTime; + if (duration !== undefined) { + this.transitOutTime = duration; + } + + this._state.goto('TRANS_CLOSE'); + + this.transitOutTime = transitionTimeSave; + + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/ConfigurationMethods.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/ConfigurationMethods.js new file mode 100644 index 000000000..c8ef2bf2c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/ConfigurationMethods.js @@ -0,0 +1,34 @@ +import NOOP from '../../../utils/object/NOOP.js'; + +export default { + setTransitInTime(time) { + this.transitInTime = time; + return this; + }, + + setTransitOutTime(time) { + this.transitOutTime = time; + return this; + }, + + setTransitInCallback(callback) { + if (!callback) { + callback = NOOP; + } + + this.transitInCallback = callback; + // callback = function(gameObject, duration) {} + return this; + }, + + setTransitOutCallback(callback) { + if (!callback) { + callback = NOOP; + } + + this.transitOutCallback = callback; + // callback = function(gameObject, duration) {} + return this; + }, + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/DelayCallMethods.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/DelayCallMethods.js new file mode 100644 index 000000000..56b7a2f4b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/DelayCallMethods.js @@ -0,0 +1,18 @@ +import PostStepDelayCall from '../../../utils/time/PostStepDelayCall.js'; + +export default { + delayCall(delay, callback, scope) { + // Invoke callback under scene's 'postupdate' event + this.delayCallTimer = PostStepDelayCall(this, delay, callback, scope); + return this; + }, + + removeDelayCall() { + if (this.delayCallTimer) { + this.delayCallTimer.remove(false); + this.delayCallTimer = undefined; + } + return this; + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/Methods.js new file mode 100644 index 000000000..92e5180b1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/Methods.js @@ -0,0 +1,16 @@ +import DelayCallMethods from './DelayCallMethods.js'; +import ConfigurationMethods from './ConfigurationMethods.js'; +import OpenMethods from './OpenMethods.js'; +import CloseMethods from './CloseMethods.js'; + +var methods = {}; + +Object.assign( + methods, + DelayCallMethods, + ConfigurationMethods, + OpenMethods, + CloseMethods, +) + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/OpenMethods.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/OpenMethods.js new file mode 100644 index 000000000..4bdcb22e8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/openclosetransition/methods/OpenMethods.js @@ -0,0 +1,30 @@ +export default { + // Override + runTransitionInCallback() { + this.transitInCallback(this.parent, this.transitInTime); + return this.transitInTime; + }, + + // Override + onOpen() { + }, + + requestOpen(openEventData, duration) { + if (!this._state.canOpen()) { + return this; + } + + this.openEventData = (arguments.length > 0) ? openEventData : this.parent; + + var transitionTimeSave = this.transitInTime; + if (duration !== undefined) { + this.transitInTime = duration; + } + + this._state.goto('TRANS_OPNE'); + + this.transitInTime = transitionTimeSave; + + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/ParticlesAlongBounds.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/ParticlesAlongBounds.d.ts new file mode 100644 index 000000000..d1ea43c23 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/ParticlesAlongBounds.d.ts @@ -0,0 +1,36 @@ +export default ParticlesAlongBounds; + +declare namespace ParticlesAlongBounds { + + interface IConfig { + textureKey: string, + textureFrames?: string | + number | + (string | number)[] | + { + frames: (string | number)[], + cycle?: boolean, + quantity?: number + }, + padding?: number | { left?: number, right?: number, top?: number, bottom?: number }, + + blendMode?: Phaser.BlendModes | string, + lifespan?: number, + stepRate?: number, + spread?: number, + + scale?: Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType | Phaser.Types.GameObjects.Particles.EmitterOpOnUpdateType + alpha?: Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType | Phaser.Types.GameObjects.Particles.EmitterOpOnUpdateType + tint?: number, + + repeat?: number, + gravityX?: number, + gravityY?: number, + duration?: number + } +} + +declare function ParticlesAlongBounds( + gameObject: Phaser.GameObjects.GameObject, + config?: ParticlesAlongBounds.IConfig, +): Phaser.GameObjects.Particles.ParticleEmitter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/ParticlesAlongBounds.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/ParticlesAlongBounds.js new file mode 100644 index 000000000..57d629616 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/ParticlesAlongBounds.js @@ -0,0 +1,20 @@ +import CreateEmitterConfig from './methods/CreateEmitterConfig.js'; +import SyncToGameObject from './methods/SyncToGameObject.js'; + +var ParticlesAlongBounds = function (gameObject, config) { + if (config === undefined) { + config = {}; + } + + var emitterConfig = CreateEmitterConfig(gameObject, config); + var particles = gameObject.scene.add.particles(0, 0, config.textureKey, emitterConfig); + SyncToGameObject(particles, gameObject, config); + + particles.once('complete', function () { + particles.destroy(); + }); + + return particles; +} + +export default ParticlesAlongBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/methods/BoundsToPoints.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/methods/BoundsToPoints.js new file mode 100644 index 000000000..20ed94716 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/methods/BoundsToPoints.js @@ -0,0 +1,27 @@ +import GetBoundsConfig from '../../../utils/bounds/GetBoundsConfig.js'; + +const Rectangle = Phaser.Geom.Rectangle; +const GetValue = Phaser.Utils.Objects.GetValue; + +var BoundsToPoints = function (gameObject, config) { + if (globRect === undefined) { + globRect = new Rectangle(); + } + + globPadding = GetBoundsConfig(GetValue(config, 'padding', 0), globPadding); + var w = gameObject.width, + h = gameObject.height; + var x = (-w / 2) - globPadding.left, + y = (-h / 2) - globPadding.top; + w += globPadding.left + globPadding.right; + h += globPadding.top + globPadding.bottom; + globRect.setTo(x, y, w, h); + var stepRate = GetValue(config, 'stepRate', 10); + var points = globRect.getPoints(0, stepRate); + return points; // Return new point array +} + +var globRect; +var globPadding; + +export default BoundsToPoints; diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/methods/CreateEmitterConfig.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/methods/CreateEmitterConfig.js new file mode 100644 index 000000000..440c4f29f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/methods/CreateEmitterConfig.js @@ -0,0 +1,86 @@ +import BoundsToPoints from './BoundsToPoints.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const TickTime = (1000 / 60); + +var CreateEmitterConfig = function (gameObject, config) { + var points = BoundsToPoints(gameObject, config); + var emitterConfig = { + blendMode: GetValue(config, 'blendMode', 'ADD'), + emitZone: { + type: 'edge', + source: { + getPoints: function () { + return points; + } + }, + yoyo: GetValue(config, 'yoyo', false) + }, + speed: GetValue(config, 'spread', 10) + }; + + // Set lifespan + var lifespan = GetValue(config, 'lifespan', 1000); + emitterConfig.lifespan = lifespan; + // Set quantity or frequency + var duration = GetValue(config, 'duration', undefined); + if (duration !== undefined) { + var lastDelay = duration - lifespan; + if (lastDelay <= 0) { // Fire all particles at beginning + emitterConfig.quantity = points.length; + } else { + var delayPerParticle = lastDelay / points.length; + if (delayPerParticle <= TickTime) { // Fire more then 1 particle per tick + emitterConfig.quantity = Math.ceil(TickTime / delayPerParticle); + } else { // Not fire 1 particle per tick, set frequency + emitterConfig.frequency = delayPerParticle; + } + } + } + + // stopAfter + var repeat = 1 + GetValue(config, 'repeat', 0); + var totalParticleCount = repeat * points.length; + if (emitterConfig.hasOwnProperty('frequency')) { + // Can't use 'stopAfter' in this case + emitterConfig.emitCallback = function (particle, emitter) { + totalParticleCount -= 1; + if (totalParticleCount <= 0) { + emitter.stop(); + } + } + } else { + emitterConfig.stopAfter = totalParticleCount; + } + + // Set texture frame + var textureFrames = GetValue(config, 'textureFrames', undefined); + if (textureFrames) { + emitterConfig.frame = { + frames: textureFrames, + cycle: GetValue(config, 'textureFrameCycle', true) + } + } + + // Set scale + var scale = GetValue(config, 'scale', undefined); + if (scale !== undefined) { + emitterConfig.scale = scale; + } + + // Set alpha + var alpha = GetValue(config, 'alpha', undefined); + if (alpha !== undefined) { + emitterConfig.alpha = alpha; + } + + // Set tint + var tint = GetValue(config, 'tint', undefined); + if (tint !== undefined) { + emitterConfig.tint = tint; + } + + return emitterConfig; +} + +export default CreateEmitterConfig; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/methods/SyncToGameObject.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/methods/SyncToGameObject.js new file mode 100644 index 000000000..c3e79e22e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/particlesalongbounds/methods/SyncToGameObject.js @@ -0,0 +1,60 @@ +const PreUpdate = Phaser.GameObjects.Particles.ParticleEmitter.prototype.preUpdate; +const GetValue = Phaser.Utils.Objects.GetValue; +const Vector2 = Phaser.Math.Vector2; + +var SyncToGameObject = function (particles, gameObject, config) { + var gravityX = GetValue(config, 'gravityX', 0); + var gravityY = GetValue(config, 'gravityY', 0); + var hasGravity = (gravityX !== 0) || (gravityY !== 0); + + // Override update, sync properties of particles to game object + particles.preUpdate = (function (delta, step, processors) { + if (!gameObject.scene) { // gameObject has been destroyed + this.destroy(); + return; + } + + // Sync to gameObject + SyncTo.call(particles, gameObject); + + if (hasGravity) { + var localGravityX, localGravityY; + if (gameObject.rotation !== 0) { + var gravityVector = new Vector2(); + gravityVector + .setTo(gravityX, gravityY) + .rotate(-gameObject.rotation); + localGravityX = gravityVector.x; + localGravityY = gravityVector.y; + } else { + localGravityX = gravityX; + localGravityY = gravityY; + } + particles.setParticleGravity(localGravityX, localGravityY); + } + + PreUpdate.call(particles, delta, step, processors); + }).bind(particles); + + return particles; +} + +var SyncTo = function (gameObject) { + if (globPoint === undefined) { + globPoint = { x: 0, y: 0 }; + } + gameObject.getCenter(globPoint); + this + .setPosition(globPoint.x, globPoint.y) + .setScale(gameObject.scaleX, gameObject.scaleY) + .setAngle(gameObject.angle) + .setAlpha(gameObject.alpha); + + if (this.depth !== gameObject.depth) { + this.setDepth(gameObject.depth); + } +} + +var globPoint; + +export default SyncToGameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/pathfollower/PathFollower.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/pathfollower/PathFollower.d.ts new file mode 100644 index 000000000..7e645a9b3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/pathfollower/PathFollower.d.ts @@ -0,0 +1,44 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default PathFollower; + +declare namespace PathFollower { + + interface IConfig { + path?: Phaser.Curves.Path, + t?: number, + rotateToPath?: boolean, + rotationOffset?: number, + angleOffset?: number, + + spacedPoints?: { + divisions?: number, + stepRate?: number + } | boolean + } +} + +declare class PathFollower extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: PathFollower.IConfig + ); + + setT(t: number): this; + t: number; + + setPath(path: Phaser.Curves.Path): this; + path: Phaser.Curves.Path; + + setRotateToPath( + rotateToPath: boolean, + rotationOffset?: number + ): this; + rotateToPath: boolean; + rotationOffset: number; + + setSpacedPointsMode( + divisions?: number | boolean, + stepRate?: number + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/pathfollower/PathFollower.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/pathfollower/PathFollower.js new file mode 100644 index 000000000..c38f339dd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/pathfollower/PathFollower.js @@ -0,0 +1,137 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Vector2 = Phaser.Math.Vector2; +const DegToRad = Phaser.Math.DegToRad; +const AngleBetween = Phaser.Math.Angle.Between; +const Linear = Phaser.Math.Linear; + +class PathFollower extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, { eventEmitter: false }); + // No event emitter + // this.parent = gameObject; + + this._t = 0; + this.pathVector = new Vector2(); + this.spacePoints = undefined; + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.setPath(GetValue(o, 'path', undefined)); + + var rotateToPath = GetValue(o, 'rotateToPath', false); + var rotationOffset = GetValue(o, 'rotationOffset', undefined); + if (rotationOffset === undefined) { + rotationOffset = DegToRad(GetValue(o, 'angleOffset', 0)); + } + this.setRotateToPath(rotateToPath, rotationOffset); + + var spacedPoints = GetValue(o, 'spacedPoints', false); + if (spacedPoints) { + this.setSpacedPointsMode( + GetValue(spacedPoints, 'divisions', undefined), + GetValue(spacedPoints, 'stepRate', 10) + ) + } else { + this.setSpacedPointsMode(false); + } + + var t = GetValue(o, 't', undefined); + if (t !== undefined) { + this.setT(t); + } + return this; + } + + toJSON() { + return { + path: this.path, + t: this.t, + rotateToPath: this.rotateToPath, + rotationOffset: this.rotationOffset + }; + } + + setPath(path) { + this.path = path; + return this; + } + + setT(t) { + this.t = t; + return this; + } + + get t() { + return this._t; + } + + set t(value) { + this._t = value; + this.update(); + } + + setRotateToPath(rotateToPath, rotationOffset) { + this.rotateToPath = rotateToPath; + this.rotationOffset = rotationOffset; + return this; + } + + setSpacedPointsMode(divisions, stepRate) { + if ((!divisions) && (!stepRate)) { + this.spacePoints = undefined; + } else { + this.spacePoints = this.path.getSpacedPoints(divisions, stepRate, this.spacePoints); + // Add point at t=1 + this.spacePoints.push(this.path.getPoint(1)); + } + return this; + } + + getPoint(t) { + if (this.spacePoints === undefined) { + return this.path.getPoint(this.t, this.pathVector); + + } else { + var start = (this.spacePoints.length - 1) * t; + var index = Math.floor(start); + var p0 = this.spacePoints[index], + p1 = this.spacePoints[index + 1]; + if (!p1) { + this.pathVector.x = p0.x; + this.pathVector.y = p0.y; + } else { + var remainderT = start - index; + this.pathVector.x = Linear(p0.x, p1.x, remainderT); + this.pathVector.y = Linear(p0.y, p1.y, remainderT); + } + return this.pathVector; + } + } + + update() { + if (this.path === undefined) { + return; + } + + var gameObject = this.parent; + var curX = gameObject.x, + curY = gameObject.y; + this.pathVector = this.getPoint(this._t); + var newX = this.pathVector.x, + newY = this.pathVector.y; + + if ((curX === newX) && (curY === newY)) { + return; + } + + gameObject.setPosition(newX, newY); + if (this.rotateToPath) { + gameObject.rotation = AngleBetween(curX, curY, newX, newY) + this.rotationOffset; + } + } +} + +export default PathFollower; diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/perlingrivatywell/PerlinGrivatyWell.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/perlingrivatywell/PerlinGrivatyWell.js new file mode 100644 index 000000000..a028029e4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/perlingrivatywell/PerlinGrivatyWell.js @@ -0,0 +1,32 @@ +import Perlin from '../../utils/math/noise/Perlin.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const PERIOD_SCALE = 1 / 5000; +const ROTATE_SCALE = 0.3; + +class PerlinGrivatyWell { + constructor(config) { + this.active = true; + this.noise = new Perlin(GetValue(config, 'seed', Math.random())); + this.periodScale = GetValue(config, 'periodScale', PERIOD_SCALE); + this.rotateScale = GetValue(config, 'rotateScale', ROTATE_SCALE); + this.velocity = new Phaser.Math.Vector2(); + } + + update(particle, delta) { + var period = delta * this.periodScale; + var noiseValue = this.noise.perlin2( + particle.x * period, + particle.y * period + ); // -1 ~ 1 + + var noiseAngle = noiseValue * Math.PI; // -PI ~ PI + this.velocity.set(particle.velocityX, particle.velocityY) + .rotate(noiseAngle * this.rotateScale) + + particle.velocityX = this.velocity.x + particle.velocityY = this.velocity.y; + } +} + +export default PerlinGrivatyWell; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/polarcoordinate/AddPolarCoordinateProperties.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/polarcoordinate/AddPolarCoordinateProperties.d.ts new file mode 100644 index 000000000..598fa72f1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/polarcoordinate/AddPolarCoordinateProperties.d.ts @@ -0,0 +1,18 @@ +export default AddPolarCoordinateProperties; + +declare namespace AddPolarCoordinateProperties { + interface PolarCoordinateGameObject extends Phaser.GameObjects.GameObject { + polarOX: number; + polarOY: number; + polarRotation: number; + polarAngle: number; + polarRadius: number; + } +} + +declare function AddPolarCoordinateProperties( + gameObject: Phaser.GameObjects.GameObject, + ox?: number, oy?: number, + rotation?: number, + radius?: number +): AddPolarCoordinateProperties.PolarCoordinateGameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/polarcoordinate/AddPolarCoordinateProperties.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/polarcoordinate/AddPolarCoordinateProperties.js new file mode 100644 index 000000000..2fd6e0a6e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/polarcoordinate/AddPolarCoordinateProperties.js @@ -0,0 +1,84 @@ +import PolarToCartesian from '../../utils/math/coordinate/PolarToCartesian.js'; + +var DegToRad = Phaser.Math.DegToRad; +var RadToDeg = Phaser.Math.RadToDeg; + +var AddPolarCoordinateProperties = function (gameObject, ox, oy, rotation, radius) { + // Don't attach properties again + if (gameObject.hasOwnProperty('polarOX')) { + return gameObject; + } + + if (ox === undefined) { + ox = 0; + } + if (oy === undefined) { + oy = 0; + } + if (rotation === undefined) { + rotation = 0; + } + if (radius === undefined) { + radius = 0; + } + + Object.defineProperty(gameObject, 'polarOX', { + get: function () { + return ox; + }, + set: function (value) { + if (ox !== value) { + ox = value; + PolarToCartesian(ox, oy, rotation, radius, gameObject); + } + }, + }); + + Object.defineProperty(gameObject, 'polarOY', { + get: function () { + return oy; + }, + set: function (value) { + if (oy !== value) { + oy = value; + PolarToCartesian(ox, oy, rotation, radius, gameObject); + } + }, + }); + + Object.defineProperty(gameObject, 'polarRotation', { + get: function () { + return rotation; + }, + set: function (value) { + if (rotation !== value) { + rotation = value; + PolarToCartesian(ox, oy, rotation, radius, gameObject); + } + }, + }); + Object.defineProperty(gameObject, 'polarAngle', { + get: function () { + return RadToDeg(rotation); + }, + set: function (value) { + this.polarRotation = DegToRad(value); + }, + }); + + Object.defineProperty(gameObject, 'polarRadius', { + get: function () { + return radius; + }, + set: function (value) { + if (radius !== value) { + radius = value; + PolarToCartesian(ox, oy, rotation, radius, gameObject); + } + }, + }); + + PolarToCartesian(ox, oy, rotation, radius, gameObject); +} + +export default AddPolarCoordinateProperties; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/rotate/Rotate.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/rotate/Rotate.d.ts new file mode 100644 index 000000000..e61c60225 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/rotate/Rotate.d.ts @@ -0,0 +1,25 @@ +import TickTask from '../../utils/componentbase/TickTask'; + +export default Rotate; + +declare namespace Rotate { + + interface IConfig { + enable?: boolean, + speed?: number, + timeScale?: number + } +} + +declare class Rotate extends TickTask { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Rotate.IConfig + ) + + setEnable(enable?: boolean): this; + enable: boolean; + + setSpeed(speed: number): this; + speed: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/rotate/Rotate.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/rotate/Rotate.js new file mode 100644 index 000000000..683981cb6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/rotate/Rotate.js @@ -0,0 +1,75 @@ +import TickTask from '../../utils/componentbase/SceneUpdateTickTask.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Rotate extends TickTask { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.setEnable(GetValue(o, 'enable', true)); + this.timeScale = GetValue(o, 'timeScale', 1); + this.setSpeed(GetValue(o, 'speed', 180)); + return this; + } + + toJSON() { + return { + enable: this.isRunning, + timeScale: this.timeScale, + speed: this.speed, + tickingMode: this.tickingMode + }; + } + + get enable() { + return this.isRunning; + } + + set enable(enable) { + if (enable) { + this.start(); + } else { + this.stop(); + } + } + + setEnable(enable) { + if (enable == undefined) { + enable = true; + } + this.enable = enable; + return this; + } + + setSpeed(speed) { + this.speed = speed; + return this; + } + + update(time, delta) { + if (!this.isRunning) { + return this; + } + + var gameObject = this.parent; + if (!gameObject.active) { + return this; + } + + if ((this.speed === 0) || (delta === 0) || (this.timeScale === 0)) { + return this; + } + + var dt = (delta * this.timeScale) / 1000; + gameObject.angle += this.speed * dt; + return this; + } +} + +export default Rotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/rotateto/RotateTo.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/rotateto/RotateTo.d.ts new file mode 100644 index 000000000..7aa64186a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/rotateto/RotateTo.d.ts @@ -0,0 +1,47 @@ +import TickTask from '../../utils/componentbase/TickTask'; + +export default RotateTo; + +declare namespace RotateTo { + + type DirectionType = 0 | 1 | 2 | 'cw' | 'ccw'; + + interface IConfig { + enable?: boolean, + speed?: number, + timeScale?: number + } + + namespace Events { + type CompleteCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + rotateTo: RotateTo + ) => void; + } +} + +declare class RotateTo extends TickTask { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: RotateTo.IConfig + ) + + setEnable(enable?: boolean): this; + enable: boolean; + + rotateTowardsPosition( + x: number, + y: number, + direction?: RotateTo.DirectionType, + speed?: number + ): this; + + rotateTo( + degrees: number, + direction?: RotateTo.DirectionType, + speed?: number + ): this; + + setSpeed(speed: number): this; + speed: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/rotateto/RotateTo.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/rotateto/RotateTo.js new file mode 100644 index 000000000..bb7d23619 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/rotateto/RotateTo.js @@ -0,0 +1,149 @@ +import TickTask from '../../utils/componentbase/SceneUpdateTickTask.js'; +import DegToRad from '../../utils/math/DegToRad.js'; +import RadToDeg from '../../utils/math/RadToDeg.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const MathWrap = Phaser.Math.Wrap; +const WrapAngle = Phaser.Math.Angle.Wrap; +const AngleBetween = Phaser.Math.Angle.Between; + + +class RotateTo extends TickTask { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.isRunning = GetValue(o, 'isRunning', false); + this.setEnable(GetValue(o, 'enable', true)); + this.timeScale = GetValue(o, 'timeScale', 1); + this.setSpeed(GetValue(o, 'speed', 180)); + this.target = GetValue(o, 'target', 0); + this.dir = GetValue(o, 'dir', 0); + return this; + } + + toJSON() { + return { + isRunning: this.isRunning, + timeScale: this.timeScale, + speed: this.speed, + target: this.target, + dir: this.dir, + tickingMode: this.tickingMode + }; + } + + setEnable(e) { + if (e == undefined) { + e = true; + } + this.enable = e; + return this; + } + + setSpeed(speed) { + this.speed = speed; + return this; + } + + rotateTo(angle, dir, speed) { + if (typeof (angle) !== 'number') { + var config = angle; + angle = GetValue(config, 'angle', undefined); + dir = GetValue(config, 'dir', undefined); + } + this.target = MathWrap(angle, 0, 360); // 0~360 + if (dir === undefined) { + dir = 0; + } + this.dir = (typeof (dir) === 'string') ? DIRMODE[dir] : dir; + if (speed !== undefined) { + this.setSpeed(speed); + } + super.start(); + this.emit('start', this.parent, this); + return this; + } + + rotateTowardsPosition(x, y, dir, speed) { + var gameObject = this.parent; + var rad = AngleBetween(gameObject.x, gameObject.y, x, y); + var angle = RadToDeg(rad); + this.rotateTo(angle, dir, speed); + return this; + } + + update(time, delta) { + if ((!this.isRunning) || (!this.enable)) { + return this; + } + + var gameObject = this.parent; + if (!gameObject.active) { + return this; + } + + var target = this.target; // 0~360 + var targetRad = WrapAngle(DegToRad(target)); // -PI~PI + if (targetRad === gameObject.rotation) { + this.complete(); + return this; + } + + if ((this.speed === 0) || (delta === 0) || (this.timeScale === 0)) { + return this; + } + + var curAngle = (360 + gameObject.angle) % 360; // 0~360 + var dt = (delta * this.timeScale) / 1000; + var movingDist = this.speed * dt; + var distToTarget, dir = this.dir; + switch (dir) { + case 0: // shotest + var distCW = diffAngle(curAngle, target, true); + var distCCW = 360 - distCW; + if (distCW < distCCW) { + dir = 1; + distToTarget = distCW; + } else { + dir = 2; + distToTarget = distCCW; + } + break; + case 1: // cw + distToTarget = diffAngle(curAngle, target, true); + break; + case 2: // ccw + distToTarget = diffAngle(curAngle, target, false); + break; + } + + var newAngle; + if (movingDist < distToTarget) { + newAngle = (dir === 1) ? (curAngle + movingDist) : (curAngle - movingDist); + } else { + newAngle = target; + } + + gameObject.rotation = DegToRad(newAngle); + return this; + } +} + +var diffAngle = function (a0, a1, cw) { + var diff = (cw) ? (a1 - a0) : (a0 - a1); + diff = MathWrap(diff, 0, 360); + return diff; +} + +const DIRMODE = { + shortest: 0, + cw: 1, + ccw: 2 +} +export default RotateTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/PopUp.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/PopUp.d.ts new file mode 100644 index 000000000..fe8451aa2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/PopUp.d.ts @@ -0,0 +1,9 @@ +import Scale from './Scale'; + +export default function PopUp( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + orientation?: number | string, + ease?: string, + scale?: Scale +): Scale; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/PopUp.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/PopUp.js new file mode 100644 index 000000000..66b63a91d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/PopUp.js @@ -0,0 +1,45 @@ +import Scale from './Scale.js'; + +var PopUp = function (gameObject, duration, orientation, ease, scale) { + if (ease === undefined) { + ease = 'Cubic'; + } + + // Ease scale from 0 to current scale + var start, end; + switch (orientation) { + case 0: + case 'x': + start = { x: 0 }; + end = { x: gameObject.scaleX }; + break; + case 1: + case 'y': + start = { y: 0 }; + end = { y: gameObject.scaleY }; + break; + default: + start = 0; + end = gameObject.scale; + break; + } + + var config = { + mode: 0, + start: start, + end: end, + duration: duration, + ease: ease + } + + if (scale === undefined) { + scale = new Scale(gameObject, config); + } else { + scale.resetFromJSON(config); + } + scale.restart(); + + return scale; +}; + +export default PopUp; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Scale.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Scale.d.ts new file mode 100644 index 000000000..82c2459ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Scale.d.ts @@ -0,0 +1,42 @@ +import EaseValueTaskBase from "../../utils/componentbase/tweentask/EaseValueTaskBase"; + +export default Scale; + +declare namespace Scale { + type ModeType = 0 | 1 | 2 | 'stop' | 'destroy' | 'yoyo'; + + interface IConfig { + mode?: ModeType, + start?: number, + end?: number, + duration?: number, + delay?: number, + ease?: string + } + + namespace Events { + type CompleteCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + scale: Scale + ) => void; + } +} + +declare class Scale extends EaseValueTaskBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Scale.IConfig + ) + + setMode(mode: Scale.ModeType): this; + mode: number; + + setScaleRange( + start: number | { x: number, y: number }, + end: number | { x: number, y: number } + ): this; + startX: number; + startY: number; + endX: number; + endY: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Scale.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Scale.js new file mode 100644 index 000000000..efb376025 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Scale.js @@ -0,0 +1,122 @@ +import EaseValueTaskBase from '../../utils/componentbase/tweentask/EaseValueTaskBase.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const Linear = Phaser.Math.Linear; + +class Scale extends EaseValueTaskBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + // this.timer + + this.scaleStart = {}; + this.scaleEnd = {}; + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + + this.setMode(GetValue(o, 'mode', 0)); + this.setScaleRange( + GetAdvancedValue(o, 'start', undefined), + GetAdvancedValue(o, 'end', 0) + ); + + return this; + } + + setMode(m) { + if (typeof (m) === 'string') { + m = MODE[m]; + } + this.mode = m; + return this; + } + + setScaleRange(start, end) { + if (typeof (start) === 'number') { + this.startX = start; + this.startY = start; + } else { + this.startX = GetAdvancedValue(start, 'x', this.parent.scaleX); + this.startY = GetAdvancedValue(start, 'y', this.parent.scaleY); + } + if (typeof (end) === 'number') { + this.endX = end; + this.endY = end; + } else { + this.endX = GetAdvancedValue(end, 'x', undefined); + this.endY = GetAdvancedValue(end, 'y', undefined); + } + + this.hasScaleX = (this.startX !== undefined) && (this.endX !== undefined); + this.hasScaleY = (this.startY !== undefined) && (this.endY !== undefined); + return this; + } + + start() { + if (this.timer.isRunning) { + return this; + } + + var gameObject = this.parent; + if (this.hasScaleX) { + gameObject.scaleX = this.startX; + } + if (this.hasScaleY) { + gameObject.scaleY = this.startY; + } + + var repeat = this.repeat; + if (this.mode === 2) { // Yoyo + if (repeat !== -1) { + repeat = ((repeat + 1) * 2) - 1; + } + } + + this.timer + .setDelay(this.delay) + .setDuration(this.duration) + .setRepeat(repeat); + + super.start(); + return this; + } + + updateGameObject(gameObject, timer) { + var t = timer.t; + if (timer.isOddIteration) { // Yoyo + t = 1 - t; + } + t = this.easeFn(t); + + if (this.hasScaleX) { + gameObject.scaleX = Linear(this.startX, this.endX, t); + } + if (this.hasScaleY) { + gameObject.scaleY = Linear(this.startY, this.endY, t); + } + } + + complete() { + super.complete(); + + if (this.mode === 1) { + this.parent.destroy(); + // Will also destroy this behavior + } + return this; + } +} + +const MODE = { + stop: 0, + destroy: 1, + yoyo: 2 +} + +export default Scale; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDown.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDown.d.ts new file mode 100644 index 000000000..673547da6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDown.d.ts @@ -0,0 +1,9 @@ +import Scale from './Scale'; + +export default function ScaleDown( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + orientation?: number | string, + ease?: string, + scale?: Scale +): Scale; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDown.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDown.js new file mode 100644 index 000000000..518755e11 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDown.js @@ -0,0 +1,40 @@ +import Scale from './Scale.js'; + +var ScaleDown = function (gameObject, duration, orientation, ease, scale) { + if (ease === undefined) { + ease = 'Linear'; + } + + var config = {}; + config.mode = 0; + switch (orientation) { + case 0: + case 'x': + config.end = { + x: 0 + }; + break; + case 1: + case 'y': + config.end = { + y: 0 + }; + break; + default: + config.end = 0; + break; + } + config.duration = duration; + config.ease = ease; + + if (scale === undefined) { + scale = new Scale(gameObject, config); + } else { + scale.resetFromJSON(config); + } + scale.restart(); + + return scale; +}; + +export default ScaleDown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDownDestroy.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDownDestroy.d.ts new file mode 100644 index 000000000..24b7a2048 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDownDestroy.d.ts @@ -0,0 +1,18 @@ +import Scale from './Scale'; + +export default function ScaleDownDestroy( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + orientation?: number | string, + ease?: string, + scale?: Scale +): Scale; + +export default function ScaleDownDestroy( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + orientation?: number | string, + ease?: string, + destroyMode?: boolean, + scale?: Scale +): Scale; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDownDestroy.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDownDestroy.js new file mode 100644 index 000000000..858db1aaf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/ScaleDownDestroy.js @@ -0,0 +1,50 @@ +import Scale from './Scale.js'; + +var ScaleDownDestroy = function (gameObject, duration, orientation, ease, destroyMode, scale) { + if (ease === undefined) { + ease = 'Linear'; + } + + // Ease from current scale to 0 + if (destroyMode instanceof Scale) { + scale = destroyMode; + destroyMode = undefined; + } + + if (destroyMode === undefined) { + destroyMode = true; + } + + var config = {}; + config.mode = (destroyMode) ? 1 : 0; + switch (orientation) { + case 0: + case 'x': + config.end = { + x: 0 + }; + break; + case 1: + case 'y': + config.end = { + y: 0 + }; + break; + default: + config.end = 0; + break; + } + config.duration = duration; + config.ease = ease; + + if (scale === undefined) { + scale = new Scale(gameObject, config); + } else { + scale.resetFromJSON(config); + } + scale.restart(); + + return scale; +}; + +export default ScaleDownDestroy; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Yoyo.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Yoyo.d.ts new file mode 100644 index 000000000..0ca6d3088 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Yoyo.d.ts @@ -0,0 +1,11 @@ +import Scale from './Scale'; + +export default function Yoyo( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + peakValue?: number, + repeat?: number, + orientation?: number | string, + ease?: string, + scale?: Scale +): Scale; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Yoyo.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Yoyo.js new file mode 100644 index 000000000..ad87f38ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/scale/Yoyo.js @@ -0,0 +1,52 @@ +import Scale from './Scale.js'; + +var Yoyo = function (gameObject, duration, peakValue, repeat, orientation, ease, scale) { + if (peakValue === undefined) { + peakValue = 1.2; + } + if (repeat === undefined) { + repeat = 0; + } + if (ease === undefined) { + ease = 'Cubic'; + } + + // Ease scale from 0 to current scale + var start, end; + switch (orientation) { + case 0: + case 'x': + start = { x: gameObject.scaleX }; + end = { x: peakValue }; + break; + case 1: + case 'y': + start = { y: gameObject.scaleX }; + end = { y: peakValue }; + break; + default: + start = gameObject.scaleX; + end = peakValue; + break; + } + + var config = { + mode: 2, + start: start, + end: end, + duration: (duration / 2), + ease: ease, + repeat: repeat, + } + + if (scale === undefined) { + scale = new Scale(gameObject, config); + } else { + scale.resetFromJSON(config); + } + scale.restart(); + + return scale; +}; + +export default Yoyo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/shake/ShakePosition.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/shake/ShakePosition.d.ts new file mode 100644 index 000000000..b33fb17f1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/shake/ShakePosition.d.ts @@ -0,0 +1,58 @@ +import TickTask from '../../utils/componentbase/TickTask'; + +export default ShakePosition; + +declare namespace ShakePosition { + + type ModeType = 0 | 1 | 'effect' | 'behavior'; + type MagnitudeModeType = 0 | 1 | 'constant' | 'decay'; + type AixsModeType = 0 | 1 | 2 | 'both' | 'h&v' | 'horizontal' | 'h' | 'vertical' | 'v'; + + interface IConfig { + mode?: ModeType, + duration?: number, + magnitude?: number, + magnitudeMode?: MagnitudeModeType, + axis?: AixsModeType, + } + + namespace Events { + type CompleteCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + shake: ShakePosition + ) => void; + } + +} + +declare class ShakePosition extends TickTask { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: ShakePosition.IConfig + ) + + setEnable(enable?: boolean): this; + enable: boolean; + + shake(duration?: number, magnitude?: number): this; + shake(config: { + duration?: number, + magnitude?: number, + }): this; + + setMode(mode: ShakePosition.ModeType): this; + mode: number; + + setDuration(duration: number): this; + duration: number; + + setMagnitude(magnitude: number): this; + magnitude: number; + + setMagnitudeMode(magnitudeMode: ShakePosition.MagnitudeModeType): this; + magnitudeMode: number; + + setAxisMode(axisMode: ShakePosition.AixsModeType): this; + axisMode: number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/shake/ShakePosition.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/shake/ShakePosition.js new file mode 100644 index 000000000..7d8e43833 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/shake/ShakePosition.js @@ -0,0 +1,252 @@ +import TickTask from '../../utils/componentbase/TickTask.js'; +import Timer from '../../utils/componentbase/timerticktask/Timer.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class ShakePosition extends TickTask { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this.timer = new Timer(); + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.timer.resetFromJSON(GetValue(o, 'timer')); + this.setEnable(GetValue(o, 'enable', true)); + this.setMode(GetValue(o, 'mode', 1)); + this.isRunning = GetValue(o, 'isRunning', false); + this.setMagnitudeMode(GetValue(o, 'magnitudeMode', 1)); + this.setAxisMode(GetValue(o, "axis", 0)); + this.setDuration(GetValue(o, 'duration', 500)); + this.setMagnitude(GetValue(o, 'magnitude', 10)); + this.ox = GetValue(o, 'ox', undefined); + this.oy = GetValue(o, 'oy', undefined); + return this; + } + + toJSON() { + return { + timer: this.timer.toJSON(), + enable: this.enable, + mode: this.mode, + isRunning: this.isRunning, + magnitudeMode: magnitudeMode, + duration: this.duration, + magnitude: this.magnitude, + ox: this.ox, + oy: this.oy, + }; + } + + // override + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + super.shutdown(fromScene); + this.timer.destroy(); + this.timer = undefined; + } + + startTicking() { + super.startTicking(); + + if (this.mode === 0) { // Effect mode + this.scene.game.events.on('poststep', this.update, this); + this.scene.game.events.on('prestep', this.backToOrigin, this); + } else { // Behavior Mode + this.scene.sys.events.on('preupdate', this.update, this); + } + } + + stopTicking() { + super.stopTicking(); + + if (this.scene) { // Scene might be destoryed + if (this.mode === 0) { // Effect mode + this.scene.game.events.off('poststep', this.update, this); + this.scene.game.events.off('prestep', this.backToOrigin, this); + } else { // Behavior Mode + this.scene.sys.events.off('preupdate', this.update, this); + } + + } + } + + setEnable(e) { + if (e == undefined) { + e = true; + } + this.enable = e; + return this; + } + + setMode(mode) { + if (typeof (mode) === 'string') { + mode = MODE[mode]; + } + this.mode = mode; + return this; + } + + setMagnitudeMode(magnitudeMode) { + if (typeof (magnitudeMode) === 'string') { + magnitudeMode = MANITUDEMODE[magnitudeMode]; + } + + this.magnitudeMode = magnitudeMode; + return this; + } + + setAxisMode(m) { + if (typeof (m) === 'string') { + m = DIRECTIONNODE[m]; + } + this.axisMode = m; + return this; + } + + setDuration(duration) { + this.duration = duration; + return this; + } + + setMagnitude(magnitude) { + this.magnitude = magnitude; + return this; + } + + start(duration, magnitude) { + if (typeof (duration) !== 'number') { + var config = duration; + magnitude = GetValue(config, 'magnitude', undefined); + duration = GetValue(config, 'duration', undefined); + } + if (magnitude !== undefined) { + this.setMagnitude(magnitude); + } + if (duration !== undefined) { + this.setDuration(duration); + } + + this.timer + .setDuration(this.duration) + .start() + + super.start(); + return this; + } + + shake(duration, magnitude) { + this.start(duration, magnitude); + return this; + } + + update(time, delta) { + if ((!this.isRunning) || (!this.enable)) { + return this; + } + + var gameObject = this.parent; + if (!gameObject.active) { + return this; + } + + this.timer.update(time, delta); + if (this.timer.isDone) { + this.backToOrigin(); + this.complete(); + } else { + if (this.ox === undefined) { + this.ox = gameObject.x; + this.oy = gameObject.y; + } + + var magnitude = this.magnitude; + if (this.magnitudeMode === 1) // decay + { + magnitude *= (1 - this.timer.t); + } + var a = Math.random() * Math.PI * 2; + var x = this.ox + (Math.cos(a) * magnitude); + var y = this.oy + (Math.sin(a) * magnitude); + + switch (this.axisMode) { + case 1: + gameObject.x = x; + break; + + case 2: + gameObject.y = y; + break; + + default: + gameObject.x = x; + gameObject.y = y; + break; + } + } + + return this; + } + + backToOrigin() { + if ((!this.isRunning) || (!this.enable)) { + return this; + } + + if (this.ox === undefined) { + return this; + } + + var gameObject = this.parent; + + switch (this.axisMode) { + case 1: + gameObject.x = this.ox; + break; + + case 2: + gameObject.y = this.oy; + break; + + default: + gameObject.x = this.ox; + gameObject.y = this.oy; + break; + } + + this.ox = undefined; + this.oy = undefined; + return this; + } +} + +const MODE = { + effect: 0, + behavior: 1, +} + +const DIRECTIONNODE = { + 'both': 0, + 'h&v': 0, + 'x&y': 0, + 'horizontal': 1, + 'h': 1, + 'x': 1, + 'vertical': 2, + 'v': 2, + 'y': 2 +}; + +const MANITUDEMODE = { + constant: 0, + decay: 1, +} + +export default ShakePosition; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/ship/Ship.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/ship/Ship.d.ts new file mode 100644 index 000000000..26fd2b4a4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/ship/Ship.d.ts @@ -0,0 +1,64 @@ +import TickTask from '../../utils/componentbase/TickTask'; + +export default Ship; + +declare namespace Ship { + + type CursorKeys = { + up: Phaser.Input.Keyboard.Key, + down: Phaser.Input.Keyboard.Key, + left: Phaser.Input.Keyboard.Key, + right: Phaser.Input.Keyboard.Key + } + + interface IConfig { + + maxSpeed?: number, + acceleration?: number, + drag?: number, + turnSpeed?: number, + enable?: boolean, + wrap?: boolean, + padding?: number, + cursorKeys?: CursorKeys + } +} + +declare class Ship extends TickTask { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Ship.IConfig + ) + + setEnable(enable?: boolean): this; + enable: boolean; + + setCursorKeys( + cursorKeys: Ship.CursorKeys + ): this; + cursorKeys: Ship.CursorKeys; + + setMaxSpeed(maxSpeed: number): this; + maxSpeed: number; + + setAcceleration(acceleration: number): this; + acceleration: number; + + setDrag(drag: number): this; + drag: number; + + setTurnSpeed(angularVelocity: number): this; + angularVelocity: number; + + setWrapMode( + wrap?: boolean, + padding?: number + ): this; + wrap: boolean; + padding: number; + + readonly isLeft: boolean; + readonly isRight: boolean; + readonly isUp: boolean; + readonly isDown: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/ship/Ship.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/ship/Ship.js new file mode 100644 index 000000000..12876d370 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/ship/Ship.js @@ -0,0 +1,157 @@ +// https://labs.phaser.io/view.html?src=src\physics\arcade\asteroids%20movement.js + +import TickTask from '../../utils/componentbase/SceneUpdateTickTask.js'; +import { SetAcceleration, SetAngularVelocity } from '../../utils/arcade/Helpers.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Ship extends TickTask { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + if (!this.parent.body) { + this.scene.physics.add.existing(this.parent, false); + } + this.setEnable(GetValue(o, 'enable', true)); + this.setMaxSpeed(GetValue(o, 'maxSpeed', 200)); + this.setAcceleration(GetValue(o, 'acceleration', 200)); + this.setDrag(GetValue(o, 'drag', 0.99)); + this.setTurnSpeed(GetValue(o, 'turnSpeed', 300)); + this.setWrapMode(GetValue(o, 'wrap', true), GetValue(o, 'padding', 0)); + this.setCursorKeys(GetValue(o, 'cursorKeys', undefined)); + return this; + } + + get enable() { + return this.isRunning; + } + + set enable(value) { + this.isRunning = value; + if (!value) { + SetAcceleration(this.parent, 0, 0); + SetAngularVelocity(this.parent, 0); + } + } + + setEnable(e) { + if (e == undefined) { + e = true; + } + this.enable = e; + return this; + } + + get maxSpeed() { + return this._maxSpeed; + } + + set maxSpeed(value) { + this._maxSpeed = value; + var body = this.parent.body; + body.setMaxSpeed(value); + } + + setMaxSpeed(speed) { + this.maxSpeed = speed; + return this; + } + + setAcceleration(acceleration) { + this.acceleration = acceleration; + return this; + } + + get drag() { + return this._drag; + } + + set drag(value) { + this._drag = value; + var body = this.parent.body; + body.setDrag(value); + body.useDamping = true; + } + + setDrag(drag) { + this.drag = drag; + return this; + } + + setTurnSpeed(angularVelocity) { + this.angularVelocity = angularVelocity; + return this; + } + + setWrapMode(wrap, padding) { + if (wrap === undefined) { + wrap = true; + } + this.wrap = wrap; + this.padding = padding; + return this; + } + + setCursorKeys(cursorKeys) { + if (cursorKeys === undefined) { + cursorKeys = this.scene.input.keyboard.createCursorKeys(); + } + this.cursorKeys = cursorKeys; + return this; + } + + get isLeft() { + return (this.enable) ? this.cursorKeys.left.isDown : false; + } + + get isRight() { + return (this.enable) ? this.cursorKeys.right.isDown : false; + } + + get isUp() { + return (this.enable) ? this.cursorKeys.up.isDown : false; + } + + get isDown() { + return (this.enable) ? this.cursorKeys.down.isDown : false; + } + + update(time, delta) { + var gameObject = this.parent; + if (!this.enable) { + SetAcceleration(gameObject, 0, 0); + SetAngularVelocity(gameObject, 0); + return this; + } + + if (!gameObject.active) { + return this; + } + + // Speed up + if (this.isUp) { + var rotation = gameObject.rotation; + var ax = Math.cos(rotation) * this.acceleration; + var ay = Math.sin(rotation) * this.acceleration; + SetAcceleration(gameObject, ax, ay); + } else { + SetAcceleration(gameObject, 0, 0); + } + + // Turn left/right + var dx = ((this.isLeft) ? -1 : 0) + ((this.isRight) ? 1 : 0); + SetAngularVelocity(gameObject, this.angularVelocity * dx); + + if (this.wrap) { + gameObject.body.world.wrap(gameObject, this.padding); + } + } +} + +export default Ship; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/step/Step.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/step/Step.d.ts new file mode 100644 index 000000000..8f56fa872 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/step/Step.d.ts @@ -0,0 +1,26 @@ +import TickTask from '../../utils/componentbase/TickTask'; + +export default Step; + +declare namespace Step { + interface IConfig { + enable?: boolean, + stepLength?: number, + } +} + +declare class Step extends TickTask { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Step.IConfig + ) + + setEnable(enable?: boolean): this; + enable: boolean; + + setStepLength(stepLength: number): this; + stepLength: number; + + cancelStep(): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/step/Step.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/step/Step.js new file mode 100644 index 000000000..1ee2036b8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/step/Step.js @@ -0,0 +1,139 @@ +import TickTask from '../../utils/componentbase/SceneUpdateTickTask.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Step extends TickTask { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.setEnable(GetValue(o, 'enable', true)); + this.setStepLength(GetValue(o, 'stepLength', 5)); + } + + toJSON() { + return { + enable: this.enable, + + }; + } + + get enable() { + return this._enable; + } + + set enable(value) { + value = !!value; + if (this._enable === value) { + return this; + } + + this._enable = value; + this.isRunning = value; + + if (value) { + var gameObject = this.parent; + this.preX = gameObject.x; + this.preY = gameObject.y; + } + } + + setEnable(e) { + if (e == undefined) { + e = true; + } + this.enable = e; + + return this; + } + + setStepLength(stepLength) { + this.stepLength = stepLength; + return this; + } + + cancelStep() { + this.cancelStepFlag = true; + return this; + } + + update(time, delta) { + if (!this.enable) { + return this; + } + + var gameObject = this.parent; + if (!gameObject.active) { + return this; + } + + var x0 = this.preX, + y0 = this.preY, + x1 = gameObject.x, + y1 = gameObject.y; + + if ((x0 === x1) && (y0 === y1)) { + return this; + } + + this.cancelStepFlag = false; + + this.step(x0, y0, x1, y1, this.stepLength); + + this.preX = x1; + this.preY = y1; + return this; + } + + step(x0, y0, x1, y1, stepLength) { + if (this.cancelStepFlag) { + return this; + } + + var dx = x1 - x0, + dy = y1 - y0; + var d = Math.sqrt(dx * dx + dy * dy); + + var steps = Math.round(d / stepLength); + if (steps === 0) { + steps = 1; + } + + var stepX = dx / steps, + stepY = dy / steps; + var xt, yt; + var gameObject = this.parent; + var points = []; + for (var i = 1; i <= steps; i++) { + xt = x0 + (stepX * i); + yt = y0 + (stepY * i); + points.push({ x: xt, y: yt }); + + this.emit('step', gameObject, this, xt, yt); + + if (this.cancelStepFlag) { + break; + } + } + + this.emit('steps', gameObject, this, points); + + return this; + } + +} + +var StepMode = { + linear: 0, + 'x,y': 1, + 'h,v': 1, + 'y,x': 2, + 'v,h': 2 +} + +export default Step; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/Edit.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/Edit.d.ts new file mode 100644 index 000000000..fed71a9c2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/Edit.d.ts @@ -0,0 +1,7 @@ +import TextEdit from './TextEdit'; + +export default function Edit( + textObject: Phaser.GameObjects.GameObject, + config?: TextEdit.IConfigOpen, + onCloseCallback?: (textObject: Phaser.GameObjects.GameObject) => void +): TextEdit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/Edit.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/Edit.js new file mode 100644 index 000000000..5928b6d71 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/Edit.js @@ -0,0 +1,13 @@ +import TextEdit from './TextEdit.js'; + +var Edit = function (gameObject, config, onCloseCallback) { + if (!gameObject._edit) { + gameObject._edit = new TextEdit(gameObject, { + clickEnable: false + }); + } + gameObject._edit.open(config, onCloseCallback); + return gameObject._edit; +} + +export default Edit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/TextEdit.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/TextEdit.d.ts new file mode 100644 index 000000000..9c816bb03 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/TextEdit.d.ts @@ -0,0 +1,45 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import InputText from '../../inputtext'; + +export default TextEdit; + +declare namespace TextEdit { + interface IConfigOpen { + type?: string, + enterClose?: boolean, + selectAll?: boolean, + + onOpen?: (textObject: Phaser.GameObjects.GameObject) => void, + onTextChanged?: (textObject: Phaser.GameObjects.GameObject, text: string) => void, + onClose?: (textObject: Phaser.GameObjects.GameObject) => void, + + text?: string, + fontFamily?: string, + fontSize?: string, + color?: string, + align?: string, + style?: { [name: string]: any }, + } + + interface IConfig extends IConfigOpen { + clickEnable?: boolean; + } +} + +declare class TextEdit extends ComponentBase { + constructor( + textObject: Phaser.GameObjects.GameObject + ); + + open( + config?: TextEdit.IConfigOpen, + onCloseCallback?: (textObject: Phaser.GameObjects.GameObject) => void + ): this; + + close(): this; + + readonly isOpened: boolean; + readonly text: string; + + readonly inputText: InputText; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/TextEdit.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/TextEdit.js new file mode 100644 index 000000000..c9384bf97 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/TextEdit.js @@ -0,0 +1,60 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import Methods from './methods/Methods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class TextEdit extends ComponentBase { + constructor(gameObject, config) { + super(gameObject); + // this.parent = gameObject; + + this.inputText = undefined; + this.onClose = undefined; + this.delayCall = undefined; + + this.setOpenConfig(config); + + var clickEnable = GetValue(config, 'clickEnable', true); + if (clickEnable) { + gameObject + .on('pointerdown', function () { + this.open(); + }, this) + .setInteractive() + } + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.close(); + + super.shutdown(fromScene); + } + + setOpenConfig(config) { + if (config === undefined) { + config = {}; + } + this.openConfig = config; + return this; + } + + get isOpened() { + return (this.inputText !== undefined); + } + + get text() { + return (this.isOpened) ? this.inputText.text : this.parent.text; + } +} + +Object.assign( + TextEdit.prototype, + Methods, +) + +export default TextEdit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/Close.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/Close.js new file mode 100644 index 000000000..975b9f1b2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/Close.js @@ -0,0 +1,30 @@ +import { CloseLastOpenEditor } from './LastOpenedEditor.js'; + +var Close = function () { + CloseLastOpenEditor(this); + + this.parent.setVisible(true); // Set parent text visible + + if (this.inputText) { + this.inputText.destroy(); + this.inputText = undefined; + } + + if (this.delayCall) { + this.delayCall.remove(); + this.delayCall = undefined; + } + + // Remove close event + this.scene.input.keyboard.off('keydown-ENTER', this.close, this); + this.scene.input.off('pointerdown', this.close, this); + + if (this.onClose) { + this.onClose(this.parent); + } + this.emit('close', this.parent); + + return this; +} + +export default Close; diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/CreateInputText.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/CreateInputText.js new file mode 100644 index 000000000..9d89211a6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/CreateInputText.js @@ -0,0 +1,87 @@ +import InputText from '../../../gameobjects/dom/inputtext/InputText.js'; +import IsTextGameObject from '../../../utils/text/IsTextGameObject.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Clone = Phaser.Utils.Objects.Clone; + +var CreateInputText = function (text, config) { + if (config === undefined) { + config = {}; + } + config = Clone(config); + + var scene = text.scene; + var style = text.style; + var backgroundColor = GetValue(config, 'backgroundColor', style.backgroundColor); + if (backgroundColor === null) { + backgroundColor = 'transparent'; + } + + config.text = GetValue(config, 'text', text.text); + config.fontFamily = GetValue(config, 'fontFamily', style.fontFamily); + config.fontSize = GetValue(config, 'fontSize', style.fontSize); + config.color = GetValue(config, 'color', style.color); + config.backgroundColor = backgroundColor; + config.direction = GetValue(config, 'rtl', style.rtl) ? 'rtl' : 'ltr'; + config.align = GetValue(config, 'align', GetHAlign(style)); + + // Built-in text game object with RTL only has 'right' align + if ((config.direction === 'rtl') && (IsTextGameObject(text))) { + config.align = 'right'; + } + + // config.paddingLeft = 0; + // config.paddingRight = 0; + // config.paddingTop = 0; + // config.paddingBottom = 0; + // var valign = GetVAlign(style); + // switch (valign) { + // case 'top': + // break; + // case 'bottom': + // break; + // } + + var inputText = new InputText(scene, + text.x, text.y, + GetValue(config, 'width', text.width), + GetValue(config, 'height', text.height), + config + ); + + inputText + // Sync origin + .setOrigin(text.originX, text.originY) + // Sync scrollFactor + .setScrollFactor(text.scrollFactorX, text.scrollFactorY) + + var textParentContainer = text.parentContainer; + if (!textParentContainer) { + scene.add.existing(inputText); + } else { + textParentContainer.add(inputText); + } + + return inputText; +} + +var GetHAlign = function (style) { + if (style.hasOwnProperty('align')) { + return style.align; + } else if (style.hasOwnProperty('halign')) { + return style.halign; + } else { + return 'left'; + } +} + +var GetVAlign = function (style) { + if (style.hasOwnProperty('halign')) { + return style.halign; + } else { + return 'top'; + } +} + + +export default CreateInputText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/LastOpenedEditor.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/LastOpenedEditor.js new file mode 100644 index 000000000..ee0f6f509 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/LastOpenedEditor.js @@ -0,0 +1,27 @@ +var LastOpenedEditor = undefined; + +var SetLastOpenedEditor = function (editor) { + if (editor === LastOpenedEditor) { + return; + } + + if (LastOpenedEditor !== undefined) { + LastOpenedEditor.close(); + } + + LastOpenedEditor = editor; +} + +var CloseLastOpenEditor = function (editor) { + if (editor !== LastOpenedEditor) { + return; + } + + // Don't call `LastOpenedEditor.close()` + LastOpenedEditor = undefined; +} + +export { + SetLastOpenedEditor, + CloseLastOpenEditor +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/Methods.js new file mode 100644 index 000000000..b25887976 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/Methods.js @@ -0,0 +1,9 @@ +import Open from './Open.js'; +import Close from './Close.js'; + +var Methods = { + open: Open, + close: Close, +}; + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/Open.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/Open.js new file mode 100644 index 000000000..7c2fd5e53 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textedit/methods/Open.js @@ -0,0 +1,60 @@ +import { SetLastOpenedEditor } from './LastOpenedEditor.js'; +import IsFunction from '../../../utils/object/IsFunction.js'; +import CreateInputTextFromText from './CreateInputText.js'; +import NextTick from '../../../utils/time/NextTick.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Merge = Phaser.Utils.Objects.Merge; + +var Open = function (config, onCloseCallback) { + if (config === undefined) { + config = {}; + } + config = Merge(config, this.openConfig) + + SetLastOpenedEditor(this); + + if (IsFunction(config)) { + onCloseCallback = config; + config = undefined; + } + if (onCloseCallback === undefined) { + onCloseCallback = GetValue(config, 'onClose', undefined); + } + + var onOpenCallback = GetValue(config, 'onOpen', undefined); + var customOnTextChanged = GetValue(config, 'onTextChanged', undefined); + + this.inputText = CreateInputTextFromText(this.parent, config) + .on('textchange', function (inputText) { + var text = inputText.text; + if (customOnTextChanged) { // Custom on-text-changed callback + customOnTextChanged(this.parent, text); + } else { // Default on-text-changed callback + this.parent.text = text; + } + }, this) + .setFocus(); + this.parent.setVisible(false); // Set parent text invisible + + // Attach close event + this.onClose = onCloseCallback; + if (GetValue(config, 'enterClose', true)) { + this.scene.input.keyboard.once('keydown-ENTER', this.close, this); + } + // Attach pointerdown (outside of input-text) event, at next tick + this.delayCall = NextTick(this.scene, function () { + this.scene.input.once('pointerdown', this.close, this); + + // Open editor completly, invoke onOpenCallback + if (onOpenCallback) { + onOpenCallback(this.parent); + } + this.emit('open', this.parent); + + }, this); + + return this; +} + +export default Open; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/TextPage.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/TextPage.d.ts new file mode 100644 index 000000000..c088aff7b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/TextPage.d.ts @@ -0,0 +1,43 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default TextPage; + +declare namespace TextPage { + interface IConfig { + text?: string | string[], + maxLines?: number, + pageBreak?: string, + } +} + +declare class TextPage extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: TextPage.IConfig + ); + + setMaxLines(maxLines: number): this; + maxLines: number; + + setPageBreak(pageBreak?: string): this; + pageBreak: string; + + setText(text: string | string[]): this; + appendText(text: string | string[]): this; + appendPage(text: string | string[]): this; + clearText(): this; + + showPage(pageIndex?: number): this; + showNextPage(): this; + showPreviousPage(): this; + + getPage(pageIndex?: number): string; + getNextPage(): string; + getPreviousPage(): string; + + readonly isLastPage: boolean; + readonly isFirstPage: boolean; + + readonly pageIndex: number; + readonly pageCount: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/TextPage.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/TextPage.js new file mode 100644 index 000000000..936f2fd5c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/TextPage.js @@ -0,0 +1,155 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import { + TextType, TagTextType, BitmapTextType +} from '../../utils/text/GetTextObjectType.js'; +import GetTextObjectType from '../../utils/text/GetTextObjectType.js'; +import TextToLines from '../../utils/text/TextToLines.js'; +import Methods from './methods/Methods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Clamp = Phaser.Math.Clamp; + +class TextPage extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, { eventEmitter: false }); + // No event emitter + // this.parent = gameObject; + + this.textObjectType = GetTextObjectType(this.parent); + + this.pageStartIndexes = []; + + // Text object : array of string + // Tag text object : pens-manager + // Bitmap text object : array of string + this.lines = TextToLines(this.parent, ''); + + this.sections = []; + + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.setMaxLines(GetValue(o, 'maxLines', undefined)); + this.setPageBreak(GetValue(o, 'pageBreak', '\f\n')); + this.setText(GetValue(o, 'text', '')); + this.setStartLineIndex(GetValue(o, 'start', 0)); + this.setPageIndex(GetValue(o, 'page', -1)); + return this; + } + + toJSON() { + return { + maxLines: this.maxLines, + text: this.content, + start: this.startLineIndex, + page: this.pageIndex, + pageBreak: this.pageBreak + }; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + switch (this.textObjectType) { + case TextType: + this.lines.length = 0; + break; + case TagTextType: + this.lines.destroy(); + break; + case BitmapTextType: + this.lines.length = 0; + break; + } + + this.pageStartIndexes.length = 0; + this.sections.length = 0; + + this.lines = undefined; + this.pageStartIndexes = undefined; + this.sections = undefined; + + super.shutdown(fromScene); + } + + setMaxLines(maxLines) { + this.maxLines = maxLines; + return this; + } + + setPageBreak(pageBreak) { + this.pageBreak = pageBreak; + return this; + } + + get pageCount() { + return this.pageStartIndexes.length; + } + + get isFirstPage() { + return (this.pageIndex <= 0); + } + + get isLastPage() { + return (this.pageIndex >= (this.pageCount - 1)); + } + + get totalLinesCount() { + return (this.lines) ? this.lines.length : 0; + } + + get startLineIndex() { + return this._startLineIndex; + } + + set startLineIndex(value) { + value = Clamp(value, 0, this.totalLinesCount - 1); + this._startLineIndex = value; + } + + setStartLineIndex(idx) { + this.startLineIndex = idx; + return this; + } + + get pageLinesCount() { + if (this.maxLines !== undefined) { + return this.maxLines; + + } else { + var count; + switch (this.textObjectType) { + case TextType: + case TagTextType: + var maxLines = this.parent.style.maxLines; + if (maxLines > 0) { + count = maxLines; + } else { + count = this.totalLinesCount; + } + break; + case BitmapTextType: + count = this.totalLinesCount; + break; + } + return count; + + } + } + + get content() { + return this.sections.join(this.pageBreak); + } +} + +Object.assign( + TextPage.prototype, + Methods, +); + + +export default TextPage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/GetLines.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/GetLines.js new file mode 100644 index 000000000..9048fb2f3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/GetLines.js @@ -0,0 +1,29 @@ +import { + TextType, TagTextType, BitmapTextType +} from '../../../utils/text/GetTextObjectType.js'; + +var GetLines = function (startLineIndex, endLineIdx) { + if (startLineIndex === undefined) { + startLineIndex = this.startLineIndex; + } + if (endLineIdx === undefined) { + endLineIdx = startLineIndex + this.pageLinesCount; + } + + var text; + switch (this.textObjectType) { + case TextType: + case BitmapTextType: + text = this.lines.slice(startLineIndex, endLineIdx).join('\n'); + break; + case TagTextType: + var startIdx = this.lines.getLineStartIndex(startLineIndex); + var endIdx = this.lines.getLineEndIndex(endLineIdx - 1); + text = this.lines.getSliceTagText(startIdx, endIdx, true); + break; + } + + return text; +} + +export default GetLines; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/GetPageMethods.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/GetPageMethods.js new file mode 100644 index 000000000..73d9e01f7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/GetPageMethods.js @@ -0,0 +1,32 @@ +const Clamp = Phaser.Math.Clamp; + +export default { + getPage(idx) { + if (idx === undefined) { + idx = this.pageIndex; + } + + return this.setPageIndex(idx).getLines(this.startLineIndex, this.endLineIndex); + }, + + getNextPage() { + return this.getPage(this.pageIndex + 1); + }, + + getPreviousPage() { + return this.getPage(this.pageIndex - 1); + }, + + resetPageIdx() { + this.pageIndex = -1; + return this; + }, + + setPageIndex(idx) { + idx = Clamp(idx, 0, this.pageCount - 1); + this.pageIndex = idx; + this.startLineIndex = this.pageStartIndexes[idx]; + this.endLineIndex = this.pageStartIndexes[idx + 1]; + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/Methods.js new file mode 100644 index 000000000..d5aeb5238 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/Methods.js @@ -0,0 +1,17 @@ +import GetLines from './GetLines.js'; +import SetContentMethods from './SetContentMethods.js'; +import GetPageMethods from './GetPageMethods.js'; +import ShowMethods from './ShowMethods.js'; + +var Methods = { + getLines: GetLines, +} + +Object.assign( + Methods, + SetContentMethods, + GetPageMethods, + ShowMethods +); + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/SetContentMethods.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/SetContentMethods.js new file mode 100644 index 000000000..921d56b37 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/SetContentMethods.js @@ -0,0 +1,69 @@ +import TextToLines from "../../../utils/text/TextToLines.js"; + +var GetString = function (text) { + if (Array.isArray(text)) { + text = text.join('\n'); + } else if (typeof (text) === 'number') { + text = text.toString(); + } + return text; +} + +export default { + clearText() { + this.sections.length = 0; + this.pageStartIndexes.length = 0; + this.lines.length = 0; + + return this; + }, + + appendPage(text) { + var pageStartIndex = this.totalLinesCount; + + this.sections.push(GetString(text)); + var text = this.sections.join('\n'); + this.lines = TextToLines(this.parent, text, this.lines); + + var newLinesCount = this.totalLinesCount - pageStartIndex; + var pageCount = Math.ceil(newLinesCount / this.pageLinesCount); + for (var i = 0; i < pageCount; i++) { + this.pageStartIndexes.push( + pageStartIndex + (i * this.pageLinesCount) + ); + } + + return this; + }, + + setText(text, resetPageIdx) { + if (resetPageIdx === undefined) { + resetPageIdx = true; + } + + if (resetPageIdx) { + this.resetPageIdx(); + } + + this.clearText(); + + var sections = GetString(text).split(this.pageBreak); + // if (sections[sections.length - 1] === '') { // Last section is an empty string + // sections.length -= 1; + // } + + for (var i = 0, cnt = sections.length; i < cnt; i++) { + this.appendPage(sections[i]); + } + + return this; + }, + + appendText(text) { + var content = this.content + GetString(text); + this.setText(content, false); + return this; + }, + + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/ShowMethods.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/ShowMethods.js new file mode 100644 index 000000000..3fa07eeb0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/textpage/methods/ShowMethods.js @@ -0,0 +1,49 @@ +import SetNoWrapText from '../../../utils/text/SetNoWrapText.js'; + +export default { + showPage(idx) { + this.displayText( + this.getPage(idx) + ); + return this; + }, + + showNextPage() { + this.displayText( + this.getNextPage() + ); + return this; + }, + + showPreviousPage() { + this.displayText( + this.getPreviousPage() + ); + return this; + }, + + show() { + this.displayText( + this.getLines() + ); + return this; + }, + + showNextLine() { + this.displayText( + this.setStartLineIndex(this.startLineIndex + 1).getLines() + ); + return this; + }, + + showPreviousLine() { + this.displayText( + this.setStartLineIndex(this.startLineIndex - 1).getLines() + ); + return this; + }, + + displayText(text) { + SetNoWrapText(this.parent, text); + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/texttranslation/TextTranslation.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/texttranslation/TextTranslation.d.ts new file mode 100644 index 000000000..98d6b2633 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/texttranslation/TextTranslation.d.ts @@ -0,0 +1,36 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default TextTranslation; + +declare namespace TextTranslation { + type SetTextCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + text: string + ) => void; + + type InterpolationsType = { [name: string]: any }; + + interface IConfig { + translationKey?: string, + interpolation?: InterpolationsType, + updateText?: boolean, + setText?: SetTextCallbackType, + } +} + +declare class TextTranslation extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: TextTranslation.IConfig + ); + + setInterpolation(interpolation: TextTranslation.InterpolationsType): this; + updateInterpolation(key: string, value: any): this; + updateInterpolation(interpolation: TextTranslation.InterpolationsType): this; + interpolation: TextTranslation.InterpolationsType; + + setTranslationKey(key: string): this; + translationKey: string; + + updateText(): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/texttranslation/TextTranslation.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/texttranslation/TextTranslation.js new file mode 100644 index 000000000..c00f5d4dd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/texttranslation/TextTranslation.js @@ -0,0 +1,86 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; + +var i18next; +const GetValue = Phaser.Utils.Objects.GetValue; + +class TextTranslation extends ComponentBase { + static setI18Next(obj) { + i18next = obj; + } + + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this.resetFromJSON(config); + + this.onLanguageChanged = this.updateText.bind(this); + i18next.on('languageChanged', this.onLanguageChanged); + } + + resetFromJSON(o) { + this.setSetTextCallback(GetValue(o, 'setText', DefaultSetTextCallback)); + this.setInterpolation(GetValue(o, 'interpolation')); + this.setTranslationKey(GetValue(o, 'translationKey', '')); + if (GetValue(o, 'updateText', true)) { + this.updateText(); + } + return this; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + i18next.off('languageChanged', this.onLanguageChanged); + this.interpolation = null; + + super.shutdown(fromScene); + } + + setSetTextCallback(callback) { + this.setTextCallback = callback; + return this; + } + + setInterpolation(interpolation) { + this.interpolation = interpolation; + return this; + } + + updateInterpolation(key, value) { + if (!this.interpolation) { + this.interpolation = {}; + } + + if (typeof (key) === 'string') { + this.interpolation[key] = value; + } else { + var data = key; + for (key in data) { + this.interpolation[key] = data[key]; + } + } + return this; + } + + setTranslationKey(key) { + this.translationKey = key; + return this; + } + + updateText() { + var text = i18next.t(this.translationKey, this.interpolation); + this.setTextCallback(this.parent, text); + return this; + } + +} + +var DefaultSetTextCallback = function (gameObject, text) { + gameObject.setText(text); +} + +export default TextTranslation; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/texttyping/TextTyping.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/texttyping/TextTyping.d.ts new file mode 100644 index 000000000..c87d18406 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/texttyping/TextTyping.d.ts @@ -0,0 +1,44 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default TextTyping; + +declare namespace TextTyping { + + type TypeModeType = 0 | 1 | 2 | 3 | 'left-to-right' | 'right-to-left' | 'middle-to-sides' | 'sides-to-middle'; + type SetTextCallbackType = (text: string, isLastChar: boolean, insertIdx: number) => string; + + interface IConfig { + speed?: number, + typeMode?: TypeModeType, + setTextCallback?: SetTextCallbackType, + setTextCallbackScope?: Object + } + + namespace Events { + type TypingCallbackType = () => void; + type TypingCompleteCallbackType = (typing: TextTyping, txt: string) => void; + } +} + +declare class TextTyping extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: TextTyping.IConfig + ); + + start(content: string, speed?: number): this; + appendText(content: string): this; + stop(showAllText?: boolean): this; + + pause(): this; + resume(): this; + + setTypeSpeed(speed: number): this; + setTypingSpeed(speed: number): this; + speed: number; + + setTypeMode(mode: TextTyping.TypeModeType): this; + typeMode: number; + + readonly isTyping: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/texttyping/TextTyping.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/texttyping/TextTyping.js new file mode 100644 index 000000000..84e7a8f36 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/texttyping/TextTyping.js @@ -0,0 +1,325 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import GetWrapText from '../../utils/text/GetWrapText.js'; +import SetNoWrapText from '../../utils/text/SetNoWrapText.js'; + +const GetFastValue = Phaser.Utils.Objects.GetFastValue; +const GetValue = Phaser.Utils.Objects.GetValue; + +class TextTyping extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this.timer = null; + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.setTextWrapEnable(GetValue(o, 'wrap', false)); + this.setTypeMode(GetValue(o, 'typeMode', 0)); + this.setTypingSpeed(GetValue(o, 'speed', 333)); + this.setTextCallback = GetFastValue(o, 'setTextCallback', null); + this.setTextCallbackScope = GetFastValue(o, 'setTextCallbackScope', null); + + this.setTypingContent(GetFastValue(o, 'text', '')); + this.typingIdx = GetFastValue(o, 'typingIdx', 0); + this.insertIdx = GetFastValue(o, 'insertIdx', null); + + var elapsed = GetFastValue(o, 'elapsed', null); + if (elapsed !== null) { + this.start(undefined, undefined, this.typingIdx, elapsed); + } + + return this; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.freeTimer(); + + super.shutdown(fromScene); + } + + setTypeMode(m) { + if (typeof (m) === 'string') { + m = TYPEMODE[m]; + } + this.typeMode = m; + return this; + } + + setTypeSpeed(speed) { + this.speed = speed; + return this; + } + + setTypingSpeed(speed) { + this.speed = speed; + return this; + } + + setTextWrapEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.textWrapEnable = enable; + return this; + } + + set text(value) { + var text = TransferText(value); + if (this.textWrapEnable) { + text = GetWrapText(this.parent, text); + } + + this._text = text; + } + + get text() { + return this._text; + } + + get isTyping() { + return (this.getTimer() !== null); + } + + get isLastChar() { + return (this.typingIdx === this.textLen); + } + + start(text, speed, startIdx, timerStartAt) { + if (text !== undefined) { + this.setTypingContent(text); + } + if (speed !== undefined) { + this.speed = speed; + } + if (startIdx === undefined) { + startIdx = 0; + } + + this.typingIdx = startIdx + 1; + if (this.speed === 0) { + this.stop(true); + } else { + this.setText(''); + this.startTimer(timerStartAt); + } + + return this; + } + + appendText(text) { + var newText = this.text.concat(TransferText(text)); + if (this.isTyping) { + this.setTypingContent(newText); + } else { + this.start(newText, undefined, this.textLen); + } + + return this; + } + + stop(showAllText) { + var timer = this.getTimer(); + if (timer) { + this.freeTimer(); + } + if (showAllText) { + this.typingIdx = this.textLen; + this.setText(this.text); + this.emit('type'); + this.emit('complete', this, this.parent); + } + + return this; + } + + pause() { + var timer = this.getTimer(); + if (timer) { + timer.paused = true; + } + return this; + } + + resume() { + var timer = this.getTimer(); + if (timer) { + timer.paused = false; + } + return this; + } + + setTypingContent(text) { + this.text = text; + this.textLen = this.getTextLength(this.text); + return this; + } + + onTyping() { + var newText = this.getTypingString(this.text, this.typingIdx, this.textLen, this.typeMode); + + this.setText(newText); + + this.emit('type'); + + if (this.isLastChar) { + this.freeTimer(); + this.emit('complete', this, this.parent); + } else { + this.timer.delay = this.speed; // delay of next typing + this.typingIdx++; + } + } + + getTypingString(text, typeIdx, textLen, typeMode) { + var result; + if (typeMode === 0) { //left-to-right + var startIdx = 0; + var endIdx = typeIdx; + this.insertIdx = endIdx; + result = this.getSubString(text, startIdx, endIdx); + + } else if (typeMode === 1) { //right-to-left + var endIdx = textLen; + var startIdx = endIdx - typeIdx; + this.insertIdx = 0; + result = this.getSubString(text, startIdx, endIdx); + + } else if (typeMode === 2) { //middle-to-sides + var midIdx = textLen / 2; + var startIdx = Math.floor(midIdx - (typeIdx / 2)); + var endIdx = startIdx + typeIdx; + this.insertIdx = (typeIdx % 2) ? typeIdx : 0; + result = this.getSubString(text, startIdx, endIdx); + + } else if (typeMode === 3) { //sides-to-middle + var lowerLen = Math.floor(typeIdx / 2); + var lowerResult; + if (lowerLen > 0) { + var endIdx = textLen; + var startIdx = endIdx - lowerLen; + lowerResult = this.getSubString(text, startIdx, endIdx); + } else { + lowerResult = ""; + } + + var upperLen = typeIdx - lowerLen; + var upperResult; + if (upperLen > 0) { + var startIdx = 0; + var endIdx = startIdx + upperLen; + this.insertIdx = endIdx; + upperResult = this.getSubString(text, startIdx, endIdx); + } else { + upperResult = ""; + this.insertIdx = 0; + } + result = upperResult + lowerResult; + } + + return result; + } + + startTimer(timerStartAt) { + if (this.timer) { + this.freeTimer(); + } + var delay, startAt; + if (timerStartAt === undefined) { + delay = 0; + startAt = 0; + } else { + delay = this.speed; + startAt = timerStartAt; + } + + this.timer = this.scene.time.addEvent({ + delay: 0.0001, + startAt: startAt, + loop: true, + callback: this.onTyping, + callbackScope: this + }); + // Note: Throw error message if delay is 0 with repeat/loop + + return this; + } + + getTimer() { + return this.timer; + } + + freeTimer() { + if (this.timer) { + this.timer.remove(); + this.timer = null; + } + + return this; + } + + setText(text) { + if (this.setTextCallback) { + if (this.setTextCallbackScope) { + text = this.setTextCallback.call(this.setTextCallbackScope, text, this.isLastChar, this.insertIdx); + } else { + text = this.setTextCallback(text, this.isLastChar, this.insertIdx); + } + } + + if (this.textWrapEnable) { + SetNoWrapText(this.parent, text); + } else { + this.parent.setText(text); + } + } + + getTextLength(text) { + var gameObject = this.parent; + var len; + if (gameObject.getPlainText) { + len = gameObject.getPlainText(text).length; + } else { + len = text.length; + } + + return len; + } + + getSubString(text, startIdx, endIdx) { + var gameObject = this.parent; + var result; + if (gameObject.getSubString) { + result = gameObject.getSubString(text, startIdx, endIdx); + } else { + result = text.slice(startIdx, endIdx); + } + + return result; + } +} + +var TransferText = function (text) { + if (Array.isArray(text)) { + text = text.join('\n'); + } else if (typeof (text) === 'number') { + text = text.toString(); + } + return text; +} + +const TYPEMODE = { + 'left-to-right': 0, + 'right-to-left': 1, + 'middle-to-sides': 2, + 'sides-to-middle': 3 +}; + + +export default TextTyping; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/tintrgb/AddTintRGBProperties.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/tintrgb/AddTintRGBProperties.d.ts new file mode 100644 index 000000000..3f2120060 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/tintrgb/AddTintRGBProperties.d.ts @@ -0,0 +1,15 @@ +export default AddTintRGBProperties; + +declare namespace AddTintRGBProperties { + interface TintRGBGameObject extends Phaser.GameObjects.GameObject { + tintR: number; + tintG: number; + tintB: number; + tintGray: number; + } +} + +declare function AddTintRGBProperties( + gameObject: Phaser.GameObjects.GameObject, + colorRGB?: number +): AddTintRGBProperties.TintRGBGameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/tintrgb/AddTintRGBProperties.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/tintrgb/AddTintRGBProperties.js new file mode 100644 index 000000000..f51f4d0e8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/tintrgb/AddTintRGBProperties.js @@ -0,0 +1,94 @@ +import { GetR, GetG, GetB } from '../../utils/color/GetRGB.js'; +import { SetR, SetG, SetB, SetRGB } from '../../utils/color/SetColor.js'; + +var AddTintRGBProperties = function (gameObject, tintRGB) { + // Don't attach properties again + if (gameObject.hasOwnProperty('tintR')) { + return gameObject; + } + + if (tintRGB === undefined) { + tintRGB = 0xffffff; + } + + var tintR = GetR(tintRGB); + var tintG = GetG(tintRGB); + var tintB = GetB(tintRGB); + + // Override tint property + Object.defineProperty(gameObject, 'tint', { + get: function () { + return tintRGB; + }, + set: function (value) { + value = Math.floor(value) & 0xffffff; + if (gameObject.setTint) { + gameObject.setTint(value); + } + if (tintRGB !== value) { + tintRGB = value; + tintR = GetR(tintRGB); + tintG = GetG(tintRGB); + tintB = GetB(tintRGB); + // gameObject.emit('_tintchange', value, tintR, tintG, tintB); + } + } + }); + + Object.defineProperty(gameObject, 'tintR', { + get: function () { + return tintR; + }, + set: function (value) { + value = Math.floor(value) & 0xff; + if (tintR !== value) { + tintR = value; + gameObject.tint = SetR(tintRGB, value); + } + }, + }) + Object.defineProperty(gameObject, 'tintG', { + get: function () { + return tintG; + }, + set: function (value) { + value = Math.floor(value) & 0xff; + if (tintG !== value) { + tintG = value; + gameObject.tint = SetG(tintRGB, value); + } + }, + }) + Object.defineProperty(gameObject, 'tintB', { + get: function () { + return tintB; + }, + set: function (value) { + value = Math.floor(value) & 0xff; + if (tintB !== value) { + tintB = value; + gameObject.tint = SetB(tintRGB, value); + } + }, + }) + Object.defineProperty(gameObject, 'tintGray', { + get: function () { + return Math.floor((tintR + tintG + tintB) / 3); + }, + set: function (value) { + value = Math.floor(value) & 0xff; + if ((tintR !== value) || (tintG !== value) || (tintB !== value)) { + tintR = value; + tintG = value; + tintB = value; + gameObject.tint = SetRGB(tintRGB, value, value, value); + } + }, + }) + + gameObject.tint = tintRGB; + + return gameObject; +} + +export default AddTintRGBProperties; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/AddViewportCoordinateProperties.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/AddViewportCoordinateProperties.d.ts new file mode 100644 index 000000000..633ac4048 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/AddViewportCoordinateProperties.d.ts @@ -0,0 +1,55 @@ +export default AddViewportCoordinateProperties; + +declare namespace AddViewportCoordinateProperties { + interface PolarCoordinateGameObject extends Phaser.GameObjects.GameObject { + vp: Phaser.Geom.Rectangle; + vpx: number; + vpy: number; + vpxOffset: number; + vpyOffset: number; + } + + type TransformCallbackType0 = + ( + vpx: number, + vpy: number, + viewport: Phaser.Geom.Rectangle, + gameObject: Phaser.GameObjects.GameObject, + ) => void; + + type TransformCallbackType1 = + ( + vpx: number, + vpy: number, + vpxOffset: number, + vpyOffset: number, + viewport: Phaser.Geom.Rectangle, + gameObject: Phaser.GameObjects.GameObject, + ) => void; + + type TransformCallbackType = TransformCallbackType0 | TransformCallbackType1; +} + +declare function AddViewportCoordinateProperties( + gameObject: Phaser.GameObjects.GameObject, + viewport?: Phaser.Geom.Rectangle, + vpx?: number, + vpy?: number, + vpxOffset?: number, + vpyOffset?: number, + transformCallback?: AddViewportCoordinateProperties.TransformCallbackType +): AddViewportCoordinateProperties.PolarCoordinateGameObject; + +declare function AddViewportCoordinateProperties( + gameObject: Phaser.GameObjects.GameObject, + viewport?: Phaser.Geom.Rectangle, + vpx?: number, + vpy?: number, + transformCallback?: AddViewportCoordinateProperties.TransformCallbackType +): AddViewportCoordinateProperties.PolarCoordinateGameObject; + +declare function AddViewportCoordinateProperties( + gameObject: Phaser.GameObjects.GameObject, + viewport?: Phaser.Geom.Rectangle, + transformCallback?: AddViewportCoordinateProperties.TransformCallbackType +): AddViewportCoordinateProperties.PolarCoordinateGameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/AddViewportCoordinateProperties.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/AddViewportCoordinateProperties.js new file mode 100644 index 000000000..7fbc23587 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/AddViewportCoordinateProperties.js @@ -0,0 +1,97 @@ +import MonitorViewport from './MonitorViewport.js'; +import VPXYToXY from './VPXYToXY.js'; + +var AddViewportCoordinateProperties = function (gameObject, viewport, vpx, vpy, vpxOffset, vpyOffset, transformCallback) { + // Don't attach properties again + if (gameObject.hasOwnProperty('vp')) { + return gameObject; + } + + if (typeof (vpx) === 'function') { + transformCallback = vpx; + vpx = undefined; + } + + if (typeof (vpxOffset) === 'function') { + transformCallback = vpxOffset; + vpxOffset = undefined; + } + + + if (vpx === undefined) { vpx = 0.5; } + if (vpy === undefined) { vpy = 0.5; } + if (vpxOffset === undefined) { vpxOffset = 0; } + if (vpyOffset === undefined) { vpyOffset = 0; } + + if (transformCallback === undefined) { + transformCallback = VPXYToXY; + } + + MonitorViewport(viewport); + var events = viewport.events; + + gameObject.vp = viewport; + + // Set position of game object when view-port changed. + var Transform = function () { + transformCallback(vpx, vpy, vpxOffset, vpyOffset, viewport, gameObject); + } + + events.on('update', Transform); + gameObject.once('destroy', function () { + events.off('update', Transform); + gameObject.vp = undefined; + }) + + Object.defineProperty(gameObject, 'vpx', { + get: function () { + return vpx; + }, + set: function (value) { + if (vpx !== value) { + vpx = value; + Transform(); + } + }, + }); + + Object.defineProperty(gameObject, 'vpy', { + get: function () { + return vpy; + }, + set: function (value) { + if (vpy !== value) { + vpy = value; + Transform(); + } + }, + }); + + Object.defineProperty(gameObject, 'vpxOffset', { + get: function () { + return vpxOffset; + }, + set: function (value) { + if (vpxOffset !== value) { + vpxOffset = value; + Transform(); + } + }, + }); + + Object.defineProperty(gameObject, 'vpyOffset', { + get: function () { + return vpyOffset; + }, + set: function (value) { + if (vpyOffset !== value) { + vpyOffset = value; + Transform(); + } + }, + }); + + Transform(); +} + +export default AddViewportCoordinateProperties; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/MonitorViewport.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/MonitorViewport.js new file mode 100644 index 000000000..2f9331286 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/MonitorViewport.js @@ -0,0 +1,71 @@ +const EventEmitter = Phaser.Events.EventEmitter; + +var MonitorViewport = function (viewport) { + if (viewport.events) { + return viewport; + } + + var events = new EventEmitter(); + + var x = viewport.x; + Object.defineProperty(viewport, 'x', { + get: function () { + return x; + }, + + set: function (value) { + if (x !== value) { + x = value; + events.emit('update', viewport); + } + }, + }); + + var y = viewport.y; + Object.defineProperty(viewport, 'y', { + get: function () { + return y; + }, + + set: function (value) { + if (y !== value) { + y = value; + events.emit('update', viewport); + } + }, + }); + + var width = viewport.width; + Object.defineProperty(viewport, 'width', { + get: function () { + return width; + }, + + set: function (value) { + if (width !== value) { + width = value; + events.emit('update', viewport); + } + }, + }); + + var height = viewport.height; + Object.defineProperty(viewport, 'height', { + get: function () { + return height; + }, + + set: function (value) { + if (height !== value) { + height = value; + events.emit('update', viewport); + } + }, + }); + + viewport.events = events; + + return viewport; +} + +export default MonitorViewport; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/VPXYToXY.d.ts b/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/VPXYToXY.d.ts new file mode 100644 index 000000000..c2a2ea7f1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/VPXYToXY.d.ts @@ -0,0 +1,17 @@ +export default VPXYToXY; + +declare function VPXYToXY( + vpx: number, + vpy: number, + vpxOffset: number, + vpyOffset: number, + viewport: Phaser.Geom.Rectangle, + out?: Phaser.Math.Vector2, +): Phaser.Math.Vector2; + +declare function VPXYToXY( + vpx: number, + vpy: number, + viewport: Phaser.Geom.Rectangle, + out?: Phaser.Math.Vector2, +): Phaser.Math.Vector2; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/VPXYToXY.js b/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/VPXYToXY.js new file mode 100644 index 000000000..d884976e4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviors/viewportcoordinate/VPXYToXY.js @@ -0,0 +1,20 @@ +var VPXYToXY = function (vpx, vpy, vpxOffset, vpyOffset, viewport, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = GlobXY; + } + + if (typeof (vpxOffset) !== 'number') { + vpxOffset = 0; + vpyOffset = 0; + } + + out.x = viewport.x + (viewport.width * vpx) + vpxOffset; + out.y = viewport.y + (viewport.height * vpy) + vpyOffset; + return out; +} + +var GlobXY = {}; + +export default VPXYToXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/behaviortree-plugin.js b/ui/src/phaser3-rex-plugins/plugins/behaviortree-plugin.js new file mode 100644 index 000000000..f350a8e63 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/behaviortree-plugin.js @@ -0,0 +1,17 @@ +import ObjectFactory from './logic/behaviortree/ObjectFactory.js'; +import Factory from './logic/behaviortree/Factory.js'; + +class BehaviorTreePlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + + this.add = new ObjectFactory(); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +export default BehaviorTreePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bitmapzone-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/bitmapzone-plugin.d.ts new file mode 100644 index 000000000..bae23bd84 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bitmapzone-plugin.d.ts @@ -0,0 +1,9 @@ +import BitmapZone from './bitmapzone'; + +export default class BitmapZonePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: BitmapZone.IConfig + ): BitmapZone; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bitmapzone-plugin.js b/ui/src/phaser3-rex-plugins/plugins/bitmapzone-plugin.js new file mode 100644 index 000000000..765bc9706 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bitmapzone-plugin.js @@ -0,0 +1,19 @@ +import BitmapZone from './bitmapzone.js'; + +class BitmapZonePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(source, config) { + return new BitmapZone(source, config); + } +} + +export default BitmapZonePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bitmapzone.d.ts b/ui/src/phaser3-rex-plugins/plugins/bitmapzone.d.ts new file mode 100644 index 000000000..7ffab78f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bitmapzone.d.ts @@ -0,0 +1,2 @@ +import BitmapZone from './behaviors/bitmapzone/BitmapZone.js'; +export default BitmapZone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bitmapzone.js b/ui/src/phaser3-rex-plugins/plugins/bitmapzone.js new file mode 100644 index 000000000..7ffab78f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bitmapzone.js @@ -0,0 +1,2 @@ +import BitmapZone from './behaviors/bitmapzone/BitmapZone.js'; +export default BitmapZone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/blitter-plugin.js b/ui/src/phaser3-rex-plugins/plugins/blitter-plugin.js new file mode 100644 index 000000000..fda4ab16d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/blitter-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/blitter/blitter/Factory.js'; +import Creator from './gameobjects/blitter/blitter/Creator.js'; +import Blitter from './gameobjects/blitter/blitter/Blitter.js'; +import SetValue from './utils/object/SetValue.js'; + +class BlitterPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexBlitter', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.Blitter', Blitter); + +export default BlitterPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/blitter.js b/ui/src/phaser3-rex-plugins/plugins/blitter.js new file mode 100644 index 000000000..a66b9f23d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/blitter.js @@ -0,0 +1,2 @@ +import Blitter from './gameobjects/blitter/blitter/Blitter.js'; +export default Blitter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board-components.d.ts b/ui/src/phaser3-rex-plugins/plugins/board-components.d.ts new file mode 100644 index 000000000..b8e95c9b1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board-components.d.ts @@ -0,0 +1,29 @@ +import Board from './board/board/Board'; +import HexagonGrid from './board/grid/hexagon/Hexagon'; +import QuadGrid from './board/grid/quad/Quad'; +import Shape from './board/shape/Shape'; +import Match from './board/match/Match'; +import MoveTo from './board/moveto/MoveTo'; +import PathFinder from './board/pathfinder/PathFinder'; +import FieldOfView from './board/fieldofview/FieldOfView'; +import Monopoly from './board/monopoly/Monopoly'; +import MiniBoard from './board/miniboard/MiniBoard'; +import HexagonMap from './board/hexagonmap/index'; +import CreateTileTexture from './board/texture/CreateTileTexture'; +import CreateBoardFromTilemap from './board/tilemap/CreateBoardFromTilemap'; + +export { + Board, + HexagonGrid, + QuadGrid, + Shape, + Match, + MoveTo, + PathFinder, + FieldOfView, + Monopoly, + MiniBoard, + HexagonMap, + CreateTileTexture, + CreateBoardFromTilemap +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board-components.js b/ui/src/phaser3-rex-plugins/plugins/board-components.js new file mode 100644 index 000000000..aaf2ee752 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board-components.js @@ -0,0 +1,29 @@ +import Board from './board/board/Board.js'; +import HexagonGrid from './board/grid/hexagon/Hexagon.js'; +import QuadGrid from './board/grid/quad/Quad.js'; +import Shape from './board/shape/Shape.js'; +import Match from './board/match/Match.js'; +import MoveTo from './board/moveto/MoveTo.js'; +import PathFinder from './board/pathfinder/PathFinder.js'; +import FieldOfView from './board/fieldofview/FieldOfView.js'; +import Monopoly from './board/monopoly/Monopoly.js'; +import MiniBoard from './board/miniboard/MiniBoard.js'; +import HexagonMap from './board/hexagonmap/index.js'; +import CreateTileTexture from './board/texture/CreateTileTexture.js'; +import CreateBoardFromTilemap from './board/tilemap/CreateBoardFromTilemap.js'; + +export { + Board, + HexagonGrid, + QuadGrid, + Shape, + Match, + MoveTo, + PathFinder, + FieldOfView, + Monopoly, + MiniBoard, + HexagonMap, + CreateTileTexture, + CreateBoardFromTilemap +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board-logic.d.ts b/ui/src/phaser3-rex-plugins/plugins/board-logic.d.ts new file mode 100644 index 000000000..678649ff5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board-logic.d.ts @@ -0,0 +1,19 @@ +import Board from './board/board/LogicBoard'; +import Quad from './board/grid/quad/Quad'; +import Hexagon from './board/grid/hexagon/Hexagon'; +import Match from './board/match/Match'; +import PathFinder from './board/pathfinder/PathFinder'; +import FieldOfView from './board/fieldofview/FieldOfView'; +import Monopoly from './board/monopoly/Monopoly'; +import HexagonMap from './board/hexagonmap/index'; + +export { + Board, + Quad, + Hexagon, + Match, + PathFinder, + FieldOfView, + Monopoly, + HexagonMap, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board-logic.js b/ui/src/phaser3-rex-plugins/plugins/board-logic.js new file mode 100644 index 000000000..cb1352a44 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board-logic.js @@ -0,0 +1,19 @@ +import Board from './board/board/LogicBoard.js'; +import Quad from './board/grid/quad/Quad.js'; +import Hexagon from './board/grid/hexagon/Hexagon.js'; +import Match from './board/match/Match.js'; +import PathFinder from './board/pathfinder/PathFinder.js'; +import FieldOfView from './board/fieldofview/FieldOfView.js'; +import Monopoly from './board/monopoly/Monopoly.js'; +import HexagonMap from './board/hexagonmap/index.js'; + +export { + Board, + Quad, + Hexagon, + Match, + PathFinder, + FieldOfView, + Monopoly, + HexagonMap, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/board-plugin.d.ts new file mode 100644 index 000000000..82abef55b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board-plugin.d.ts @@ -0,0 +1,63 @@ +// import * as Phaser from 'phaser'; + +import BoardFactory from './board/board/Factory.js'; +import QuadGridFactory from './board/grid/quad/Factory'; +import HexagonGridFactory from './board/grid/hexagon/Factory'; +import ShapeFactory from './board/shape/Factory'; +import MoveToFactory from './board/moveto/Factory'; +import PathFinderFactory from './board/pathfinder/Factory'; +import MatchFactory from './board/match/Factory'; +import FieldOfViewFactory from './board/fieldofview/Factory'; +import MonopolyFactory from './board/monopoly/Factory'; +import MiniBoardFactory from './board/miniboard/Factory'; + +import HexagonMap from './board/hexagonmap/index'; +import CreateTileTexture from './board/texture/CreateTileTexture'; +import CreateBoardFromTilemap from './board/tilemap/CreateBoardFromTilemap'; + +export default BoardPlugin; + +declare class Factories { + board: typeof BoardFactory; + quadGrid: typeof QuadGridFactory; + hexagonGrid: typeof HexagonGridFactory; + shape: typeof ShapeFactory; + moveTo: typeof MoveToFactory; + pathFinder: typeof PathFinderFactory; + match: typeof MatchFactory; + fieldOfView: typeof FieldOfViewFactory; + monopoly: typeof MonopolyFactory; + miniBoard: typeof MiniBoardFactory; +} + +declare class BoardPlugin extends Phaser.Plugins.ScenePlugin { + add: Factories; + + hexagonMap: HexagonMap; + createTileTexture: typeof CreateTileTexture; + createBoardFromTilemap: typeof CreateBoardFromTilemap; +} + +import BoardClass from './board/board/Board'; +import HexagonClass from './board/grid/hexagon/Hexagon'; +import QuadClass from './board/grid/quad/Quad'; +import ShapeClass from './board/shape/Shape'; +import MoveToClass from './board/moveto/MoveTo'; +import MatchClass from './board/match/Match'; +import PathFinderClass from './board/pathfinder/PathFinder'; +import FieldOfViewClass from './board/fieldofview/FieldOfView'; +import MonopolyClass from './board/monopoly/Monopoly'; +import MiniBoardClass from './board/miniboard/MiniBoard'; + +declare namespace BoardPlugin { + type Board = BoardClass; + type Quad = QuadClass; + type Hexagon = HexagonClass; + type Shape = ShapeClass; + type MoveTo = MoveToClass; + type Match = MatchClass; + type PathFinder = PathFinderClass; + type FieldOfView = FieldOfViewClass; + type Monopoly = MonopolyClass; + type MiniBoard = MiniBoardClass; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board-plugin.js b/ui/src/phaser3-rex-plugins/plugins/board-plugin.js new file mode 100644 index 000000000..c358d4215 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board-plugin.js @@ -0,0 +1,40 @@ +import ObjectFactory from './board/ObjectFactory.js'; + +import BoardFactory from './board/board/Factory.js'; +import HexagonFactory from './board/grid/hexagon/Factory.js'; +import QuadFactory from './board/grid/quad/Factory.js'; +import ShapeFactory from './board/shape/Factory.js'; + +import MoveToFactory from './board/moveto/Factory.js'; +import MatchFactory from './board/match/Factory.js'; +import PathFinderFactory from './board/pathfinder/Factory.js'; +import FieldOfViewFactory from './board/fieldofview/Factory.js'; +import MonopolyFactory from './board/monopoly/Factory.js'; + +import MiniBoardFactory from './board/miniboard/Factory.js'; + +import HexagonMap from './board/hexagonmap/index.js'; + +import CreateTileTexture from './board/texture/CreateTileTexture.js'; + +import CreateBoardFromTilemap from './board/tilemap/CreateBoardFromTilemap.js'; + +class BoardPlugin extends Phaser.Plugins.ScenePlugin { + constructor(scene, pluginManager) { + super(scene, pluginManager); + + this.add = new ObjectFactory(scene); + + // Helper functions + this.hexagonMap = HexagonMap; + this.createTileTexture = CreateTileTexture; + this.createBoardFromTilemap = CreateBoardFromTilemap; + } + + start() { + var eventEmitter = this.scene.sys.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +export default BoardPlugin; diff --git a/ui/src/phaser3-rex-plugins/plugins/board/ObjectFactory.js b/ui/src/phaser3-rex-plugins/plugins/board/ObjectFactory.js new file mode 100644 index 000000000..ebd288be8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/ObjectFactory.js @@ -0,0 +1,20 @@ +class ObjectFactory { + constructor(scene) { + this.scene = scene; + this.displayList = scene.sys.displayList; + this.updateList = scene.sys.updateList; + + scene.sys.events.once('destroy', this.destroy, this); + } + + destroy() { + this.scene = null; + this.displayList = null; + this.updateList = null; + } + + static register(type, callback) { + ObjectFactory.prototype[type] = callback; + } +}; +export default ObjectFactory; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/Board.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/board/Board.d.ts new file mode 100644 index 000000000..cbe431d49 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/Board.d.ts @@ -0,0 +1,143 @@ +// import * as Phaser from 'phaser'; +import LogicBoard from './LogicBoard'; +import { TileXYZType, TileXYType } from '../types/Position'; +import { Tap, Press, Swipe } from '../../gestures' + +export default Board; + +declare namespace Board { + interface IConfig extends LogicBoard.IConfig { } + + interface SetInteractiveIConfig { + enable?: boolean; + useTouchZone?: boolean; + } + + namespace Events { + type KickOutCallbackType = ( + chessToAdd: unknown, + occupiedChess: unknown, + tileXYZ: TileXYZType + ) => void; + + type TileDownCallbackType = ( + pointer: Phaser.Input.Pointer, + tileXY: TileXYType + ) => void; + + type GameObjectDownCallbackType = ( + pointer: Phaser.Input.Pointer, + gameObject: Phaser.GameObjects.GameObject + ) => void; + + type PointerDownCallbackType = ( + pointer: Phaser.Input.Pointer + ) => void; + + type TileUpCallbackType = ( + pointer: Phaser.Input.Pointer, + tileXY: TileXYType + ) => void; + + type GameObjectUpCallbackType = ( + pointer: Phaser.Input.Pointer, + gameObject: Phaser.GameObjects.GameObject + ) => void; + + type PointerUpCallbackType = ( + pointer: Phaser.Input.Pointer + ) => void; + + type TileMoveCallbackType = ( + pointer: Phaser.Input.Pointer, + tileXY: TileXYType + ) => void; + + type GameObjectMoveCallbackType = ( + pointer: Phaser.Input.Pointer, + gameObject: Phaser.GameObjects.GameObject + ) => void; + + type PointerMoveCallbackType = ( + pointer: Phaser.Input.Pointer + ) => void; + + type TileOverCallbackType = ( + pointer: Phaser.Input.Pointer, + tileXY: TileXYType + ) => void; + + type GameObjectOverCallbackType = ( + pointer: Phaser.Input.Pointer, + gameObject: Phaser.GameObjects.GameObject + ) => void; + + type PointerOverCallbackType = ( + pointer: Phaser.Input.Pointer + ) => void; + + type TileOutCallbackType = ( + pointer: Phaser.Input.Pointer, + tileXY: TileXYType + ) => void; + + type GameObjectOutCallbackType = ( + pointer: Phaser.Input.Pointer, + gameObject: Phaser.GameObjects.GameObject + ) => void; + + type PointerOutCallbackType = ( + pointer: Phaser.Input.Pointer + ) => void; + + type TileTapCallbackType = ( + tap: Tap, + tileXY: TileXYType + ) => void; + + type GameObjectTapCallbackType = ( + tap: Tap, + gameObject: Phaser.GameObjects.GameObject + ) => void; + + type TapCallbackType = (tap: Tap) => void; + + type TilePressCallbackType = ( + press: Press, + tileXY: TileXYType + ) => void; + + type GameObjectPressCallbackType = ( + press: Press, + gameObject: Phaser.GameObjects.GameObject + ) => void; + + type PressCallbackType = (press: Press) => void; + + type TileSwipeCallbackType = ( + swipe: Swipe, + tileXY: TileXYType + ) => void; + + type GameObjectSwipeCallbackType = ( + swipe: Swipe, + gameObject: Phaser.GameObjects.GameObject + ) => void; + + type SwipeCallbackType = (swipe: Swipe) => void; + } +} + +declare class Board extends LogicBoard { + constructor(scene: Phaser.Scene, config?: Board.IConfig); + scene: Phaser.Scene; + + setInteractive(config?: Board.SetInteractiveIConfig): this; + setInteractive(enable?: boolean): this; + + getTouchZone(): Phaser.GameObjects.Zone; + readonly touchZone: Phaser.GameObjects.Zone; + + chessToBoard(chess: any): Board; + static GetBoard(chess: any): Board; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/Board.js b/ui/src/phaser3-rex-plugins/plugins/board/board/Board.js new file mode 100644 index 000000000..8dbf0d1fc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/Board.js @@ -0,0 +1,28 @@ +import LogicBoard from './LogicBoard.js'; +import SetInteractive from './input/SetInteractive.js'; +import ForEachCullTileXY from './camera/ForEachCullTileXY.js'; + +class Board extends LogicBoard { + get touchZone() { + if (this.input) { + return this.input.touchZone; + } else { + return null; + } + } + + getTouchZone() { + return this.touchZone; + } +} + +var methods = { + setInteractive: SetInteractive, + forEachCullTileXY: ForEachCullTileXY, +} +Object.assign( + Board.prototype, + methods +); + +export default Board; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/board/Factory.d.ts new file mode 100644 index 000000000..65ec51e6d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/Factory.d.ts @@ -0,0 +1,5 @@ +import Board from './Board'; + +export default function ( + config?: Board.IConfig +): Board; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/Factory.js b/ui/src/phaser3-rex-plugins/plugins/board/board/Factory.js new file mode 100644 index 000000000..b7e26d2d6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/Factory.js @@ -0,0 +1,11 @@ +import Board from './Board.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../utils/object/SetValue.js'; + +ObjectFactory.register('board', function (config) { + return new Board(this.scene, config); +}); + +SetValue(window, 'RexPlugins.Board.Board', Board); + +export default Board; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/LogicBoard.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/board/LogicBoard.d.ts new file mode 100644 index 000000000..47624601b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/LogicBoard.d.ts @@ -0,0 +1,441 @@ +import EE from '../../utils/eventemitter/EventEmitter'; +import QuadGrid from '../grid/quad/Quad'; +import HexagonGrid from '../grid/hexagon/Hexagon'; +import Quad from '../grid/quad/Quad'; +import Hexagon from '../grid/hexagon/Hexagon'; +import { + TileXYZType, TileXYType, TileXYDirectionType, + WorldXYType +} from '../types/Position'; +import Line from '../../utils/geom/line/Line'; +import Circle from '../../utils/geom/circle/Circle'; +import Rectangle from '../../utils/geom/rectangle/Rectangle'; +import Ellipse from '../../utils/geom/ellipse/Ellipse'; +import Triangle from '../../utils/geom/triangle/Triangle'; +import Polygon from '../../utils/geom/polygon/Polygon'; + +export default Board; + +declare namespace Board { + type ForEachTileXYOrderTypes = 0 | 1 | 2 | 3 | 'x+' | 'x-' | 'y+' | 'y-'; + + interface IConfigQuadGrid extends Quad.IConfig { + gridType: 'quadGrid', + } + + interface IConfigHexagonGrid extends Hexagon.IConfig { + gridType: 'hexagonGrid', + } + + interface IConfig { + grid?: QuadGrid | HexagonGrid | IConfigQuadGrid | IConfigHexagonGrid, + width?: number, + height?: number, + + wrap?: boolean, + infinity?: boolean + } + + namespace Events { + type KickOutCallbackType = ( + chessToAdd: unknown, + occupiedChess: unknown, + tileXYZ: TileXYZType + ) => void; + } + +} + +declare class Board extends EE { + constructor( + scene: unknown, + config?: Board.IConfig + ); + + scene: unknown; + + setGrid(grid: QuadGrid | HexagonGrid | Board.IConfigQuadGrid | Board.IConfigHexagonGrid): this; + grid: QuadGrid | HexagonGrid; + + setBoardWidth(width: number): this; + readonly width: number; + setBoardHeight(height: number): this; + readonly height: number; + + setWrapMode(enable?: boolean): this; + wrapMode: boolean; + + setInfinityMode(enable?: boolean): this; + infinityMode: boolean; + + addChess( + chess: ChessType, + tileX: number, + tileY: number, + tileZ: number | string, + align?: boolean + ): this; + + removeChess( + chess: ChessType, + tileX?: null, + tileY?: null, + tileZ?: null, + destroy?: boolean + ): this; + removeChess( + chess: null, + tileX: number, + tileY: number, + tileZ: number | string, + destroy?: boolean + ): this; + + removeAllChess(destroy?: boolean): this; + + moveChess( + chess: ChessType, + toTileX: number, + toTileY: number, + toTileZ: number | string, + align?: boolean + ): this; + + setChessTileZ( + chess: ChessType, + toTileZ: number | string, + align?: boolean + ): this; + + swapChess( + chessA: ChessType, + chessB: ChessType, + align?: boolean + ): this; + + chessToTileXYZ( + chess: ChessType | TileXYType | number | undefined | null + ): TileXYZType | null; + + tileXYZToChess( + tileX: number, + tileY: number, + tileZ: number | string + ): ChessType | null; + + tileXYToChessArray( + tileX: number, + tileY: number, + out?: ChessType[] + ): ChessType[]; + + tileZToChessArray( + tileZ: number, + out?: ChessType[] + ): ChessType[]; + + tileXYArrayToChessArray( + tileXYArray: TileXYType[], + tileZ?: number | string, + out?: ChessType[] + ): ChessType[]; + tileXYArrayToChessArray( + tileXYArray: TileXYType[], + out?: ChessType[] + ): ChessType[]; + + worldXYToChessArray( + worldX: number, + worldY: number, + out?: ChessType[] + ): ChessType[]; + + worldXYToChess( + worldX: number, + worldY: number, + tileZ?: number | string + ): ChessType; + + contains( + tileX: number, + tileY: number, + tileZ?: number | string + ): boolean; + + exists( + chess: ChessType + ): boolean; + + forEachTileXY( + callback: (tileXY: TileXYType, board: Board) => void | boolean, + scope?: object, + order?: Board.ForEachTileXYOrderTypes + ): this; + + tileXYToWorldXY( + tileX: number, + tileY: number, + out?: WorldXYType | true + ): WorldXYType; + + worldXYToTileXY( + worldX: number, + worldY: number, + out?: TileXYType | true + ): TileXYType; + + worldXYSnapToGrid( + worldX: number, + worldY: number, + out?: WorldXYType | true + ): WorldXYType; + + getDistance( + tileA: ChessType | TileXYType, + tileB: ChessType | TileXYType + ): number; + + ringToTileXYArray( + centerTileXY: ChessType | TileXYType, + radius: number, + out?: TileXYType[] + ): TileXYType[]; + + filledRingToTileXYArray( + centerTileXY: ChessType | TileXYType, + radius: number, + nearToFar?: boolean, + out?: TileXYType[] + ): TileXYType[]; + filledRingToTileXYArray( + centerTileXY: ChessType | TileXYType, + radius: number, + out?: TileXYType[] + ): TileXYType[]; + + lineToTileXYArray( + line: Line, + out?: TileXYType[] + ): TileXYType[]; + lineToTileXYArray( + startWorldX: number, + startWorldY: number, + endWorldX: number, + endWorldY: number, + out?: TileXYType[] + ): TileXYType[]; + + circleToTileXYArray( + circle: Circle, + out?: TileXYType[] + ): TileXYType[]; + + circleToTileXYArray( + circle: Circle, + testMode?: number, + out?: TileXYType[] + ): TileXYType[]; + + rectangleToTileXYArray( + rectangle: Rectangle, + out?: TileXYType[] + ): TileXYType[]; + + rectangleToTileXYArray( + rectangle: Rectangle, + testMode?: number, + out?: TileXYType[] + ): TileXYType[]; + + ellipseToTileXYArray( + ellipse: Ellipse, + out?: TileXYType[] + ): TileXYType[]; + + ellipseToTileXYArray( + ellipse: Ellipse, + testMode?: number, + out?: TileXYType[] + ): TileXYType[]; + + triangleToTileXYArray( + triangle: Triangle, + out?: TileXYType[] + ): TileXYType[]; + + triangleToTileXYArray( + triangle: Triangle, + testMode?: number, + out?: TileXYType[] + ): TileXYType[]; + + polygonToTileXYArray( + polygon: Polygon, + out?: TileXYType[] + ): TileXYType[]; + + polygonToTileXYArray( + polygon: Polygon, + testMode?: number, + out?: TileXYType[] + ): TileXYType[]; + + angleBetween( + tileA: ChessType | TileXYType, + tileB: ChessType | TileXYType + ): number; + + isAngleInCone( + chessA: ChessType | TileXYType, + chessB: ChessType | TileXYType, + face: number, + cone: number + ): boolean; + + directionBetween( + chessA: ChessType | TileXYType, + chessB: ChessType | TileXYType, + round?: boolean + ): number; + + isDirectionInCone( + chessA: ChessType | TileXYType, + chessB: ChessType | TileXYType, + face: number, + cone: number + ): boolean; + + getOppositeDirection( + tileX: number | ChessType | TileXYType, + tileY: number, + direction?: number + ): number; + + angleSnapToDirection( + tileXY: ChessType | TileXYType | undefined, + angle: number + ): number; + + gridAlign(chess?: ChessType): this; + + isOverlappingPoint( + worldX: number, + worldY: number, + tileZ?: number | string + ): boolean; + + getNeighborTileXY( + srcTileXY: ChessType | TileXYType, + direction: number, + out?: TileXYType | true + ): TileXYDirectionType; + getNeighborTileXY( + srcTileXY: ChessType | TileXYType, + direction: number | number[] | string | null, + out?: TileXYType[] + ): TileXYDirectionType | TileXYDirectionType[]; + + getNeighborTileDirection( + srcTile: ChessType | TileXYType, + neighborTileXY: TileXYType + ): number | null; + + getNeighborTileXYAtAngle( + srcTileXY: ChessType | TileXYType, + angle: number, + out?: TileXYType | true + ): TileXYType; + + getNeighborChess( + tileXYZ: ChessType | TileXYType, + direction: number | number[] | string | null, + neighborTileZ?: number + ): ChessType | ChessType[]; + + areNeighbors( + tileA: ChessType | TileXYType, + tileB: ChessType | TileXYType + ): boolean; + + mapNeighbors( + tileXYZ: ChessType | TileXYType, + callback: ( + tileXY: TileXYDirectionType, + index: number, + tileXYArray: TileXYDirectionType[] + ) => any, + scope?: object + ): any[]; + + mapNeighbors( + tileXYZ: ChessType | TileXYType, + distance: number, + callback: ( + tileXY: TileXYDirectionType, + index: number, + tileXYArray: TileXYDirectionType[] + ) => any, + scope?: object + ): any[]; + + getTileXYAtDirection( + srcTileXY: ChessType | TileXYType, + direction: number | number[] | string | null, + distance: number | number[] | { start?: number, end?: number, step?: number }, + out?: TileXYType[] + ): TileXYDirectionType | TileXYDirectionType[]; + + isEmptyTileXYZ( + tileX: number, + tileY?: number, + tileZ?: number | string + ): boolean; + + getRandomEmptyTileXY( + tileZ: number | string, + out?: TileXYType | true + ): TileXYType; + + getEmptyTileXYArray( + tileZ: number | string, + out?: TileXYType[] + ): TileXYType[]; + + getRandomEmptyTileXYInRange( + centerTileXY: ChessType | TileXYType, + radius: number, + tileZ: number | string, + out?: TileXYType | true + ): TileXYType; + + getEmptyTileXYArrayInRange( + centerTileXY: ChessType | TileXYType, + radius: number, + tileZ: number | string, + out?: TileXYType[] + ): TileXYType[]; + + getAllChess(): ChessType[]; + + fit(tileXYArray: TileXYType[]): this; + + hasBlocker( + tileX: number | ChessType | TileXYType, + tileY?: number, + tileZ?: number | string + ): boolean; + + getGridPoints( + tileX: number, + tileY?: number, + out?: WorldXYType[] | true + ): WorldXYType[]; + getGridPoints( + tileXY: ChessType | TileXYType, + out?: WorldXYType[] | true + ): WorldXYType[]; + + chessToBoard(chess: any): Board; + static GetBoard(chess: any): Board; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/LogicBoard.js b/ui/src/phaser3-rex-plugins/plugins/board/board/LogicBoard.js new file mode 100644 index 000000000..611be6f76 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/LogicBoard.js @@ -0,0 +1,130 @@ +import EE from '../../utils/eventemitter/EventEmitter.js'; +import LogicMethods from './LogicMethods.js'; +import BoardData from './boarddata/BoardData.js'; +import DefaultGrids from '../grid/index.js'; +import GetValue from '../../utils/object/GetValue.js'; +import IsPlainObject from '../../utils/object/IsPlainObject.js'; +import GetBoard from './chess/GetBoard.js'; + +class Board extends EE { + constructor(scene, config) { + // scene: scene instance, or undefined + super(); + + this.isShutdown = false; + this.scene = scene; + this.boardData = new BoardData(); + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.isBoard = GetValue(o, 'isBoard', true); // false: in Miniboard + this.setGrid(GetValue(o, 'grid', undefined)); + this.setWrapMode(GetValue(o, 'wrap', false)); + this.setInfinityMode(GetValue(o, 'infinity', false)); + this.setBoardWidth(GetValue(o, 'width', 0)); + this.setBoardHeight(GetValue(o, 'height', 0)); + return this; + } + + boot() { + if (this.scene && this.isBoard) { + this.scene.sys.events.once('shutdown', this.destroy, this); + } + } + + shutdown(fromScene) { + if (this.isShutdown) { + return; + } + + if (this.scene && this.isBoard) { + this.scene.sys.events.off('shutdown', this.destroy, this); + } + + if (this.isBoard) { + this.removeAllChess(!fromScene, true); + } else { + + } + + super.shutdown(); + this.boardData.shutdown(fromScene); + + this.scene = undefined; + this.boardData = undefined; + this.isShutdown = true; + + return this; + } + + destroy(fromScene) { + if (this.isShutdown) { + return; + } + this.emit('destroy', this, fromScene); + this.shutdown(fromScene); + } + + setGrid(grid) { + if (IsPlainObject(grid)) { + var config = grid; + var gridType = GetValue(config, 'gridType', 'quadGrid'); + var grid = new DefaultGrids[gridType](config); + } + this.grid = grid; + return this; + } + + setWrapMode(enable) { + if (enable === undefined) { + enable = true; + } + this.wrapMode = enable; + return this; + } + + setInfinityMode(enable) { + if (enable === undefined) { + enable = true; + } + this.infinityMode = enable; + return this; + } + + setBoardSize(width, height) { + this.setBoardWidth(width); + this.setBoardHeight(height); + return this; + } + + exists(gameObject) { + // game object or uid + return this.boardData.exists(this.getChessUID(gameObject)); + } + + get chessCount() { + return this.boardData.chessCount; + } + + clear(destroy) { + if (destroy === undefined) { + destroy = true; + } + this.removeAllChess(destroy, true); + this.boardData.clear(); + return this; + } + + static GetBoard(chess) { + return GetBoard(chess); + } +} + +Object.assign( + Board.prototype, + LogicMethods +); + +export default Board; diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/LogicMethods.js b/ui/src/phaser3-rex-plugins/plugins/board/board/LogicMethods.js new file mode 100644 index 000000000..179065fe5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/LogicMethods.js @@ -0,0 +1,183 @@ +import GetChessData from '../chess/GetChessData.js'; +import GetChessUID from '../chess/GetChessUID.js'; + +import SetBoardWidth from './boarddata/SetBoardWidth.js'; +import SetBoardHeight from './boarddata/SetBoardHeight.js'; + +import TileXYZToKey from '../utils/tilexyzkey/TileXYZToKey.js'; +import TileXYToKey from '../utils/tilexyzkey/TileXYToKey.js'; +import KeyToTileXYZ from '../utils/tilexyzkey/KeyToTileXYZ.js'; + +import TileXYToWorldX from './worldposition/TileXYToWorldX.js'; +import TileXYToWorldY from './worldposition/TileXYToWorldY.js'; +import TileXYToWorldXY from './worldposition/TileXYToWorldXY.js'; +import TileXYArrayToWorldXYArray from './worldposition/TileXYArrayToWorldXYArray.js'; +import WorldXYToTileX from './worldposition/WorldXYToTileX.js'; +import WorldXYToTileY from './worldposition/WorldXYToTileY.js'; +import WorldXYToTileXY from './worldposition/WorldXYToTileXY.js'; +import WorldXYToChessArray from './worldposition/WorldXYToChessArray.js'; +import WorldXYToChess from './worldposition/WorldXYToChess.js'; +import WorldXYSnapToGrid from './worldposition/WorldXYSnapToGrid.js'; +import AngleBetween from './worldposition/AngleBetween.js'; +import IsAngleInCone from './worldposition/IsAngleInCone.js'; +import AngleToward from './worldposition/AngleToward.js'; +import AngleSnapToDirection from './worldposition/AngleSnapToDirection.js'; +import IsOverlappingPoint from './worldposition/IsOverlappingPoint.js'; +import GridAlign from './worldposition/GridAlign.js'; +import GetGridPoints from './worldposition/GetGridPoints.js'; +import GetGridBounds from './worldposition/GetGridBounds.js'; +import GetBoardBounds from './worldposition/GetBoardBounds.js'; + +import LineToTileXYArray from './shape/LineToTileXYArray.js'; +import CircleToTileXYArray from './shape/CircleToTileXYArray.js'; +import EllipseToTileXYArray from './shape/EllipseToTileXYArray.js'; +import PolygonToTileXYArray from './shape/PolygonToTileXYArray.js'; +import RectangleToTileXYArray from './shape/RectangleToTileXYArray.js'; +import TriangleToTileXYArray from './shape/TriangleToTileXYArray.js'; +import ShapeToTileXYArray from './shape/ShapeToTileXYArray.js'; +import ForEachTileXYInShape from './shape/ForEachTileXYInShape.js'; + +import UidToChess from './chess/UidToChess.js'; +import AddChess from './chess/AddChess.js'; +import SetChessTileZ from './chess/SetChessTileZ.js'; +import RemoveChess from './chess/RemoveChess.js'; +import RemoveAllChess from './chess/RemoveAllChess.js'; +import SwapChess from './chess/SwapChess.js'; +import GetAllChess from './chess/GetAllChess.js'; + +import Contains from './tileposition/Contains.js'; +import ForEachTileXY from './tileposition/ForEachTileXY.js'; +import GetWrapTileXY from './tileposition/GetWrapTileXY.js'; +import TileXYZToChess from './tileposition/TileXYZToChess.js'; +import TileXYToChessArray from './tileposition/TileXYToChessArray.js'; +import TileZToChessArray from './tileposition/TileZToChessArray.js'; +import TileXYArrayToChessArray from './tileposition/TileXYArrayToChessArray.js'; +import ChessToTileXYZ from './tileposition/ChessToTileXYZ.js'; +import GetOppositeDirection from './tileposition/GetOppositeDirection.js'; +import GetDistance from './tileposition/GetDistance.js'; +import DirectionBetween from './tileposition/DirectionBetween.js'; +import IsDirectionInCone from './tileposition/IsDirectionInCone.js'; + +import Offset from './transform/Offset.js'; +import Mirror from './transform/Mirror.js'; +import Rotate from './transform/Rotate.js'; +import Fit from './transform/Fit.js'; + +import IsEmptyTileXYZ from './empty/IsEmptyTileXYZ.js'; +import GetEmptyTileXYArray from './empty/GetEmptyTileXYArray.js'; +import GetRandomEmptyTileXY from './empty/GetRandomEmptyTileXY.js'; +import GetEmptyTileXYArrayInRange from './empty/GetEmptyTileXYArrayInRange.js'; +import GetRandomEmptyTileXYInRange from './empty/GetRandomEmptyTileXYInRange.js'; + +import GetTileXYAtDirection from './neighbors/GetTileXYAtDirection.js'; +import GetNeighborTileXY from './neighbors/GetNeighborTileXY.js'; +import GetNeighborTileXYAtAngle from './neighbors/GetNeighborTileXYAtAngle.js'; +import GetNeighborChess from './neighbors/GetNeighborChess.js'; +import GetNeighborTileDirection from './neighbors/GetNeighborTileDirection.js'; +import GetNeighborChessDirection from './neighbors/GetNeighborChessDirection.js'; +import AreNeighbors from './neighbors/AreNeighbors.js'; +import MapNeighbors from './neighbors/MapNeighobrs.js'; + +import RingToTileXYArray from './ring/RingToTileXYArray.js'; +import RingToChessArray from './ring/RingToChessArray.js'; +import FilledRingToTileXYArray from './ring/FilledRingToTileXYArray.js'; +import FilledRingToChessArray from './ring/FilledRingToChessArray.js'; + +import HasBlocker from './blocker/HasBlocker.js'; +import HasEdgeBlocker from './blocker/HasEdgeBlocker.js'; + +import GetBoard from './chess/GetBoard.js'; + +export default { + getChessData: GetChessData, + getChessUID: GetChessUID, + + setBoardWidth: SetBoardWidth, + setBoardHeight: SetBoardHeight, + + tileXYZToKey: TileXYZToKey, + tileXYToKey: TileXYToKey, + keyToTileXYZ: KeyToTileXYZ, + + tileXYToWorldX: TileXYToWorldX, + tileXYToWorldY: TileXYToWorldY, + tileXYToWorldXY: TileXYToWorldXY, + tileXYArrayToWorldXYArray: TileXYArrayToWorldXYArray, + worldXYToTileX: WorldXYToTileX, + worldXYToTileY: WorldXYToTileY, + worldXYToTileXY: WorldXYToTileXY, + worldXYToChessArray: WorldXYToChessArray, + worldXYToChess: WorldXYToChess, + worldXYSnapToGrid: WorldXYSnapToGrid, + angleBetween: AngleBetween, + isAngleInCone: IsAngleInCone, + angleToward: AngleToward, + angleSnapToDirection: AngleSnapToDirection, + isOverlappingPoint: IsOverlappingPoint, + gridAlign: GridAlign, + getGridPoints: GetGridPoints, + getGridBounds: GetGridBounds, + getBoardBounds: GetBoardBounds, + + lineToTileXYArray: LineToTileXYArray, + circleToTileXYArray: CircleToTileXYArray, + ellipseToTileXYArray: EllipseToTileXYArray, + polygonToTileXYArray: PolygonToTileXYArray, + rectangleToTileXYArray: RectangleToTileXYArray, + triangleToTileXYArray: TriangleToTileXYArray, + shapeToTileXYArray: ShapeToTileXYArray, + forEachTileXYInShape: ForEachTileXYInShape, + + uidToChess: UidToChess, + addChess: AddChess, + removeChess: RemoveChess, + removeAllChess: RemoveAllChess, + swapChess: SwapChess, + moveChess: AddChess, + setChessTileZ: SetChessTileZ, + getAllChess: GetAllChess, + + contains: Contains, + forEachTileXY: ForEachTileXY, + getWrapTileXY: GetWrapTileXY, + tileXYZToChess: TileXYZToChess, + tileXYToChessArray: TileXYToChessArray, + tileZToChessArray: TileZToChessArray, + tileXYArrayToChessArray: TileXYArrayToChessArray, + chessToTileXYZ: ChessToTileXYZ, + offset: Offset, + mirror: Mirror, + rotate: Rotate, + getOppositeDirection: GetOppositeDirection, + getDistance: GetDistance, + directionBetween: DirectionBetween, + isDirectionInCone: IsDirectionInCone, + fit: Fit, + + isEmptyTileXYZ: IsEmptyTileXYZ, + getEmptyTileXYArray: GetEmptyTileXYArray, + getRandomEmptyTileXY: GetRandomEmptyTileXY, + getEmptyTileXYArrayInRange: GetEmptyTileXYArrayInRange, + getRandomEmptyTileXYInRange: GetRandomEmptyTileXYInRange, + + getTileXYAtDirection: GetTileXYAtDirection, + getNeighborTileXY: GetNeighborTileXY, + getNeighborTileXYAtAngle: GetNeighborTileXYAtAngle, + getNeighborChess: GetNeighborChess, + getNeighborTileDirection: GetNeighborTileDirection, + getNeighborChessDirection: GetNeighborChessDirection, + areNeighbors: AreNeighbors, + mapNeighbors: MapNeighbors, + + ringToTileXYArray: RingToTileXYArray, + ringToChessArray: RingToChessArray, + filledRingToTileXYArray: FilledRingToTileXYArray, + filledRingToChessArray: FilledRingToChessArray, + + hasBlocker: HasBlocker, + hasEdgeBlocker: HasEdgeBlocker, + + getGridPoints: GetGridPoints, + + chessToBoard: GetBoard, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/blocker/HasBlocker.js b/ui/src/phaser3-rex-plugins/plugins/board/board/blocker/HasBlocker.js new file mode 100644 index 000000000..3f8e2c36d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/blocker/HasBlocker.js @@ -0,0 +1,36 @@ +var HasBlocker = function (tileX, tileY, tileZ) { + if (tileX && (typeof (tileX) !== 'number')) { + var tileXYZ = this.chessToTileXYZ(tileX); // tileX is a Chess or TileXY + tileX = tileXYZ.x; + tileY = tileXYZ.y; + tileZ = tileXYZ.z; + } + + var chess, blocker; + if (tileZ === undefined) { + // any chess at (tileX, tileY) has blocker + chess = this.tileXYToChessArray(tileX, tileY, globChessArray); + for (var i = 0, cnt = chess.length; i < cnt; i++) { + blocker = this.getChessData(chess[i]).blocker; + if (blocker === true) { + globChessArray.length = 0; + return true; + } + } + globChessArray.length = 0; + return false; + + } else { + // chess at (tileX, tileY, tileZ) has blocker + var chess = this.tileXYZToChess(tileX, tileY, tileZ); + if (chess === null) { + return false; + } + blocker = this.getChessData(chess).blocker; + return (blocker === true); + + } +} +var globChessArray = []; + +export default HasBlocker; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/blocker/HasEdgeBlocker.js b/ui/src/phaser3-rex-plugins/plugins/board/board/blocker/HasEdgeBlocker.js new file mode 100644 index 000000000..f1f835c81 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/blocker/HasEdgeBlocker.js @@ -0,0 +1,35 @@ +var HasEdgeBlocker = function (tileX, tileY, tileZ, direction) { + var chess, blocker; + if (tileZ === undefined) { + // any chess at (tileX, tileY) has blocker + chess = this.tileXYToChessArray(tileX, tileY, globChessArray); + for (var i = 0, cnt = chess.length; i < cnt; i++) { + if (isEdgeBlocker(this.getChessData(chess[i]).blocker)) { + globChessArray.length = 0; + return true; + } + } + globChessArray.length = 0; + return false; + + } else { + // chess at (tileX, tileY, tileZ) has blocker + var chess = this.tileXYZToChess(tileX, tileY, tileZ); + if (chess === null) { + return false; + } + return isEdgeBlocker(this.getChessData(chess).blocker); + } +} + +var isEdgeBlocker = function (blocker, direction) { + if ((blocker === false) || (blocker === true)) { + return blocker; + } else { + return (blocker[direction] === true); + } +} + +var globChessArray = []; + +export default HasEdgeBlocker; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/boarddata/BoardData.js b/ui/src/phaser3-rex-plugins/plugins/board/board/boarddata/BoardData.js new file mode 100644 index 000000000..35e14618d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/boarddata/BoardData.js @@ -0,0 +1,182 @@ +import Clear from '../../../utils/object/Clear.js'; +import IsEmpty from '../../../utils/object/IsEmpty.js'; + +class BoardData { + constructor() { + this.XYZToUID = {}; // [x][y][z] : uid + this.UIDToXYZ = {}; // uid : xyz + this.clear(); + } + + shutdown(fromScene) { + this.XYZToUID = undefined; + this.UIDToXYZ = undefined; + return this; + } + + destroy(fromScene) { + this.shutdown(fromScene); + return this; + } + + clear() { + Clear(this.UIDToXYZ); + Clear(this.XYZToUID); + this.chessCount = 0; + this.clearBounds(); + return this; + } + + clearBounds() { + this._xMax = undefined; + this._xMin = undefined; + this._yMax = undefined; + this._yMin = undefined; + return this; + } + + addUID(uid, x, y, z) { + if (!this.XYZToUID.hasOwnProperty(x)) { + this.XYZToUID[x] = {}; + } + var tmpx = this.XYZToUID[x]; + if (!tmpx.hasOwnProperty(y)) { + tmpx[y] = {}; + } + var tmpy = tmpx[y]; + tmpy[z] = uid; + this.UIDToXYZ[uid] = { + x: x, + y: y, + z: z + }; + + this.chessCount++; + this.clearBounds(); + return this; + } + + getUID(x, y, z) { + // (x,y,z) -> uid + // (x,y) -> zHash = {z:uid} + var tmp = this.XYZToUID[x]; + if (tmp) { + tmp = tmp[y]; + if (tmp) { + if (z !== undefined) { + tmp = tmp[z]; + } + } + } + return tmp; + } + + removeUID(x, y, z) { + if (!this.XYZToUID.hasOwnProperty(x)) { + return this; + } + var tmpx = this.XYZToUID[x]; + if (!tmpx.hasOwnProperty(y)) { + return this; + } + var tmpy = tmpx[y]; + if (!tmpy.hasOwnProperty(z)) { + return this; + } + + var uid = tmpy[z]; + delete tmpy[z]; + delete this.UIDToXYZ[uid]; + if (IsEmpty(tmpy)) { + delete tmpx[y]; + } + if (IsEmpty(tmpx)) { + delete this.XYZToUID[x]; + } + + this.chessCount--; + this.clearBounds(); + return this; + } + + exists(uid) { + return this.UIDToXYZ.hasOwnProperty(uid); + } + + contains(x, y, z) { + return (this.getUID(x, y, z) != null); + } + + getXYZ(uid) { + if (this.exists(uid)) { + return this.UIDToXYZ[uid]; + } + return null; + } + + get xMax() { + if (this._xMax === undefined) { + this._xMax = -Infinity; + var UIDToXYZ = this.UIDToXYZ, + x; + for (var uid in UIDToXYZ) { + x = UIDToXYZ[uid].x; + if (this._xMax < x) { + this._xMax = x; + } + } + } + + return this._xMax; + } + + get xMin() { + if (this._xMin === undefined) { + this._xMin = Infinity; + var UIDToXYZ = this.UIDToXYZ, + x; + for (var uid in UIDToXYZ) { + x = UIDToXYZ[uid].x; + if (this._xMin > x) { + this._xMin = x; + } + } + } + + return this._xMin; + } + + get yMax() { + if (this._yMax === undefined) { + this._yMax = -Infinity; + var UIDToXYZ = this.UIDToXYZ, + y; + for (var uid in UIDToXYZ) { + y = UIDToXYZ[uid].y; + if (this._yMax < y) { + this._yMax = y; + } + } + } + + return this._yMax; + } + + get yMin() { + if (this._yMin === undefined) { + this._yMin = Infinity; + var UIDToXYZ = this.UIDToXYZ, + y; + for (var uid in UIDToXYZ) { + y = UIDToXYZ[uid].y; + if (this._yMin > y) { + this._yMin = y; + } + } + } + + return this._yMin; + } +} + +export default BoardData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/boarddata/SetBoardHeight.js b/ui/src/phaser3-rex-plugins/plugins/board/board/boarddata/SetBoardHeight.js new file mode 100644 index 000000000..f109d6786 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/boarddata/SetBoardHeight.js @@ -0,0 +1,25 @@ +var SetBoardHeight = function (height) { + if (this.infinityMode) { + return this; + } + if ((this.height === undefined) || (this.height <= height)) { + this.height = height; + return this; + } + + // this.height > height : collapse + var tileX, tileY, tileZ, tileZToUIDs; + for (tileY = height; tileY < this.height; tileY++) { + for (tileX = 0; tileX < this.width; tileX++) { + tileZToUIDs = this.boardData.getUID(tileX, tileY); + for (tileZ in tileZToUIDs) { + this.RemoveChess(false, tileX, tileY, tileZ); + } + } + } + + this.height = height; + return this; +} + +export default SetBoardHeight; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/boarddata/SetBoardWidth.js b/ui/src/phaser3-rex-plugins/plugins/board/board/boarddata/SetBoardWidth.js new file mode 100644 index 000000000..249eefeb3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/boarddata/SetBoardWidth.js @@ -0,0 +1,25 @@ +var SetBoardWidth = function (width) { + if (this.infinityMode) { + return this; + } + if ((this.width === undefined) || (this.width <= width)) { + this.width = width; + return this; + } + + // this.width > width : collapse + var tileX, tileY, tileZ, tileZToUIDs; + for (tileX = width; tileX < this.width; tileX++) { + for (tileY = 0; tileY < this.height; tileY++) { + tileZToUIDs = this.boardData.getUID(tileX, tileY); + for (tileZ in tileZToUIDs) { + this.RemoveChess(false, tileX, tileY, tileZ); + } + } + } + + this.width = width; + return this; +} + +export default SetBoardWidth; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/camera/ForEachCullTileXY.js b/ui/src/phaser3-rex-plugins/plugins/board/board/camera/ForEachCullTileXY.js new file mode 100644 index 000000000..a3e66e0b7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/camera/ForEachCullTileXY.js @@ -0,0 +1,44 @@ +import GetValue from '../../../utils/object/GetValue.js'; +import Rectangle from '../../../utils/geom/rectangle/Rectangle.js'; + +var ForEachCullTileXY = function (callback, scope, config) { + if (typeof (config) === 'number') { + config = { + order: config + } + } + + if (config === undefined) { + config = {}; + } + + var order = GetValue(config, 'order', 0); + + var camera = GetValue(config, 'camera', this.scene.cameras.main); + var paddingX = GetValue(config, 'paddingX', 1); + var paddingY = GetValue(config, 'paddingY', 1); + + if (ViewportBounds === undefined) { + ViewportBounds = new Rectangle(); + } + ViewportBounds.width = (camera.width + paddingX * 2) / camera.zoomX; + ViewportBounds.height = (camera.height + paddingY * 2) / camera.zoomY; + ViewportBounds.centerX = camera.centerX + camera.scrollX; + ViewportBounds.centerY = camera.centerY + camera.scrollY; + + this.forEachTileXYInShape( + ViewportBounds, + callback, + scope, + { + order: order, + testMode: 1 + } + ); + + return this; +} + +var ViewportBounds; + +export default ForEachCullTileXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/chess/AddChess.js b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/AddChess.js new file mode 100644 index 000000000..d77330e2f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/AddChess.js @@ -0,0 +1,44 @@ +var AddChess = function (gameObject, tileX, tileY, tileZ, align) { + if (!this.contains(tileX, tileY)) { + return this; + } + if (align === undefined) { + align = true; + } + + var curTileXYZ = this.chessToTileXYZ(gameObject); + if (tileZ === undefined) { + if (curTileXYZ) { + tileZ = curTileXYZ.z; + } else { + tileZ = 0; + } + } + if (curTileXYZ && + (curTileXYZ.x === tileX) && (curTileXYZ.y === tileY) && (curTileXYZ.z === tileZ)) { + // Move to current position + return this; + } + var occupiedChess = this.tileXYZToChess(tileX, tileY, tileZ); + if (occupiedChess) { + this.emit('kickout', gameObject, occupiedChess, curTileXYZ); + } + + this.removeChess(gameObject); + if (occupiedChess) { + this.removeChess(occupiedChess, tileX, tileY, tileZ); + } + this.boardData.addUID(this.getChessUID(gameObject), tileX, tileY, tileZ); + + if (this.isBoard) { + this.getChessData(gameObject).setBoard(this); + } + + if (align) { + this.gridAlign(gameObject, tileX, tileY); + } + + return this; +}; + +export default AddChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/chess/GetAllChess.js b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/GetAllChess.js new file mode 100644 index 000000000..de3d24aae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/GetAllChess.js @@ -0,0 +1,11 @@ +var GetAllChess = function (out) { + if (out === undefined) { + out = []; + } + var uids = this.boardData.UIDToXYZ; + for (var uid in uids) { + out.push(this.uidToChess(uid)); + } + return out; +}; +export default GetAllChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/chess/GetBoard.js b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/GetBoard.js new file mode 100644 index 000000000..4060124e4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/GetBoard.js @@ -0,0 +1,13 @@ +var GetBoard = function (chess) { + if (!chess) { + return undefined; + } else if (chess.rexChess) { // Chess game object + return chess.rexChess.board; + } else if (chess.mainBoard) { // Mini-board + return chess.mainBoard; + } else { + return undefined; + } +} + +export default GetBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/chess/RemoveAllChess.js b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/RemoveAllChess.js new file mode 100644 index 000000000..2bffd182a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/RemoveAllChess.js @@ -0,0 +1,8 @@ +var RemoveAllChess = function (destroy, fromBoardRemove) { + var chess = this.getAllChess(); + for (var i = 0, cnt = chess.length; i < cnt; i++) { + this.removeChess(chess[i], undefined, undefined, undefined, destroy, fromBoardRemove); + } + return this; +} +export default RemoveAllChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/chess/RemoveChess.js b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/RemoveChess.js new file mode 100644 index 000000000..dde54567d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/RemoveChess.js @@ -0,0 +1,40 @@ +var RemoveChess = function (gameObject, tileX, tileY, tileZ, destroy, fromBoardRemove) { + if (destroy === undefined) { + destroy = false; + } + if (fromBoardRemove === undefined) { + fromBoardRemove = false; + } + if (gameObject) { + var tileXYZ = this.chessToTileXYZ(gameObject); + if (tileXYZ) { + tileX = tileXYZ.x; + tileY = tileXYZ.y; + tileZ = tileXYZ.z; + } else { + // chess is not in this board + return this; + } + } else { + gameObject = this.tileXYZToChess(tileX, tileY, tileZ); + if (!gameObject) { + // chess is not in this board + return this; + } + } + + if (!fromBoardRemove) { + this.boardData.removeUID(tileX, tileY, tileZ); + } + if (this.isBoard) { + this.getChessData(gameObject).setBoard(null); + } + + if (destroy && gameObject.destroy) { + gameObject.destroy(); + } + + return this; +} + +export default RemoveChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/chess/SetChessTileZ.js b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/SetChessTileZ.js new file mode 100644 index 000000000..e30d2f71e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/SetChessTileZ.js @@ -0,0 +1,12 @@ +var SetChessTileZ = function (chess, tileZ, align) { + if (align === undefined) { + align = false; + } + var tileXYZ = this.chessToTileXYZ(chess); + if (tileXYZ) { + this.moveChess(chess, tileXYZ.x, tileXYZ.y, tileZ, align); + } + return this; +} + +export default SetChessTileZ; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/chess/SwapChess.js b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/SwapChess.js new file mode 100644 index 000000000..4f5c4e6ae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/SwapChess.js @@ -0,0 +1,18 @@ +var SwapChess = function (gameObjectA, gameObjectB, align) { + if (align === undefined) { + align = true; + } + + var tileXYZA = this.chessToTileXYZ(gameObjectA); + var tileXYZB = this.chessToTileXYZ(gameObjectB); + if ((tileXYZA == null) || (tileXYZB == null)) { + return this; + } + this.removeChess(gameObjectA); + this.removeChess(gameObjectB); + this.addChess(gameObjectA, tileXYZB.x, tileXYZB.y, tileXYZB.z, align); + this.addChess(gameObjectB, tileXYZA.x, tileXYZA.y, tileXYZA.z, align); + return this; +}; + +export default SwapChess; diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/chess/UidToChess.js b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/UidToChess.js new file mode 100644 index 000000000..eebcbf96b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/chess/UidToChess.js @@ -0,0 +1,13 @@ +import ChessBank from '../../chess/ChessBank.js'; + +var UidToChess = function (uid) { + if (uid == null) { + return null; + } else { + if (!this.boardData.exists(uid)) { + return null; + } + return ChessBank.get(uid).parent; + } +} +export default UidToChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetEmptyTileXYArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetEmptyTileXYArray.js new file mode 100644 index 000000000..b795cc3f1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetEmptyTileXYArray.js @@ -0,0 +1,21 @@ +var GetEmptyTileXYArray = function (tileZ, out) { + if (tileZ === undefined) { + tileZ = 0; + } + if (out === undefined) { + out = []; + } + + for (var tileY = 0; tileY < this.height; tileY++) { + for (var tileX = 0; tileX < this.width; tileX++) { + if (this.isEmptyTileXYZ(tileX, tileY, tileZ)) { + out.push({ + x: tileX, + y: tileY + }); + } + } + } + return out; +} +export default GetEmptyTileXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetEmptyTileXYArrayInRange.js b/ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetEmptyTileXYArrayInRange.js new file mode 100644 index 000000000..ce28151f1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetEmptyTileXYArrayInRange.js @@ -0,0 +1,26 @@ +var GetEmptyTileXYArrayInRange = function (centerTileXY, radius, tileZ, out) { + if (radius === undefined) { + radius = 1; + } + if (tileZ === undefined) { + tileZ = 0; + } + if (out === undefined) { + out = []; + } + + centerTileXY = this.chessToTileXYZ(centerTileXY); + this.grid.ringToTileXYArray(centerTileXY, radius, globTileXYArray); + var tileXY; + for (var i = 0, cnt = globTileXYArray.length; i < cnt; i++) { + tileXY = globTileXYArray[i]; + if (this.isEmptyTileXYZ(tileXY.x, tileXY.y, tileZ)) { + out.push(tileXY); + } + } + globTileXYArray.length = 0; + return out; +} + +var globTileXYArray = []; +export default GetEmptyTileXYArrayInRange; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetRandomEmptyTileXY.js b/ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetRandomEmptyTileXY.js new file mode 100644 index 000000000..d94f41ba0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetRandomEmptyTileXY.js @@ -0,0 +1,44 @@ +import Random from '../../../utils/math/Between.js'; +import GetRandomItem from '../../../utils/array/GetRandom.js'; + +var GetRandomEmptyTileXY = function (tileZ, out) { + if (tileZ === undefined) { + tileZ = 0; + } + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + var tileX, tileY; + var isOccupied = true; + var tryCount = 20; + while (isOccupied && (tryCount > 0)) { + tileX = Random(0, this.width - 1); + tileY = Random(0, this.height - 1); + isOccupied = (this.tileXYZToChess(tileX, tileY, tileZ) !== null); + tryCount--; + } + + if (!isOccupied) { + out.x = tileX; + out.y = tileY; + return out; + } else { + globTileXYArray = this.getEmptyTileXYArray(tileZ, globTileXYArray); + if (globTileXYArray.length === 0) { + return null; + } else { + var tileXY = GetRandomItem(globTileXYArray); + out.x = tileXY.x; + out.y = tileXY.y; + globTileXYArray.length = 0; + return out; + } + } +} + +var globTileXYArray = []; +var globTileXY = {}; +export default GetRandomEmptyTileXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetRandomEmptyTileXYInRange.js b/ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetRandomEmptyTileXYInRange.js new file mode 100644 index 000000000..7416635d2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/empty/GetRandomEmptyTileXYInRange.js @@ -0,0 +1,37 @@ +import Shuffle from '../../../utils/array/Shuffle.js'; + +var GetRandomEmptyTileXYInRange = function (centerTileXY, radius, tileZ, out) { + if (radius === undefined) { + radius = 1; + } + if (tileZ === undefined) { + tileZ = 0; + } + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + centerTileXY = this.chessToTileXYZ(centerTileXY); + this.grid.ringToTileXYArray(centerTileXY, radius, globTileXYArray); + Shuffle(globTileXYArray); + + var tileXY; + for (var i = 0, cnt = globTileXYArray.length; i < cnt; i++) { + tileXY = globTileXYArray[i]; + if (this.isEmptyTileXYZ(tileXY.x, tileXY.y, tileZ)) { + out.x = tileXY.x; + out.y = tileXY.y; + globTileXYArray.length = 0; + return out; + } + } + + globTileXYArray.length = 0; + return null; +} + +var globTileXYArray = []; +var globTileXY = {}; +export default GetRandomEmptyTileXYInRange; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/empty/IsEmptyTileXYZ.js b/ui/src/phaser3-rex-plugins/plugins/board/board/empty/IsEmptyTileXYZ.js new file mode 100644 index 000000000..199146613 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/empty/IsEmptyTileXYZ.js @@ -0,0 +1,6 @@ +var IsEmptyTileXYZ = function (tileX, tileY, tileZ) { + // TileXY is inside board, and TileXYZ has no chess + return this.contains(tileX, tileY) && !this.contains(tileX, tileY, tileZ); +} + +export default IsEmptyTileXYZ; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/input/EmitChessEvent.js b/ui/src/phaser3-rex-plugins/plugins/board/board/input/EmitChessEvent.js new file mode 100644 index 000000000..aa533454f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/input/EmitChessEvent.js @@ -0,0 +1,39 @@ +import InputCandidate from './../../../utils/input/InputCandidate.js'; + +var EmitChessEvent = function (boardEventName, chessEventName, board, tileX, tileY, pointer) { + if ((tileX == null) || (tileY == null)) { + return; + } + + var boardEventCallback = (typeof (boardEventName) !== 'string') ? boardEventName : undefined; + var chessEventCallback = (typeof (chessEventName) !== 'string') ? chessEventName : undefined; + + var gameObjects = board.tileXYToChessArray(tileX, tileY, globChessArray); + // Fire events + var gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + gameObject = gameObjects[i]; + if (!InputCandidate(gameObject)) { + continue; + } + + if (gameObject.emit) { + if (!chessEventCallback) { + gameObject.emit(chessEventName, pointer); + } else { + chessEventCallback(gameObject); + } + } + + if (!boardEventCallback) { + board.emit(boardEventName, pointer, gameObject); + } else { + boardEventCallback(gameObject); + } + } + globChessArray.length = 0; +} + +var globChessArray = []; + +export default EmitChessEvent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/input/Input.js b/ui/src/phaser3-rex-plugins/plugins/board/board/input/Input.js new file mode 100644 index 000000000..55e24b548 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/input/Input.js @@ -0,0 +1,112 @@ +import GetValue from '../../../utils/object/GetValue.js'; +import TouchZone from './TouchZone.js'; + +import OnPointerDown from './OnPointerDown.js'; +import OnPointerUp from './OnPointerUp.js'; +import OnPointerMove from './OnPointerMove.js'; + +import InstallTap from './InstallTap.js'; +import InstallPress from './InstallPress.js'; +import InstallSwipe from './InstallSwipe.js'; + +class Input { + constructor(board, config) { + var enable = GetValue(config, 'enable', true); + var useTouchZone = GetValue(config, 'useTouchZone', true); + + var scene = board.scene; + + this.board = board; + this.touchZone = undefined; + this._enable = true; + this.pointer = null; + this.tilePosition = { x: undefined, y: undefined }; + this.prevTilePosition = { x: undefined, y: undefined }; + + if (useTouchZone) { + var touchZone = new TouchZone(scene); + touchZone.on('pointerdown', OnPointerDown, this); + touchZone.on('pointerup', OnPointerUp, this); + touchZone.on('pointermove', OnPointerMove, this); + this.touchZone = touchZone; + + } else { + scene.input.on('pointerdown', OnPointerDown, this); + scene.input.on('pointerup', OnPointerUp, this); + scene.input.on('pointermove', OnPointerMove, this); + + } + + this.tap = InstallTap.call(this); + this.press = InstallPress.call(this); + this.swipe = InstallSwipe.call(this); + + board.once('destroy', this.onBoardDestroy, this); + + this.setEnable(enable); + } + + destroy(fromScene) { + this.tap.destroy(fromScene); + this.press.destroy(fromScene); + this.swipe.destroy(fromScene); + + if (this.touchZone) { + this.touchZone.destroy(fromScene); + this.touchZone = undefined; + + } else { + var scene = this.board.scene; + if (scene) { + scene.input.off('pointerdown', OnPointerDown, this); + scene.input.off('pointerup', OnPointerUp, this); + scene.input.off('pointermove', OnPointerMove, this); + } + + } + + this.board = undefined; + + // board.off('destroy', this.onBoardDestroy, this); + } + + onBoardDestroy(parent, fromScene) { + this.destroy(fromScene); + } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + + if (!e) { + this.pointer = null; + } + this._enable = e; + + if (this.touchZone) { + if (e) { + this.touchZone.setInteractive(); + } else { + this.touchZone.disableInteractive(); + } + } + this.tap.setEnable(e); + this.press.setEnable(e); + this.swipe.setEnable(e); + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } +} +export default Input; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/input/InstallPress.js b/ui/src/phaser3-rex-plugins/plugins/board/board/input/InstallPress.js new file mode 100644 index 000000000..a60b9a9d7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/input/InstallPress.js @@ -0,0 +1,54 @@ +import Press from '../../../input/gestures/press/Press.js'; +import EmitChessEvent from './EmitChessEvent.js'; + +var InstallPress = function () { + var touchZone = (this.touchZone) ? this.touchZone : this.board.scene; + var press = new Press(touchZone); + press + .on('pressstart', OnPressStart, this) + .on('pressend', OnPressEnd, this); + + return press; +} + +var OnPressStart = function (press) { + var board = this.board; + // Get touched tileX, tileY + var tileXY = board.worldXYToTileXY(press.worldX, press.worldY); + var tileX = tileXY.x, + tileY = tileXY.y; + if (!board.contains(tileX, tileY)) { + return; + } + + board.emit('tilepressstart', press, tileXY); + + EmitChessEvent( + 'gameobjectpressstart', + 'board.pressstart', + board, tileX, tileY, + press + ); +} + +var OnPressEnd = function (press) { + var board = this.board; + // Get touched tileX, tileY + var tileXY = board.worldXYToTileXY(press.worldX, press.worldY); + var tileX = tileXY.x, + tileY = tileXY.y; + if (!board.contains(tileX, tileY)) { + return; + } + + board.emit('tilepressend', press, tileXY); + + EmitChessEvent( + 'gameobjectpressend', + 'board.pressend', + board, tileX, tileY, + press + ); +} + +export default InstallPress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/input/InstallSwipe.js b/ui/src/phaser3-rex-plugins/plugins/board/board/input/InstallSwipe.js new file mode 100644 index 000000000..a06a816bb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/input/InstallSwipe.js @@ -0,0 +1,35 @@ +import Swipe from '../../../input/gestures/swipe/Swipe.js'; +import EmitChessEvent from './EmitChessEvent.js'; + +var InstallSwipe = function () { + var touchZone = (this.touchZone) ? this.touchZone : this.board.scene; + var swipe = new Swipe(touchZone); + swipe + .on('swipe', OnSwipe, this); + + return swipe; +} + +var OnSwipe = function (swipe) { + var board = this.board; + // Get touched tileX, tileY + var tileXY = board.worldXYToTileXY(swipe.worldX, swipe.worldY); + var tileX = tileXY.x, + tileY = tileXY.y; + if (!board.contains(tileX, tileY)) { + return; + } + + swipe.direction = board.angleSnapToDirection(tileXY, swipe.getVelocityAngle()); + + board.emit('tileswipe', swipe, tileXY); + + EmitChessEvent( + 'gameobjectswipe', + 'board.swipe', + board, tileX, tileY, + swipe + ); +} + +export default InstallSwipe; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/input/InstallTap.js b/ui/src/phaser3-rex-plugins/plugins/board/board/input/InstallTap.js new file mode 100644 index 000000000..bb171f115 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/input/InstallTap.js @@ -0,0 +1,40 @@ +import Tap from '../../../input/gestures/tap/Tap.js'; +import EmitChessEvent from './EmitChessEvent.js'; + +var InstallTap = function () { + var touchZone = (this.touchZone) ? this.touchZone : this.board.scene; + var tap = new Tap(touchZone); + tap.on('tap', OnTap, this); + return tap; +} + +var OnTap = function (tap) { + var board = this.board; + // Get touched tileX, tileY + var tileXY = board.worldXYToTileXY(tap.worldX, tap.worldY); + var tileX = tileXY.x, + tileY = tileXY.y; + if (!board.contains(tileX, tileY)) { + return; + } + + board.emit('tiletap', tap, tileXY); + board.emit(`tile${tap.tapsCount}tap`, tap, tileXY); + + var boardEventCallback = function (gameObject) { + board.emit('gameobjecttap', tap, gameObject); + board.emit(`gameobject${tap.tapsCount}tap`, tap, gameObject); + } + var chessEventCallback = function (gameObject) { + gameObject.emit('board.tap', tap); + gameObject.emit(`board.${tap.tapsCount}tap`, tap); + } + EmitChessEvent( + boardEventCallback, + chessEventCallback, + board, tileX, tileY, + tap + ); +} + +export default InstallTap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/input/OnPointerDown.js b/ui/src/phaser3-rex-plugins/plugins/board/board/input/OnPointerDown.js new file mode 100644 index 000000000..28f9e45d2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/input/OnPointerDown.js @@ -0,0 +1,46 @@ +import EmitChessEvent from './EmitChessEvent.js'; + +var OnPointerDown = function (pointer) { + if (!this.enable) { + return; + } + if (!pointer.isDown) { + return; + } + + var board = this.board; + if (this.pointer === null) { // Catch new touch pointer + this.pointer = pointer; + } + // Get touched tileX, tileY + var out = board.worldXYToTileXY(pointer.worldX, pointer.worldY, true); + var tileX = out.x, + tileY = out.y; + this.prevTilePosition.x = this.tilePosition.x; + this.prevTilePosition.y = this.tilePosition.y; + this.tilePosition.x = tileX; + this.tilePosition.y = tileY; + if (!board.contains(tileX, tileY)) { + return; + } + board.emit('tiledown', pointer, this.tilePosition); + board.emit('tileover', pointer, this.tilePosition); + + var boardEventCallback = function (gameObject) { + board.emit('gameobjectdown', pointer, gameObject); + board.emit('gameobjectover', pointer, gameObject); + } + var chessEventCallback = function (gameObject) { + gameObject.emit('board.pointerdown', pointer); + gameObject.emit('board.pointerover', pointer); + } + + EmitChessEvent( + boardEventCallback, + chessEventCallback, + board, tileX, tileY, + pointer + ); +}; + +export default OnPointerDown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/input/OnPointerMove.js b/ui/src/phaser3-rex-plugins/plugins/board/board/input/OnPointerMove.js new file mode 100644 index 000000000..1980a75ea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/input/OnPointerMove.js @@ -0,0 +1,74 @@ +import AreTileXYEqual from '../../utils/AreTileXYEqual.js'; +import EmitChessEvent from './EmitChessEvent.js'; + +var OnPointerMove = function (pointer) { + if (!this.enable) { + return; + } + + var board = this.board; + // Get touched tileX, tileY + var out = board.worldXYToTileXY(pointer.worldX, pointer.worldY, true); + if (AreTileXYEqual(this.tilePosition, out)) { + // Tile position dose not change + return; + } + + this.prevTilePosition.x = this.tilePosition.x; + this.prevTilePosition.y = this.tilePosition.y; + // prevTilePosition might be undefined at beginning + if ((this.prevTilePosition.x != null) && (this.prevTilePosition.y != null)) { + board.emit('tileout', pointer, this.prevTilePosition); + } + + var tileX = out.x, + tileY = out.y; + this.tilePosition.x = tileX; + this.tilePosition.y = tileY; + if (!board.contains(tileX, tileY)) { + // Move outside + EmitChessEvent( + 'gameobjectout', + 'board.pointerout', + board, this.prevTilePosition.x, this.prevTilePosition.y, + pointer + ); + + if (this.pointer === pointer) { // Release touch pointer + this.pointer = null; + } + return; + } + + if (this.pointer === null) { // Catch new touch pointer + this.pointer = pointer; + } + + board.emit('tilemove', pointer, this.tilePosition); + board.emit('tileover', pointer, this.tilePosition); + + EmitChessEvent( + 'gameobjectout', + 'board.pointerout', + board, this.prevTilePosition.x, this.prevTilePosition.y, + pointer + ); + + var boardEventCallback = function (gameObject) { + board.emit('gameobjectmove', pointer, gameObject); + board.emit('gameobjectover', pointer, gameObject); + } + var chessEventCallback = function (gameObject) { + gameObject.emit('board.pointermove', pointer); + gameObject.emit('board.pointerover', pointer); + } + + EmitChessEvent( + boardEventCallback, + chessEventCallback, + board, tileX, tileY, + pointer + ); +}; + +export default OnPointerMove; diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/input/OnPointerUp.js b/ui/src/phaser3-rex-plugins/plugins/board/board/input/OnPointerUp.js new file mode 100644 index 000000000..9dc1cce3d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/input/OnPointerUp.js @@ -0,0 +1,44 @@ +import EmitChessEvent from './EmitChessEvent.js'; + +var OnPointerUp = function (pointer) { + if (!this.enable) { + return; + } + + var board = this.board; + // Get touched tileX, tileY + var out = board.worldXYToTileXY(pointer.worldX, pointer.worldY, true); + var tileX = out.x, + tileY = out.y; + this.prevTilePosition.x = this.tilePosition.x; + this.prevTilePosition.y = this.tilePosition.y; + this.tilePosition.x = tileX; + this.tilePosition.y = tileY; + if (!board.contains(tileX, tileY)) { + return; + } + board.emit('tileup', pointer, this.tilePosition); + board.emit('tileout', pointer, this.prevTilePosition); + + var boardEventCallback = function (gameObject) { + board.emit('gameobjectup', pointer, gameObject); + board.emit('gameobjectout', pointer, gameObject); + } + var chessEventCallback = function (gameObject) { + gameObject.emit('board.pointerup', pointer); + gameObject.emit('board.pointerout', pointer); + } + + EmitChessEvent( + boardEventCallback, + chessEventCallback, + board, tileX, tileY, + pointer + ); + + if (this.pointer === pointer) { // Release touch pointer + this.pointer = null; + } +}; + +export default OnPointerUp; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/input/SetInteractive.js b/ui/src/phaser3-rex-plugins/plugins/board/board/input/SetInteractive.js new file mode 100644 index 000000000..650171e00 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/input/SetInteractive.js @@ -0,0 +1,15 @@ +import Input from './Input.js'; + +var SetInteractive = function (config) { + // Input + if (!this.input) { + this.input = new Input(this, config); + } else { + var enable = (config === false) ? false : true; + this.input.setEnable(enable); + } + + return this; +}; + +export default SetInteractive; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/input/TouchZone.js b/ui/src/phaser3-rex-plugins/plugins/board/board/input/TouchZone.js new file mode 100644 index 000000000..f49c89806 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/input/TouchZone.js @@ -0,0 +1,14 @@ +const Zone = Phaser.GameObjects.Zone; + +class TouchZone extends Zone { + constructor(scene) { + super(scene, 0, 0, 1, 1); + scene.add.existing(this); // Add to scene + this.setScrollFactor(0); + this.setInteractive({ + hitArea: {}, + hitAreaCallback: function () { return true; } + }); + } +} +export default TouchZone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/AreNeighbors.js b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/AreNeighbors.js new file mode 100644 index 000000000..068e9777c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/AreNeighbors.js @@ -0,0 +1,4 @@ +var AreNeighbors = function(chessA, chessB) { + return (this.getNeighborChessDirection(chessA, chessB) !== null); +} +export default AreNeighbors; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborChess.js b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborChess.js new file mode 100644 index 000000000..865989fe5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborChess.js @@ -0,0 +1,43 @@ +var GetNeighborChess = function (tileXYZ, directions, neighborTileZ, out) { + var tileXYZ = this.chessToTileXYZ(tileXYZ); + if (tileXYZ === null) { + return null; + } + + if (neighborTileZ == null) { + neighborTileZ = tileXYZ.z; + } + + var typeOfDirection = typeof (directions); + if ( + (typeOfDirection === 'number') || + ((typeOfDirection === 'string') && (directions.indexOf(',') === -1)) + ) { + // 1 direction + var dir = directions; + var neighborTileXY = this.getNeighborTileXY(tileXYZ, dir, true); + if (neighborTileXY === null) { + return null; + } + return this.tileXYZToChess(neighborTileXY.x, neighborTileXY.y, neighborTileZ); + } else { + // directions array + if (out === undefined) { + out = []; + } + this.getNeighborTileXY(tileXYZ, directions, globTileXYArray); + var neighborChess; + for (var i = 0, cnt = globTileXYArray.length; i < cnt; i++) { + neighborChess = this.tileXYZToChess(globTileXYArray[i].x, globTileXYArray[i].y, neighborTileZ); + if (neighborChess == null) { + continue; + } + out.push(neighborChess); + } + globTileXYArray.length = 0; + return out; + } +} + +var globTileXYArray = []; +export default GetNeighborChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborChessDirection.js b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborChessDirection.js new file mode 100644 index 000000000..cd9e4291e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborChessDirection.js @@ -0,0 +1,6 @@ +var GetNeighborChessDirection = function (chess, neighborChess) { + var srcTileXYZ = this.chessToTileXYZ(chess); + var neighborTileXYZ = this.chessToTileXYZ(neighborChess); + return this.getNeighborTileDirection(srcTileXYZ, neighborTileXYZ); +} +export default GetNeighborChessDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborTileDirection.js b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborTileDirection.js new file mode 100644 index 000000000..408e211a7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborTileDirection.js @@ -0,0 +1,31 @@ +import AreTileXYEqual from '../../utils/AreTileXYEqual.js'; + +var GetNeighborTileDirection = function (srcTileXY, neighborTileXY) { + if ((srcTileXY === null) || (neighborTileXY === null)) { + return null; + } + + srcTileXY = this.chessToTileXYZ(srcTileXY); + neighborTileXY = this.chessToTileXYZ(neighborTileXY); + + if (AreTileXYEqual(srcTileXY, neighborTileXY)) { + return null; + } + + var direction = this.grid.getNeighborTileDirection(srcTileXY, neighborTileXY); + if (this.wrapMode && (direction === null)) { + globNeighborTileXYArray = this.getNeighborTileXY(srcTileXY, null, globNeighborTileXYArray); + for (var i = 0, cnt = globNeighborTileXYArray.length; i < cnt; i++) { + if (AreTileXYEqual(neighborTileXY, globNeighborTileXYArray[i])) { + direction = i; + break; + } + } + globNeighborTileXYArray.length = 0; + } + return direction; +} + +var globNeighborTileXYArray = []; + +export default GetNeighborTileDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborTileXY.js b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborTileXY.js new file mode 100644 index 000000000..36cd98047 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborTileXY.js @@ -0,0 +1,5 @@ +var GetNeighborTileXY = function (srcTileXY, directions, out) { + return this.getTileXYAtDirection(srcTileXY, directions, 1, out); +}; + +export default GetNeighborTileXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborTileXYAtAngle.js b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborTileXYAtAngle.js new file mode 100644 index 000000000..876cccab2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetNeighborTileXYAtAngle.js @@ -0,0 +1,6 @@ +var GetNeighborTileXYAtAngle = function (srcTileXY, angle, out) { + var direction = this.angleSnapToDirection(srcTileXY, angle); + return this.getTileXYAtDirection(srcTileXY, direction, 1, out); +}; + +export default GetNeighborTileXYAtAngle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetTileXYAtDirection.js b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetTileXYAtDirection.js new file mode 100644 index 000000000..14fdf97fd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/GetTileXYAtDirection.js @@ -0,0 +1,90 @@ +import IsPlainObject from '../../../utils/object/IsPlainObject.js'; +import GetValue from '../../../utils/object/GetValue.js'; + +var GetTileXYAtDirection = function (chess, directions, distance, out) { + var srcTileXY = this.chessToTileXYZ(chess); + if (!srcTileXY) { + return null; + } + + if (typeof (directions) === 'string') { + if (directions.indexOf(',') === -1) { + directions = parseInt(directions); + } else { + directions = directions.split(','); + } + } + + var isNumberDirection = (typeof (directions) === 'number'); + var isNumberDistance = (typeof (distance) === 'number'); + if (isNumberDirection && isNumberDistance) { + // Return a single tileXY + out = this.grid.getTileXYAtDirection(srcTileXY.x, srcTileXY.y, directions, distance, out); // directions is a number, distance is a number, return a singl tileXY + this.getWrapTileXY(out.x, out.y, out); + if ((out.x == null) || (out.y == null)) { + out = null; + } else { + out.direction = directions; + } + + } else { + // Return an array of tileXY + if (out === undefined) { + out = []; + } + if (directions == null) { + directions = this.grid.allDirections; + } + + var resultTileXY; + if (isNumberDirection) { // directions is a number, distance is an object or list + if (IsPlainObject(distance)) { + var endIdx = GetValue(distance, 'end', 1); + var startIdx = GetValue(distance, 'start', (endIdx > 0) ? 1 : -1); + var step = GetValue(distance, 'step', ((endIdx >= startIdx) ? 1 : -1)); + if (startIdx === endIdx) { + resultTileXY = this.getTileXYAtDirection(srcTileXY, directions, endIdx); // Return a single tileXY + if (resultTileXY !== null) { + out.push(resultTileXY); + } + } else if (startIdx < endIdx) { + for (var i = startIdx; i <= endIdx; i += step) { + resultTileXY = this.getTileXYAtDirection(srcTileXY, directions, i); // Return a single tileXY + if (resultTileXY !== null) { + out.push(resultTileXY); + } + } + } else { + for (var i = startIdx; i >= endIdx; i += step) { + resultTileXY = this.getTileXYAtDirection(srcTileXY, directions, i); // Return a single tileXY + if (resultTileXY !== null) { + out.push(resultTileXY); + } + } + } + } else { // Is array + for (var i = 0, cnt = distance.length; i < cnt; i++) { + resultTileXY = this.getTileXYAtDirection(srcTileXY, directions, distance[i]); + if (resultTileXY !== null) { + out.push(resultTileXY); + } + } + } + } else { // directions is a list + for (var i = 0, cnt = directions.length; i < cnt; i++) { + if (isNumberDistance) { // return a single tileXY + resultTileXY = this.getTileXYAtDirection(srcTileXY, directions[i], distance); + if (resultTileXY !== null) { + out.push(resultTileXY); + } + } else { // append an array of tileXY + this.getTileXYAtDirection(srcTileXY, directions[i], distance, out); + } + + } + } + } + + return out; +} +export default GetTileXYAtDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/MapNeighobrs.js b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/MapNeighobrs.js new file mode 100644 index 000000000..dd75af5f1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/neighbors/MapNeighobrs.js @@ -0,0 +1,13 @@ +var MapNeighbors = function (chess, distance, callback, scope) { + if (typeof (distance) !== 'number') { + scope = callback; + callback = distance; + distance = 1; + } + + var tileXYArray = this.getTileXYAtDirection(chess, undefined, distance); + // Array of {x,y,direction} + return tileXYArray.map(callback, scope); +} + +export default MapNeighbors; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/ring/FilledRingToChessArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/ring/FilledRingToChessArray.js new file mode 100644 index 000000000..583e0e878 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/ring/FilledRingToChessArray.js @@ -0,0 +1,25 @@ +import IsArray from '../../../utils/object/IsArray.js'; + +var FilledRingToChessArray = function (centerTileXY, radius, tileZ, nearToFar, out) { + if (IsArray(nearToFar)) { + out = nearToFar; + nearToFar = undefined; + } + + if (nearToFar === undefined) { + nearToFar = true; + } + if (out === undefined) { + out = []; + } + + centerTileXY = this.chessToTileXYZ(centerTileXY); + + var level; + for (var i = 0; i <= radius; i++) { + level = (nearToFar) ? i : (radius - i); + this.ringToChessArray(centerTileXY, level, tileZ, out); + } + return out; +} +export default FilledRingToChessArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/ring/FilledRingToTileXYArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/ring/FilledRingToTileXYArray.js new file mode 100644 index 000000000..2a8d0fbcb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/ring/FilledRingToTileXYArray.js @@ -0,0 +1,25 @@ +import IsArray from '../../../utils/object/IsArray.js'; + +var FilledRingToTileXYArray = function (centerTileXY, radius, nearToFar, out) { + if (IsArray(nearToFar)) { + out = nearToFar; + nearToFar = undefined; + } + + if (nearToFar === undefined) { + nearToFar = true; + } + if (out === undefined) { + out = []; + } + + centerTileXY = this.chessToTileXYZ(centerTileXY); + + var level; + for (var i = 0; i <= radius; i++) { + level = (nearToFar) ? i : (radius - i); + this.ringToTileXYArray(centerTileXY, level, out); + } + return out; +} +export default FilledRingToTileXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/ring/RingToChessArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/ring/RingToChessArray.js new file mode 100644 index 000000000..b41a57668 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/ring/RingToChessArray.js @@ -0,0 +1,27 @@ +var RingToChessArray = function (centerTileXY, radius, tileZ, out) { + if (Array.isArray(tileZ)) { + out = tileZ; + tileZ = undefined; + } + if (out === undefined) { + out = []; + } + + centerTileXY = this.chessToTileXYZ(centerTileXY); + this.grid.ringToTileXYArray(centerTileXY, radius, globTileArray); + var tileXY, chess; + for (var i = 0, cnt = globTileArray.length; i < cnt; i++) { + tileXY = globTileArray[i]; + chess = this.tileXYZToChess(tileXY.x, tileXY.y, tileZ); + if (chess) { + out.push(chess); + } + } + globTileArray.length = 0; + + return out; +} + +var globTileArray = []; + +export default RingToChessArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/ring/RingToTileXYArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/ring/RingToTileXYArray.js new file mode 100644 index 000000000..4866cd07f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/ring/RingToTileXYArray.js @@ -0,0 +1,20 @@ +var RingToTileXYArray = function (centerTileXY, radius, out) { + if (out === undefined) { + out = []; + } + + centerTileXY = this.chessToTileXYZ(centerTileXY); + this.grid.ringToTileXYArray(centerTileXY, radius, globTileArray); + var tileXY; + for (var i = 0, cnt = globTileArray.length; i < cnt; i++) { + tileXY = globTileArray[i]; + if (this.contains(tileXY.x, tileXY.y)) { + out.push(tileXY); + } + } + globTileArray.length = 0; + return out; +} + +var globTileArray = []; +export default RingToTileXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/shape/CircleToTileXYArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/CircleToTileXYArray.js new file mode 100644 index 000000000..d19e0e186 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/CircleToTileXYArray.js @@ -0,0 +1,5 @@ +var CircleToTileXYArray = function (circle, testMode, out) { + return this.shapeToTileXYArray(circle, testMode, out); +} + +export default CircleToTileXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/shape/EllipseToTileXYArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/EllipseToTileXYArray.js new file mode 100644 index 000000000..567116479 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/EllipseToTileXYArray.js @@ -0,0 +1,5 @@ +var EllipseToTileXYArray = function (ellipse, testMode, out) { + return this.shapeToTileXYArray(ellipse, testMode, out); +} + +export default EllipseToTileXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/shape/ForEachTileXYInShape.js b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/ForEachTileXYInShape.js new file mode 100644 index 000000000..fd1f9fa89 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/ForEachTileXYInShape.js @@ -0,0 +1,89 @@ +import GetValue from '../../../utils/object/GetValue.js'; + +var ForEachTileXYInShape = function (shape, callback, scope, config) { + var testMode = GetValue(config, 'testMode', 0); + var searchRectangle = GetValue(config, 'searchRectangle', shape); + var order = GetValue(config, 'order', 0); + + if (scope) { + callback = callback.bind(scope); + } + + globLeftToptileXY = this.worldXYToTileXY(searchRectangle.left, searchRectangle.top, globLeftToptileXY); + globRightBottomTileXY = this.worldXYToTileXY(searchRectangle.right, searchRectangle.bottom, globRightBottomTileXY); + var left = globLeftToptileXY.x - 1, + top = globLeftToptileXY.y - 1, + right = globRightBottomTileXY.x + 1, + bottom = globRightBottomTileXY.y + 1; + + this.forEachTileXY( + function (tileXY, board) { + if (IsInShape(board, shape, tileXY.x, tileXY.y, testMode)) { + return callback(tileXY, board); + } + }, + this, + { + left: left, + right: right, + top: top, + bottom: bottom + } + ) + + return this; +}; + +var IsInShape = function (board, shape, x, y, testMode) { + var targetWorldXY = board.tileXYToWorldXY(x, y, true); + if (shape.contains(targetWorldXY.x, targetWorldXY.y)) { + return true; + } + + switch (testMode) { + case 1: // Test grid bounds (a rectangle) + var rect = board.getGridBounds(x, y, true); + return OverlapRectangle(shape, rect); + + case 2: // Test grid points + var points = board.getGridPoints(x, y, true); + return ContainsAnyPoint(shape, points); + + default: + return false; + } +} + +var OverlapRectangle = function (shape, rectangle) { + var top = rectangle.top, + bottom = rectangle.bottom, + left = rectangle.left, + right = rectangle.right; + if (shape.contains(left, top)) { + return true; + } + if (shape.contains(left, bottom)) { + return true; + } + if (shape.contains(right, top)) { + return true; + } + if (shape.contains(right, bottom)) { + return true; + } + return false; +} + +var ContainsAnyPoint = function (shape, points) { + for (var i = 0, cnt = points.length; i < cnt; i++) { + var point = points[i]; + if (shape.contains(point.x, point.y)) { + return true; + } + } + return false; +}; + +var globLeftToptileXY, globRightBottomTileXY; + +export default ForEachTileXYInShape; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/shape/LineToTileXYArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/LineToTileXYArray.js new file mode 100644 index 000000000..77c1c97a9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/LineToTileXYArray.js @@ -0,0 +1,42 @@ +import DistanceBetween from '../../../utils/math/distance/DistanceBetween.js'; +import Linear from '../../../utils/math/Linear.js'; +import AreTileXYEqual from '../../utils/AreTileXYEqual.js'; + +var LineToTileXYArray = function (startX, startY, endX, endY, out) { + if (typeof (startX) !== 'number') { + var line = startX; + out = startY; + startX = line.x1; + startY = line.y1; + endX = line.x2; + endY = line.y2; + } + + if (out === undefined) { + out = []; + } + + var totalDistance = DistanceBetween(startX, startY, endX, endY); + var gridSize = Math.min(this.grid.cellWidth, this.grid.cellHeight); + var quantity = Math.ceil(totalDistance / (gridSize / 4)), + t; + var worldX, worldY; + var preTileXY, tileXY; + for (var i = 0; i <= quantity; i++) { + t = i / quantity; + worldX = Linear(startX, endX, t); + worldY = Linear(startY, endY, t); + tileXY = this.worldXYToTileXY(worldX, worldY); + if (!this.contains(tileXY.x, tileXY.y)) { + continue; + } + if (preTileXY && AreTileXYEqual(preTileXY, tileXY)) { + continue; + } + + out.push(tileXY); + preTileXY = tileXY; + } + return out; +} +export default LineToTileXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/shape/PolygonToTileXYArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/PolygonToTileXYArray.js new file mode 100644 index 000000000..9460396a6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/PolygonToTileXYArray.js @@ -0,0 +1,18 @@ +import GetAABB from '../../../utils/geom/polygon/GetAABB.js'; + +var PolygonToTileXYArray = function (polygon, testMode, out) { + if (Array.isArray(testMode)) { + out = testMode; + testMode = undefined; + } + globSearchRectangle = GetAABB(polygon, globSearchRectangle); + var config = { + testMode: testMode, + searchRectangle: globSearchRectangle + } + return this.shapeToTileXYArray(polygon, config, out); +} + +var globSearchRectangle; + +export default PolygonToTileXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/shape/RectangleToTileXYArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/RectangleToTileXYArray.js new file mode 100644 index 000000000..245704e23 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/RectangleToTileXYArray.js @@ -0,0 +1,5 @@ +var RectangleToTileXYArray = function (rectangle, testMode, out) { + return this.shapeToTileXYArray(rectangle, testMode, out); +} + +export default RectangleToTileXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/shape/ShapeToTileXYArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/ShapeToTileXYArray.js new file mode 100644 index 000000000..dff6f6a36 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/ShapeToTileXYArray.js @@ -0,0 +1,29 @@ +var ShapeToTileXYArray = function (shape, config, out) { + if (typeof (config) === 'number') { + config = { + testMode: config + } + } + + if (Array.isArray(config)) { + out = config; + config = undefined; + } + + if (out === undefined) { + out = []; + } + + this.forEachTileXYInShape( + shape, + function (tileXY) { + out.push({ x: tileXY.x, y: tileXY.y }); + }, + undefined, + config + ); + + return out; +}; + +export default ShapeToTileXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/shape/TriangleToTileXYArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/TriangleToTileXYArray.js new file mode 100644 index 000000000..7c1282361 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/shape/TriangleToTileXYArray.js @@ -0,0 +1,5 @@ +var TriangleToTileXYArray = function (triangle, testMode, out) { + return this.shapeToTileXYArray(triangle, testMode, out); +} + +export default TriangleToTileXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/ChessToTileXYZ.js b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/ChessToTileXYZ.js new file mode 100644 index 000000000..718345a01 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/ChessToTileXYZ.js @@ -0,0 +1,21 @@ +import IsUID from '../../chess/IsUID.js'; +import IsChess from '../../chess/IsChess'; +import GetChessUID from '../../chess/GetChessUID.js'; +import IsTileXYZ from '../../utils/IsTileXYZ.js'; + +var ChessToTileXYZ = function (chess) { + if (!chess) { + return null; + } + + // chess: chess object, UID, or tileXYZ + if (IsUID(chess) || IsChess(chess)) { // UID, or game object + var uid = GetChessUID(chess); + return this.boardData.getXYZ(uid); + } else if (IsTileXYZ(chess)) { // {x, y}, or {x, y, z} + return chess; + } else { + return null; + } +} +export default ChessToTileXYZ; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/Contains.js b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/Contains.js new file mode 100644 index 000000000..6d1c2c7d6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/Contains.js @@ -0,0 +1,14 @@ +var Contains = function (tileX, tileY, tileZ) { + var result; + if (this.infinityMode) { + result = true; + } else { + result = (tileX >= 0) && (tileX < this.width) && (tileY >= 0) && (tileY < this.height); + } + if (result && (tileZ !== undefined)) { + result = this.boardData.contains(tileX, tileY, tileZ); + } + return result; +}; + +export default Contains; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/DirectionBetween.js b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/DirectionBetween.js new file mode 100644 index 000000000..a137db456 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/DirectionBetween.js @@ -0,0 +1,9 @@ +var DirectionBetween = function (chessA, chessB, round) { + if (round === undefined) { + round = true; + } + var tileA = this.chessToTileXYZ(chessA); + var tileB = this.chessToTileXYZ(chessB); + return this.grid.directionBetween(tileA, tileB, round); +} +export default DirectionBetween; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/ForEachTileXY.js b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/ForEachTileXY.js new file mode 100644 index 000000000..8b2a1ab40 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/ForEachTileXY.js @@ -0,0 +1,110 @@ +import GetValue from '../../../utils/object/GetValue.js'; +import Clamp from '../../../utils/math/Clamp.js'; + +var ForEachTileXY = function (callback, scope, config) { + if (typeof (config) === 'number') { + config = { + order: config + } + } + + var lastX = this.width - 1, + lastY = this.height - 1; + var order = GetValue(config, 'order', 0); + var left = GetValue(config, 'left', 0); + var right = GetValue(config, 'right', lastX); + var top = GetValue(config, 'top', 0); + var bottom = GetValue(config, 'bottom', lastY); + + if (!this.infinityMode) { + left = Clamp(left, 0, lastX); + top = Clamp(top, 0, lastY); + right = Clamp(right, 0, lastX); + bottom = Clamp(bottom, 0, lastY); + } + + switch (order) { + case 0: // x+,y+ + var isBreak; + for (var y = top; y <= bottom; y++) { + for (var x = left; x <= right; x++) { + globTileXY.x = x; + globTileXY.y = y; + if (scope) { + isBreak = callback.call(scope, globTileXY, this); + } else { + isBreak = callback(globTileXY, this); + } + + if (isBreak) { + break; + } + } + } + break; + + case 1: // x-,y+ + var isBreak; + for (var y = top; y <= bottom; y++) { + for (var x = right; x >= left; x--) { + globTileXY.x = x; + globTileXY.y = y; + if (scope) { + isBreak = callback.call(scope, globTileXY, this); + } else { + isBreak = callback(globTileXY, this); + } + + if (isBreak) { + break; + } + } + } + break; + + case 2: // y+,x+ + var isBreak; + for (var x = left; x <= right; x++) { + for (var y = top; y <= bottom; y++) { + globTileXY.x = x; + globTileXY.y = y; + if (scope) { + isBreak = callback.call(scope, globTileXY, this); + } else { + isBreak = callback(globTileXY, this); + } + + if (isBreak) { + break; + } + } + } + break; + + case 3: // y-,x+ + var isBreak; + for (var x = left; x <= right; x++) { + for (var y = bottom; y >= top; y--) { + globTileXY.x = x; + globTileXY.y = y; + if (scope) { + isBreak = callback.call(scope, globTileXY, this); + } else { + isBreak = callback(globTileXY, this); + } + + if (isBreak) { + break; + } + } + } + } + return this; +}; + +var globTileXY = { + x: 0, + y: 0 +} + +export default ForEachTileXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/GetDistance.js b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/GetDistance.js new file mode 100644 index 000000000..3170de858 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/GetDistance.js @@ -0,0 +1,6 @@ +var GetDistance = function (tileA, tileB, roughMode) { + tileA = this.chessToTileXYZ(tileA); + tileB = this.chessToTileXYZ(tileB); + return this.grid.getDistance(tileA, tileB, roughMode); +} +export default GetDistance; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/GetOppositeDirection.js b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/GetOppositeDirection.js new file mode 100644 index 000000000..067629561 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/GetOppositeDirection.js @@ -0,0 +1,11 @@ +var GetOppositeDirection = function (tileX, tileY, direction) { + if (tileX && (typeof (tileX) !== 'number')) { + direction = tileY; + var chess = tileX; + var tileXY = this.chessToTileXYZ(chess); + tileX = tileXY.x; + tileY = tileXY.y; + } + return this.grid.getOppositeDirection(tileX, tileY, direction); +} +export default GetOppositeDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/GetWrapTileXY.js b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/GetWrapTileXY.js new file mode 100644 index 000000000..09d5d9783 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/GetWrapTileXY.js @@ -0,0 +1,29 @@ +import Wrap from '../../../utils/math/Wrap.js'; + +var GetWrapTileXY = function (tileX, tileY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + if (this.wrapMode) { + tileX = Wrap(tileX, 0, this.width); + } else if ((!this.infinityMode) && + ((tileX < 0) || (tileX >= this.width))) { + tileX = null; + } + if (this.wrapMode) { + tileY = Wrap(tileY, 0, this.height); + } else if ((!this.infinityMode) && + ((tileY < 0) || (tileY >= this.height))) { + tileY = null; + } + out.x = tileX; + out.y = tileY; + return out; +} + +var globTileXY = {}; + +export default GetWrapTileXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/IsDirectionInCone.js b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/IsDirectionInCone.js new file mode 100644 index 000000000..039d8b5f6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/IsDirectionInCone.js @@ -0,0 +1,14 @@ +var IsDirectionInCone = function (chessA, chessB, face, cone) { + var tileXYA = this.chessToTileXYZ(chessA); + var tileXYB = this.chessToTileXYZ(chessB); + + var savedDirections = this.grid.directions; // Save directions + this.grid.setDirectionMode(this.sides); + var direction = this.grid.directionBetween(tileXYA, tileXYB, false); + this.grid.setDirectionMode(savedDirections); // Restore directions + + var deltaDirection = Math.abs(direction - face); + deltaDirection = Math.min(deltaDirection, this.grid.directions - deltaDirection); + return (deltaDirection <= (cone / 2)); +} +export default IsDirectionInCone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileXYArrayToChessArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileXYArrayToChessArray.js new file mode 100644 index 000000000..1aaccfe77 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileXYArrayToChessArray.js @@ -0,0 +1,21 @@ +var TileXYArrayToChessArray = function (tileXYArray, tileZ, out) { + if (Array.isArray(tileZ)) { + out = tileZ; + tileZ = undefined; + } + if (out === undefined) { + out = []; + } + var tileZMode = (tileZ != null); + var tileXY; + for (var i = 0, cnt = tileXYArray.length; i < cnt; i++) { + tileXY = tileXYArray[i]; + if (tileZMode) { + out.push(this.tileXYZToChess(tileXY.x, tileXY.y, tileZ)); + } else { + this.tileXYToChessArray(tileXY.x, tileXY.y, out); + } + } + return out; +} +export default TileXYArrayToChessArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileXYToChessArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileXYToChessArray.js new file mode 100644 index 000000000..9c3799357 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileXYToChessArray.js @@ -0,0 +1,15 @@ +var TileXYToChessArray = function (tileX, tileY, out) { + if (out === undefined) { + out = []; + } + var tileZToUIDs = this.boardData.getUID(tileX, tileY); + if (tileZToUIDs == null) { + return out; + } + + for (var tileZ in tileZToUIDs) { + out.push(this.uidToChess(tileZToUIDs[tileZ])); + } + return out; +} +export default TileXYToChessArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileXYZToChess.js b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileXYZToChess.js new file mode 100644 index 000000000..687b51b79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileXYZToChess.js @@ -0,0 +1,5 @@ +var TileXYZToChess = function (tileX, tileY, tileZ) { + var uid = this.boardData.getUID(tileX, tileY, tileZ); + return this.uidToChess(uid); +} +export default TileXYZToChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileZToChessArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileZToChessArray.js new file mode 100644 index 000000000..972cd5978 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/tileposition/TileZToChessArray.js @@ -0,0 +1,16 @@ +var TileZToChessArray = function (tileZ, out) { + if (out === undefined) { + out = []; + } + var uids = this.boardData.UIDToXYZ; + var tileXYZ; + for (var uid in uids) { + tileXYZ = uids[uid]; + if (tileXYZ.z !== tileZ) { + continue; + } + out.push(this.uidToChess(uid)); + } + return out; +} +export default TileZToChessArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/transform/Fit.js b/ui/src/phaser3-rex-plugins/plugins/board/board/transform/Fit.js new file mode 100644 index 000000000..971ab26ee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/transform/Fit.js @@ -0,0 +1,33 @@ +// Offset tileXYArray to (0,0), and set board size to fit tileXYArray +var Fit = function (tileXYArray) { + // Get minimum tileX, tileY + var minX = Infinity; + var minY = Infinity; + var tileXY; + for (var i in tileXYArray) { + tileXY = tileXYArray[i]; + minX = Math.min(minX, tileXY.x); + minY = Math.min(minY, tileXY.y); + } + // Offset tileXYArray to (0,0) + if ((minX !== 0) || (minY !== 0)) { + for (var i in tileXYArray) { + tileXY = tileXYArray[i]; + this.offset(tileXY, -minX, -minY, tileXY); + } + } + + // Get maximun tileX, tileY + var maxX = -Infinity; + var maxY = -Infinity; + for (var i in tileXYArray) { + tileXY = tileXYArray[i]; + maxX = Math.max(maxX, tileXY.x); + maxY = Math.max(maxY, tileXY.y); + } + // Set board size + this.setBoardWidth(maxX + 1); + this.setBoardHeight(maxY + 1); + return tileXYArray; +} +export default Fit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/transform/Mirror.js b/ui/src/phaser3-rex-plugins/plugins/board/board/transform/Mirror.js new file mode 100644 index 000000000..32626b614 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/transform/Mirror.js @@ -0,0 +1,22 @@ +var Mirror = function (tileXY, mode, originTileXY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + if (originTileXY !== undefined) { + this.offset(tileXY, -originTileXY.x, -originTileXY.y, out); + } else { + out.x = tileXY.x; + out.y = tileXY.y; + } + this.grid.mirror(out, mode, out); + if (originTileXY !== undefined) { + this.offset(out, originTileXY.x, originTileXY.y, out); + } + return out; +}; + +var globTileXY = {}; +export default Mirror; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/transform/Offset.js b/ui/src/phaser3-rex-plugins/plugins/board/board/transform/Offset.js new file mode 100644 index 000000000..5bfd1735c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/transform/Offset.js @@ -0,0 +1,18 @@ +var Offset = function (tileXY, OffsetTileX, OffsetTileY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + if ((OffsetTileX === 0) && (OffsetTileY === 0)) { + out.x = tileXY.x; + out.y = tileXY.y; + } else { + this.grid.offset(tileXY, OffsetTileX, OffsetTileY, out); + } + return out; +}; + +var globTileXY = {}; +export default Offset; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/transform/Rotate.js b/ui/src/phaser3-rex-plugins/plugins/board/board/transform/Rotate.js new file mode 100644 index 000000000..bfe83f8ea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/transform/Rotate.js @@ -0,0 +1,22 @@ +var Rotate = function (tileXY, direction, originTileXY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + + if (originTileXY !== undefined) { + this.offset(tileXY, -originTileXY.x, -originTileXY.y, out); + } else { + out.x = tileXY.x; + out.y = tileXY.y; + } + this.grid.rotate(out, direction, out); + if (originTileXY !== undefined) { + this.offset(out, originTileXY.x, originTileXY.y, out); + } + return out; +}; + +var globTileXY = {}; +export default Rotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/AngleBetween.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/AngleBetween.js new file mode 100644 index 000000000..f90660d5a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/AngleBetween.js @@ -0,0 +1,15 @@ +import GetAngle from '../../../utils/math/angle/Between.js'; + +var AngleBetween = function (tileA, tileB) { + tileA = this.chessToTileXYZ(tileA); + tileB = this.chessToTileXYZ(tileB); + var out = this.tileXYToWorldXY(tileA.x, tileA.y, true); + var x0 = out.x; + var y0 = out.y; + out = this.tileXYToWorldXY(tileB.x, tileB.y, true); + var x1 = out.x; + var y1 = out.y; + return GetAngle(x0, y0, x1, y1); // -PI~PI +} + +export default AngleBetween; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/AngleSnapToDirection.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/AngleSnapToDirection.js new file mode 100644 index 000000000..b81f81d8e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/AngleSnapToDirection.js @@ -0,0 +1,22 @@ +import RadToDeg from '../../../utils/math/RadToDeg.js'; +import ShortestBetween from '../../../utils/math/angle/ShortestBetween.js'; + +var AngleSnapToDirection = function (tileXY, angle) { + angle = RadToDeg(angle); // -180~180 + var directions = this.grid.allDirections; + var neighborAngle, deltaAngle; + var minDeltaAngle = Infinity, + direction = undefined; + for (var i = 0, cnt = directions.length; i < cnt; i++) { + neighborAngle = RadToDeg(this.angleToward(tileXY, directions[i])); // -PI~PI -> -180~180 + deltaAngle = Math.abs(ShortestBetween(angle, neighborAngle)); + if (deltaAngle < minDeltaAngle) { + minDeltaAngle = deltaAngle; + direction = i; + } + } + + return direction; +}; + +export default AngleSnapToDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/AngleToward.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/AngleToward.js new file mode 100644 index 000000000..c9914e190 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/AngleToward.js @@ -0,0 +1,22 @@ +var AngleToward = function (tileXY, direction) { + if (tileXY === undefined) { + tileXY = zeroTileXY; + } + // Save wrapMode, infinityMode and clear them + var wrapModeSave = this.wrapMode; + var infinityModeSave = this.infinityMode; + this.wrapMode = false; + this.infinityMode = true; + + // Get neighborTileXY + var neighborTileXY = this.getNeighborTileXY(tileXY, direction, true); + + // Restore wrapMode, infinityMode and clear them + this.wrapMode = wrapModeSave; + this.infinityMode = infinityModeSave; + return this.angleBetween(tileXY, neighborTileXY); // -PI~PI +} + +var zeroTileXY = { x: 0, y: 0 }; + +export default AngleToward; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GetBoardBounds.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GetBoardBounds.js new file mode 100644 index 000000000..96cf52b79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GetBoardBounds.js @@ -0,0 +1,27 @@ +import Rectangle from '../../../utils/geom/rectangle/Rectangle.js'; +import Union from '../../../utils/geom/rectangle/Union.js'; + +var GetBoardBounds = function (out) { + if (out === undefined) { + out = new Rectangle(); + } else if (out === true) { + out = globalBounds; + } + + var isFirstTile = true; + this.forEachTileXY(function (tileXY, board) { + var tileBounds = board.getGridBounds(tileXY.x, tileXY.y, true); + if (isFirstTile) { + out.setTo(tileBounds.x, tileBounds.y, tileBounds.width, tileBounds.height); + isFirstTile = false; + } else { + out = Union(out, tileBounds, out); + } + }); + + return out; +} + +var globalBounds = new Rectangle(); + +export default GetBoardBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GetGridBounds.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GetGridBounds.js new file mode 100644 index 000000000..369f9752c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GetGridBounds.js @@ -0,0 +1,11 @@ +var GetGridBounds = function (tileX, tileY, out) { + if (tileX && (typeof (tileX) !== 'number')) { + out = tileY; + var tileXY = this.chessToTileXYZ(tileX); // tileX is a Chess or TileXY + tileX = tileXY.x; + tileY = tileXY.y; + } + return this.grid.getBounds(tileX, tileY, out); +} + +export default GetGridBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GetGridPoints.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GetGridPoints.js new file mode 100644 index 000000000..d0511292f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GetGridPoints.js @@ -0,0 +1,10 @@ +var GetGridPoints = function (tileX, tileY, points) { + if (tileX && (typeof (tileX) !== 'number')) { + points = tileY; + var tileXY = this.chessToTileXYZ(tileX); // tileX is a Chess or TileXY + tileX = tileXY.x; + tileY = tileXY.y; + } + return this.grid.getGridPoints(tileX, tileY, points); +} +export default GetGridPoints; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GridAlign.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GridAlign.js new file mode 100644 index 000000000..6573c6eb3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/GridAlign.js @@ -0,0 +1,24 @@ +import IsUID from '../../chess/IsUID.js'; + +var GridAlign = function (gameObject, tileX, tileY) { + if (gameObject === undefined) { + var chess = this.getAllChess(); + for (var i = 0, cnt = chess.length; i < cnt; i++) { + this.gridAlign(chess[i]); + } + } else { + if (IsUID(gameObject)) { + gameObject = this.uidToChess(gameObject); + } + if (tileX === undefined) { + var tileXYZ = this.chessToTileXYZ(gameObject); + tileX = tileXYZ.x; + tileY = tileXYZ.y; + } + + this.tileXYToWorldXY(tileX, tileY, gameObject); + } + return this; +}; + +export default GridAlign; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/IsAngleInCone.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/IsAngleInCone.js new file mode 100644 index 000000000..ac081229e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/IsAngleInCone.js @@ -0,0 +1,17 @@ +import AngleNormalize from '../../../utils/math/angle/Normalize.js'; +import Equal from '../../../utils/math/fuzzy/Equal.js'; + +var IsAngleInCone = function (chessA, chessB, face, cone) { + var tileXYA = this.chessToTileXYZ(chessA); + var tileXYB = this.chessToTileXYZ(chessB); + var targetAngle = this.angleBetween(tileXYA, tileXYB); // -PI~PI + targetAngle = AngleNormalize(targetAngle); // 0~2PI + var deltaAngle = Math.abs(targetAngle - face); + deltaAngle = Math.min(deltaAngle, PI2 - deltaAngle); + var halfCone = cone / 2; + return Equal(deltaAngle, halfCone) || (deltaAngle < halfCone); +} + +const PI = Math.PI; +const PI2 = Math.PI * 2; +export default IsAngleInCone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/IsOverlappingPoint.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/IsOverlappingPoint.js new file mode 100644 index 000000000..85e149291 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/IsOverlappingPoint.js @@ -0,0 +1,9 @@ +var IsOverlappingPoint = function (worldX, worldY, tileZ) { + if (this.infinityMode && (tileZ === undefined)) { + return true; + } + + var out = this.worldXYToTileXY(worldX, worldY, true); + return this.contains(out.x, out.y, tileZ); +} +export default IsOverlappingPoint; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYArrayToWorldXYArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYArrayToWorldXYArray.js new file mode 100644 index 000000000..7a6817180 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYArrayToWorldXYArray.js @@ -0,0 +1,13 @@ +var TileXYArrayToWorldXYArray = function (tileXYArray, out) { + if (out === undefined) { + out = []; + } + + var tileXY; + for (var i = 0, cnt = tileXYArray.length; i < cnt; i++) { + tileXY = tileXYArray[i]; + out.push(this.tileXYToWorldXY(tileXY.x, tileXY.y)); + } + return out; +}; +export default TileXYArrayToWorldXYArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYToWorldX.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYToWorldX.js new file mode 100644 index 000000000..e030839ab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYToWorldX.js @@ -0,0 +1,5 @@ +var TileXYToWorldX = function (tileX, tileY) { + // console.warn('Use board.tileXYToWorldXY instead of (board.tileXYToWorldX, board.tileXYToWorldY)'); + return this.tileXYToWorldXY(tileX, tileY, true).x; +} +export default TileXYToWorldX; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYToWorldXY.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYToWorldXY.js new file mode 100644 index 000000000..2863f5bfd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYToWorldXY.js @@ -0,0 +1,4 @@ +var TileXYToWorldXY = function (tileX, tileY, out) { + return this.grid.getWorldXY(tileX, tileY, out); +} +export default TileXYToWorldXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYToWorldY.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYToWorldY.js new file mode 100644 index 000000000..503083b27 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/TileXYToWorldY.js @@ -0,0 +1,5 @@ +var TileXYToWorldY = function (tileX, tileY) { + // console.warn('Use board.tileXYToWorldXY instead of (board.tileXYToWorldX, board.tileXYToWorldY)'); + return this.tileXYToWorldXY(tileX, tileY, true).y; +} +export default TileXYToWorldY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYSnapToGrid.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYSnapToGrid.js new file mode 100644 index 000000000..effba5f95 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYSnapToGrid.js @@ -0,0 +1,15 @@ +var WorldXYSnapToGrid = function (worldX, worldY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globWorldXY; + } + + this.worldXYToTileXY(worldX, worldY, out); + this.tileXYToWorldXY(out.x, out.y, out); + return out; +}; + +var globWorldXY = {}; + +export default WorldXYSnapToGrid; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToChess.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToChess.js new file mode 100644 index 000000000..3c72ff567 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToChess.js @@ -0,0 +1,16 @@ +var WorldXYToChess = function (worldX, worldY, tileZ) { + var tileXY = this.worldXYToTileXY(worldX, worldY, true); + if (tileZ !== undefined) { + return this.tileXYZToChess(tileXY.x, tileXY.y, tileZ) + } else { + var tileZToUIDs = this.boardData.getUID(tileXY.x, tileXY.y); + if (tileZToUIDs == null) { + return null; + } + for (var tileZ in tileZToUIDs) { + return this.uidToChess(tileZToUIDs[tileZ]); + } + } +} + +export default WorldXYToChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToChessArray.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToChessArray.js new file mode 100644 index 000000000..a3ae5eeae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToChessArray.js @@ -0,0 +1,6 @@ +var WorldXYToChessArray = function (worldX, worldY, out) { + var tileXY = this.worldXYToTileXY(worldX, worldY, true); + return this.tileXYToChessArray(tileXY.x, tileXY.y, out) +} + +export default WorldXYToChessArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToTileX.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToTileX.js new file mode 100644 index 000000000..b425df136 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToTileX.js @@ -0,0 +1,5 @@ +var WorldXYToTileX = function (worldX, worldY) { + // console.warn('Use board.worldXYToTileXY instead of (board.worldXYToTileX, board.worldXYToTileY)'); + return this.worldXYToTileXY(worldX, worldY, true).x; +} +export default WorldXYToTileX; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToTileXY.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToTileXY.js new file mode 100644 index 000000000..a043996b3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToTileXY.js @@ -0,0 +1,4 @@ +var WorldXYToTileXY = function (worldX, worldY, out) { + return this.grid.getTileXY(worldX, worldY, out); +} +export default WorldXYToTileXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToTileY.js b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToTileY.js new file mode 100644 index 000000000..c881adfa8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/board/worldposition/WorldXYToTileY.js @@ -0,0 +1,5 @@ +var WorldXYToTileY = function (worldX, worldY) { + // console.warn('Use board.worldXYToTileXY instead of (board.worldXYToTileX, board.worldXYToTileY)'); + return this.worldXYToTileXY(worldX, worldY, true).y; +} +export default WorldXYToTileY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/chess/ChessBank.js b/ui/src/phaser3-rex-plugins/plugins/board/chess/ChessBank.js new file mode 100644 index 000000000..a81c34f90 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/chess/ChessBank.js @@ -0,0 +1,7 @@ +import Bank from '../../bank.js'; + +var ChessBank = new Bank({ + uidKey: '$uid', + remove: false, // remove uid manually +}); +export default ChessBank; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/chess/ChessData.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/chess/ChessData.d.ts new file mode 100644 index 000000000..a3872424c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/chess/ChessData.d.ts @@ -0,0 +1,20 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; +import Board from '../board/LogicBoard'; + +export default ChessData; + +declare class ChessData extends ComponentBase { + readonly $uid: number; + + readonly board: Board; + + readonly tileXYZ: { x: number, y: number, z: number }; + + setTileZ(tileZ: number): this; + + getTileDirection(tileX: number, tileY: number): this; + + setBlocker(value?: boolean): this; + + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/chess/ChessData.js b/ui/src/phaser3-rex-plugins/plugins/board/chess/ChessData.js new file mode 100644 index 000000000..16f50f07c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/chess/ChessData.js @@ -0,0 +1,103 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import ChessBank from './ChessBank.js'; +import GetTileDirection from './GetTileDirection.js'; +import IsPlainObject from '../../utils/object/IsPlainObject.js'; + +const uidKey = ChessBank.uidKey; + +class Chess extends ComponentBase { + constructor(parent, uid) { + super(parent, { eventEmitter: false }); + // this.parent + + ChessBank.add(this, uid); // uid is stored in `this.$uid` + this.board = null; + this.blocker = false; + } + + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + if (this.board) { + this.board.removeChess(this[uidKey]); + } + ChessBank.remove(this[uidKey]); + this.board = null; + + super.shutdown(fromScene); + } + + setBoard(board) { + this.board = board; + return this; + } + + get tileXYZ() { + if (this.board == null) { + return null; + } + return this.board.chessToTileXYZ(this[uidKey]); + } + + setTileZ(tileZ) { + if (this.board == null) { + return this; + } + this.board.setChessTileZ(this.parent, tileZ); + return this; + } + + setBlocker(value) { + if (value === undefined) { + value = true; + } + this.blocker = value; + return this; + } + + setBlockEdge(direction, value) { + if (this.blocker === false) { + this.blocker = {}; + } + var blocker = this.blocker; + if (IsPlainObject(direction)) { + var blockEdges = direction; + for (direction in blockEdges) { + blocker[direction] = blockEdges[direction]; + } + } else { + if (value === undefined) { + value = true; + } + blocker[direction] = value; + } + return this; + } + + getBlockEdge(direction) { + var blocker = this.blocker; + if (blocker === false) { + return false; + } + + if (!blocker.hasOwnProperty(direction)) { + return false; + } else { + return blocker[direction]; + } + } +} + +var methods = { + getTileDirection: GetTileDirection +}; +Object.assign( + Chess.prototype, + methods +); + +export default Chess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/chess/GetChessData.js b/ui/src/phaser3-rex-plugins/plugins/board/chess/GetChessData.js new file mode 100644 index 000000000..ecf42672f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/chess/GetChessData.js @@ -0,0 +1,18 @@ +import ChessBank from './ChessBank.js'; +import ChessData from './ChessData.js'; +import IsUID from './IsUID'; + +var GetChessData = function (gameObject) { + // game object or uid + if (IsUID(gameObject)) { + // uid + return ChessBank.get(gameObject); + } else { + // game object + if (!gameObject.hasOwnProperty('rexChess')) { + gameObject.rexChess = new ChessData(gameObject); + } + return gameObject.rexChess; + } +} +export default GetChessData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/chess/GetChessUID.js b/ui/src/phaser3-rex-plugins/plugins/board/chess/GetChessUID.js new file mode 100644 index 000000000..ba2837221 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/chess/GetChessUID.js @@ -0,0 +1,16 @@ +import GetChessData from './GetChessData.js'; +import ChessBank from './ChessBank.js'; +import IsUID from './IsUID.js'; + +const uidKey = ChessBank.uidKey; +var GetChessUID = function (gameObject) { + // Game object or uid + var uid; + if (IsUID(gameObject)) { + uid = gameObject; + } else { + uid = GetChessData(gameObject)[uidKey]; + } + return uid; +} +export default GetChessUID; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/chess/GetTileDirection.js b/ui/src/phaser3-rex-plugins/plugins/board/chess/GetTileDirection.js new file mode 100644 index 000000000..2b461edb4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/chess/GetTileDirection.js @@ -0,0 +1,15 @@ +var GetTileDirection = function(tileX, tileY) { + var board = this.board; + if (board === null) { + return null; + } + globTileXY.x = tileX; + globTileXY.y = tileY; + return board.getNeighborTileDirection(this.tileXYZ, globTileXY); +} + +var globTileXY = { + x: 0, + y: 0 +}; +export default GetTileDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/chess/IsChess.js b/ui/src/phaser3-rex-plugins/plugins/board/chess/IsChess.js new file mode 100644 index 000000000..f8d40eb06 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/chess/IsChess.js @@ -0,0 +1,11 @@ +import IsUID from './IsUID.js' + +var IsChess = function (chess) { + if (IsUID(chess)) { // Number or string + return false; + } else { + return chess && (!!chess.rexChess); + } +} + +export default IsChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/chess/IsUID.js b/ui/src/phaser3-rex-plugins/plugins/board/chess/IsUID.js new file mode 100644 index 000000000..1e16c936b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/chess/IsUID.js @@ -0,0 +1,5 @@ +var IsUID = function (object) { + var type = typeof (object); + return (type === 'number') || (type === 'string'); +} +export default IsUID; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/Factory.d.ts new file mode 100644 index 000000000..bde84e939 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/Factory.d.ts @@ -0,0 +1,6 @@ +import FieldOfView from './FieldOfView'; + +export default function ( + gameObject: Phaser.GameObjects.GameObject, + config?: FieldOfView.IConfig +): FieldOfView; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/Factory.js b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/Factory.js new file mode 100644 index 000000000..0e1d55b09 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/Factory.js @@ -0,0 +1,11 @@ +import FieldOfView from './FieldOfView.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../utils/object/SetValue.js'; + +ObjectFactory.register('fieldOfView', function (gameObject, config) { + return new FieldOfView(gameObject, config); +}); + +SetValue(window, 'RexPlugins.Board.FieldOfView', FieldOfView); + +export default FieldOfView; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/FieldOfView.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/FieldOfView.d.ts new file mode 100644 index 000000000..66210bd92 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/FieldOfView.d.ts @@ -0,0 +1,137 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import { TileXYType } from '../types/Position'; +import Board from '../board/Board'; + +export default FieldOfView; + +declare namespace FieldOfView { + + type ConeModeType = 0 | 1 | 'direction' | 'angle'; + + type BLOCKER = null; + type INFINITY = undefined; + + type PreTestCallbackType = ( + tileXYArray: TileXYType[], + visiblePoints: number | INFINITY, + fieldOfView: FieldOfView, + ) => boolean; + + type CostCallbackType = ( + curTile: TileXYType, + fieldOfView: FieldOfView, + tileXYArray: TileXYType[] + ) => number | BLOCKER; + + + interface IConfig { + face?: number, + cone?: number | undefined, + coneMode?: ConeModeType, + + // pre-test + occupiedTest?: boolean, + blockerTest?: boolean, + preTestCallback: PreTestCallbackType, + preTestCallbackScope?: object, + + // cost + costCallback: CostCallbackType, + costCallbackScope?: object, + cost?: number, + + perspective?: boolean, + + debug?: { + graphics: Phaser.GameObjects.Graphics, + visibleLineColor?: number, + invisibleLineColor?: number, + log?: boolean, + } + } +} + +declare class FieldOfView extends ComponentBase { + constructor( + gameObject: ChessType, + config?: FieldOfView.IConfig + ); + + constructor( + config?: FieldOfView.IConfig + ); + + readonly board: Board; + + setPreTestFunction( + callback: FieldOfView.PreTestCallbackType, + scope?: object + ): this; + + setCostFunction(cost: number): this; + setCostFunction( + callback: FieldOfView.CostCallbackType, + scope?: object + ): this; + + isInLOS( + chess: ChessType | TileXYType, + visiblePoints?: number | FieldOfView.INFINITY, + originTileXY?: TileXYType + ): boolean; + + findFOV( + visiblePoints?: number | FieldOfView.INFINITY, + out?: TileXYType[] + ): TileXYType[]; + + findFOV( + visiblePoints?: number | FieldOfView.INFINITY, + originTileXY?: TileXYType, + out?: TileXYType[] + ): TileXYType[]; + + LOS( + chess: ChessType | TileXYType, + visiblePoints?: number | FieldOfView.INFINITY, + originTileXY?: TileXYType + ): boolean; + + LOS( + chess: (ChessType | TileXYType)[], + out?: (ChessType | TileXYType)[], + ): (ChessType | TileXYType)[]; + + + LOS( + chess: (ChessType | TileXYType)[], + originTileXY?: TileXYType, + out?: (ChessType | TileXYType)[], + ): (ChessType | TileXYType)[]; + + LOS( + chess: (ChessType | TileXYType)[], + visiblePoints?: number | FieldOfView.INFINITY, + out?: (ChessType | TileXYType)[], + ): (ChessType | TileXYType)[]; + + LOS( + chess: (ChessType | TileXYType)[], + visiblePoints?: number | FieldOfView.INFINITY, + originTileXY?: TileXYType, + out?: (ChessType | TileXYType)[], + ): (ChessType | TileXYType)[]; + + + setFace(direction: number): this; + face: number; + + clearDebugGraphics(): this; + setDebugLineColor( + visibleLineColor?: number | undefined, + invisibleLineColor?: number | undefined + ): this; + + readonly BLOCKER: FieldOfView.BLOCKER; + readonly INFINITY: FieldOfView.INFINITY; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/FieldOfView.js b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/FieldOfView.js new file mode 100644 index 000000000..dc67af2f8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/FieldOfView.js @@ -0,0 +1,239 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import Methods from './Methods.js'; +import GetChessData from '../chess/GetChessData.js'; +import CONST from './const.js'; +import DegToRad from '../../utils/math/DegToRad.js'; +import AngleNormalize from '../../utils/math/angle/Normalize.js'; +import IsPlainObject from '../../utils/object/IsPlainObject.js'; +import GetValue from '../../utils/object/GetValue.js'; + +const BLOCKER = CONST.BLOCKER; +const INFINITY = CONST.INFINITY; + +class FieldOfView extends ComponentBase { + constructor(gameObject, config) { + if (IsPlainObject(gameObject)) { + config = gameObject; + gameObject = undefined; + } + + super(gameObject, { eventEmitter: false }); + // No event emitter + // this.parent = gameObject; + + this.setChess(gameObject); + this.resetFromJSON(config); + } + + resetFromJSON(o) { + // Pre-test + var occupiedTest = GetValue(o, 'occupiedTest', false); + var blockerTest = GetValue(o, 'blockerTest', false); + var edgeBlockerTest = GetValue(o, 'edgeBlockerTest', false); // Unsupport now + var preTestCallback = GetValue(o, 'preTestCallback', undefined); + var preTestCallbackScope = GetValue(o, 'preTestCallbackScope', undefined); + // Cost of each tile + var costCallback = GetValue(o, 'costCallback', undefined); + var costCallbackScope = GetValue(o, 'costCallbackScope', undefined); + if (costCallback === undefined) { + costCallback = GetValue(o, 'cost', undefined); + } + + this.setFace(GetValue(o, 'face', 0)); + this.setConeMode(GetValue(o, 'coneMode', 0)); + this.setCone(GetValue(o, 'cone', undefined)); + this.setOccupiedTest(occupiedTest); + this.setBlockerTest(blockerTest); + this.setEdgeBlockerTest(edgeBlockerTest); + this.setPreTestFunction(preTestCallback, preTestCallbackScope); + this.setCostFunction(costCallback, costCallbackScope); + this.setPerspectiveEnable(GetValue(o, 'perspective', false)); + this.setDebugGraphics(GetValue(o, 'debug.graphics', undefined)); + this.setDebugLineColor(GetValue(o, 'debug.visibleLineColor', 0x00ff00), GetValue(o, 'debug.invisibleLineColor', 0xff0000)); + this.setDebugLog(GetValue(o, 'debug.log', false)); + return this; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.debugGraphics = undefined; + this.chessData = undefined; + + super.shutdown(fromScene); + } + + setChess(gameObject) { + if (gameObject) { + this.chessData = GetChessData(gameObject); + if (this.parent !== gameObject) { + // Remove attatched event from previous gameObject + if (this.parent && this.parent.once) { + this.parent.off('destroy', this.onParentDestroy, this); + } + // Attach event + this.setParent(gameObject); + if (this.parent && this.parent.once) { + this.parent.once('destroy', this.onParentDestroy, this); + } + } + } else { + this.setParent(); + this.chessData = undefined; + } + return this; + } + + get face() { + return this._face; + } + + set face(direction) { + if (!this.chessData) { + if (this._face === undefined) { + this._face = 0; + } + return; + } + + direction = this.board.grid.directionNormalize(direction); + this._face = direction; + if (this.coneMode === 0) { // Direction + // Do nothing + } else { // Angle + var angle = this.board.angleToward(this.chessData.tileXYZ, direction); // -PI~PI + this.faceAngle = AngleNormalize(angle); // 0~2PI + } + } + + setFace(direction) { + this.face = direction; + return this; + } + + get cone() { + return this._cone; + } + + set cone(value) { + this._cone = value; + + if (value !== undefined) { + if (this.coneMode === 0) { // Direction + } else { // Angle + this.coneRad = DegToRad(value); + } + } + } + + setConeMode(mode) { + if (typeof (mode) === 'string') { + mode = CONEMODE[mode]; + } + this.coneMode = mode; + return this; + } + + setCone(value) { + this.cone = value; + return this; + } + + setOccupiedTest(enable) { + if (enable === undefined) { + enable = true; + } + this.occupiedTest = enable; + return this; + } + + setBlockerTest(enable) { + if (enable === undefined) { + enable = true; + } + this.blockerTest = enable; + return this; + } + + setEdgeBlockerTest(enable) { + if (enable === undefined) { + enable = true; + } + this.edgeBlockerTest = enable; + return this; + } + + setCostFunction(callback, scope) { + this.costCallback = callback; + this.costCallbackScope = scope; + return this; + } + + setPreTestFunction(callback, scope) { + this.preTestCallback = callback; + this.preTestCallbackScope = scope; + return this; + } + + setPerspectiveEnable(enable) { + if (enable === undefined) { + enable = true; + } + + this.perspectiveEnable = enable; + return this; + } + + setDebugGraphics(graphics) { + this.debugGraphics = graphics; + return this; + } + + setDebugLineColor(visibleLineColor, invisibleLineColor) { + this.debugVisibleLineColor = visibleLineColor; + this.debugInvisibleLineColor = invisibleLineColor; + return this; + } + + setDebugLog(enabled) { + if (enabled === undefined) { + enabled = true; + } + this.debugLog = enabled; + return this; + } + + clearDebugGraphics() { + if (this.debugGraphics) { + this.debugGraphics.clear(); + } + return this; + } + + get BLOCKER() { + return BLOCKER; + } + + get INFINITY() { + return INFINITY; + } + + get board() { + return this.chessData.board; + } +} + +const CONEMODE = { + direction: 0, + angle: 1, +}; + +Object.assign( + FieldOfView.prototype, + Methods +); + +export default FieldOfView; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/FindFOV.js b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/FindFOV.js new file mode 100644 index 000000000..6a12f3427 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/FindFOV.js @@ -0,0 +1,63 @@ +import IsArray from '../../utils/object/IsArray.js'; +import IsPlainObject from '../../utils/object/IsPlainObject.js'; + +var FindFOV = function (visiblePoints, originTileXY, out) { + if (IsPlainObject(visiblePoints)) { + out = originTileXY; + originTileXY = visiblePoints; + visiblePoints = undefined; + } else if (IsArray(visiblePoints)) { + out = visiblePoints; + originTileXY = undefined; + visiblePoints = undefined; + } + if (IsArray(originTileXY)) { + out = originTileXY; + originTileXY = undefined; + } + + if (out === undefined) { + out = []; + } + + var board = this.board; + var myTileXYZ = this.chessData.tileXYZ, + targetTileXY; + var isAnyVisible, hasAnyTestingTileXY; + var radius = 1; + + while (true) { + isAnyVisible = false; + hasAnyTestingTileXY = false; + board.ringToTileXYArray(myTileXYZ, radius, globRing); + for (var i = 0, cnt = globRing.length; i < cnt; i++) { + targetTileXY = globRing[i]; + if (!board.contains(targetTileXY.x, targetTileXY.y)) { + continue; + } + hasAnyTestingTileXY = true; + if (this.isInLOS(targetTileXY, visiblePoints, originTileXY)) { + isAnyVisible = true; + out.push(targetTileXY); + } + } + radius++; + globRing.length = 0; + + if (!this.perspectiveEnable && !isAnyVisible) { + if (!isAnyVisible) { + break; + } + } else { + if (!hasAnyTestingTileXY) { + break; + } + } + } + + return out; +} + +var globRing = []; + +export default FindFOV; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/GetCost.js b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/GetCost.js new file mode 100644 index 000000000..6ba1f4b00 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/GetCost.js @@ -0,0 +1,11 @@ +var GetCost = function (curTileXY, tileXYArray) { + if (typeof (this.costCallback) === 'number') { + return this.costCallback; + } + if (this.costCallbackScope) { + return this.costCallback.call(this.costCallbackScope, curTileXY, this, tileXYArray); + } else { + return this.costCallback(curTileXY, this, tileXYArray); + } +} +export default GetCost; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/IsInCone.js b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/IsInCone.js new file mode 100644 index 000000000..1b4d72616 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/IsInCone.js @@ -0,0 +1,13 @@ +var IsInCone = function (targetTileXY) { + if (this.cone === undefined) { + return true; + } + var board = this.board; + var myTileXYZ = this.chessData.tileXYZ; + if (this.coneMode === 0) { // Direction + return board.isDirectionInCone(myTileXYZ, targetTileXY, this.face, this.cone); + } else { // Angle + return board.isAngleInCone(myTileXYZ, targetTileXY, this.faceAngle, this.coneRad); + } +} +export default IsInCone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/IsInLOS.js b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/IsInLOS.js new file mode 100644 index 000000000..37f10474a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/IsInLOS.js @@ -0,0 +1,98 @@ +import CONST from './const.js'; +import AngleBetween from '../../utils/math/angle/Between.js'; +import AreTileXYArrayEqual from '../utils/AreTileXYArrayEqual.js'; + +const INFINITY = CONST.INFINITY; +const LINEOFFSET = 0.001; + +var IsInLOS = function (chess, visiblePoints, originTileXY) { + // chess: chess object or tileXY + if ((visiblePoints !== INFINITY) && (visiblePoints <= 0)) { + return false; + } + + var board = this.board; + var targetTileXY = board.chessToTileXYZ(chess); + if (!this.isInCone(targetTileXY)) { + return false; + } + + if (originTileXY === undefined) { + originTileXY = this.chessData.tileXYZ; + } + if (this.debugLog) { + console.log('Visible test from (' + originTileXY.x + ',' + originTileXY.y + ') to (' + targetTileXY.x + ',' + targetTileXY.y + ')'); + } + + if (!globTileXYArray0) { + globTileXYArray0 = []; + globTileXYArray1 = []; + } + + var out = board.tileXYToWorldXY(originTileXY.x, originTileXY.y, true); + var startX = out.x, + startY = out.y; + out = board.tileXYToWorldXY(targetTileXY.x, targetTileXY.y, true); + var endX = out.x, + endY = out.y; + var lineAngle = AngleBetween(startX, startY, endX, endY), + offsetX, offsetY, isVisivle; + + // Shift a small distance + lineAngle += (Math.PI / 2); + offsetX = LINEOFFSET * Math.cos(lineAngle); + offsetY = LINEOFFSET * Math.sin(lineAngle); + var x0 = startX + offsetX, + y0 = startY + offsetY, + x1 = endX + offsetX, + y1 = endY + offsetY; + board.lineToTileXYArray(x0, y0, x1, y1, globTileXYArray0); + if (this.debugLog) { + console.log('Line 0: ' + JSON.stringify(globTileXYArray0)); + } + isVisivle = this.isPathVisible(globTileXYArray0, visiblePoints); + if (isVisivle) { + globTileXYArray0.length = 0; + DrawLine( + this.debugGraphics, + this.debugVisibleLineColor, + startX, startY, endX, endY + ); + return true; + } + + // Shift a small distance + lineAngle += Math.PI; + offsetX = LINEOFFSET * Math.cos(lineAngle); + offsetY = LINEOFFSET * Math.sin(lineAngle); + var x0 = startX + offsetX, + y0 = startY + offsetY, + x1 = endX + offsetX, + y1 = endY + offsetY; + board.lineToTileXYArray(x0, y0, x1, y1, globTileXYArray1); + if (this.debugLog) { + console.log('Line 1: ' + JSON.stringify(globTileXYArray1)); + } + // No need do visible checking if path is the same as previous one + if (!AreTileXYArrayEqual(globTileXYArray0, globTileXYArray1)) { + isVisivle = this.isPathVisible(globTileXYArray1, visiblePoints); + } + globTileXYArray0.length = 0; + globTileXYArray1.length = 0; + DrawLine( + this.debugGraphics, + ((isVisivle) ? this.debugVisibleLineColor : this.debugInvisibleLineColor), + startX, startY, endX, endY + ); + return isVisivle; +} + +var DrawLine = function (graphics, color, startX, startY, endX, endY) { + if (graphics && (color !== undefined)) { + graphics.lineStyle(1, color, 1).lineBetween(startX, startY, endX, endY); + } +} + +var globTileXYArray0, + globTileXYArray1; +export default IsInLOS; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/IsPathVisible.js b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/IsPathVisible.js new file mode 100644 index 000000000..3ec26f7cd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/IsPathVisible.js @@ -0,0 +1,42 @@ +import CONST from './const.js'; +import AreTileXYEqual from '../utils/AreTileXYEqual.js'; + +const BLOCKER = CONST.BLOCKER; +const INFINITY = CONST.INFINITY; + +var IsPathVisible = function (tileXYArray, visiblePoints) { + if (this.preTest(tileXYArray, visiblePoints) === false) { + return false; + } + + if (this.costCallback === undefined) { + return true; + } + var myTileXYZ = this.chessData.tileXYZ; + var tileXY, cost, behindBlocker = false; + for (var i = 1, cnt = tileXYArray.length; i < cnt; i++) { + tileXY = tileXYArray[i]; + if (AreTileXYEqual(myTileXYZ, tileXY)) { + continue; + } + + if (behindBlocker) { + return false; + } + + cost = this.getCost(tileXY, tileXYArray); + if (cost === BLOCKER) { + behindBlocker = true; + continue; + } + + if (visiblePoints !== INFINITY) { + visiblePoints -= cost; + if (visiblePoints < 0) { + return false; + } + } + } + return true; +} +export default IsPathVisible; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/LOS.js b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/LOS.js new file mode 100644 index 000000000..da30d16e6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/LOS.js @@ -0,0 +1,40 @@ +import IsArray from '../../utils/object/IsArray.js'; +import IsPlainObject from '../../utils/object/IsPlainObject.js'; + +var LOS = function (chessArray, visiblePoints, originTileXY, out) { + // chessArray: array of chess object or tileXY + if (!IsArray(chessArray)) { + var chess = chessArray; + return this.isInLOS(chess, visiblePoints, originTileXY); + } else { + if (IsPlainObject(visiblePoints)) { + out = originTileXY; + originTileXY = visiblePoints; + visiblePoints = undefined; + } else if (IsArray(visiblePoints)) { + out = visiblePoints; + visiblePoints = undefined; + originTileXY = undefined; + } + if (IsArray(originTileXY)) { + out = originTileXY; + originTileXY = undefined; + } + + if (out === undefined) { + out = []; + } + + var chess; + for (var i = 0, cnt = chessArray.length; i < cnt; i++) { + chess = chessArray[i]; + if (!this.isInLOS(chess, visiblePoints, originTileXY)) { + continue; + } + out.push(chess) + } + return out; + } +} + +export default LOS; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/Methods.js b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/Methods.js new file mode 100644 index 000000000..d02480a83 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/Methods.js @@ -0,0 +1,17 @@ +import PreTest from './PreTest.js'; +import GetCost from './GetCost.js'; +import IsInCone from './IsInCone.js'; +import IsPathVisible from './IsPathVisible.js'; +import IsInLOS from './IsInLOS.js'; +import LOS from './LOS.js'; +import FindFOV from './FindFOV.js'; + +export default { + preTest: PreTest, + getCost: GetCost, + isInCone: IsInCone, + isPathVisible: IsPathVisible, + isInLOS: IsInLOS, + LOS: LOS, + findFOV: FindFOV, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/PreTest.js b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/PreTest.js new file mode 100644 index 000000000..6d69254a7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/PreTest.js @@ -0,0 +1,36 @@ +var PreTest = function (tileXYArray, visiblePoints) { + if (this.occupiedTest || this.blockerTest || this.edgeBlockerTest) { + var myTileZ = this.chessData.tileXYZ.z; + var tileXY; + for (var i = 1, cnt = tileXYArray.length; i < cnt; i++) { + tileXY = tileXYArray[i]; + // Occupied test + if (this.occupiedTest) { + if (this.board.contains(tileXY.x, tileXY.y, myTileZ)) { + return false; + } + } + // Blocker test + if (this.blockerTest) { + if (this.board.hasBlocker(tileXY.x, tileXY.y)) { + return false; + } + } + // Edge-blocker test + if (this.edgeBlockerTest) { + // TODO + } + } + } + + if (this.preTestCallback) { + if (this.preTestCallbackScope) { + return this.preTestCallback.call(this.preTestCallbackScope, tileXYArray, visiblePoints, this); + } else { + return this.preTestCallback(tileXYArray, visiblePoints, this); + } + } + + return true; +} +export default PreTest; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/const.js b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/const.js new file mode 100644 index 000000000..9bcee74f7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/fieldofview/const.js @@ -0,0 +1,7 @@ +export default { + // special cost + 'BLOCKER': null, + + // special moving point + 'INFINITY': undefined, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Factory.d.ts new file mode 100644 index 000000000..e252a074e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Factory.d.ts @@ -0,0 +1,5 @@ +import Hexagon from './Hexagon'; + +export default function ( + config?: Hexagon.IConfig +): Hexagon; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Factory.js b/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Factory.js new file mode 100644 index 000000000..02998d80b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Factory.js @@ -0,0 +1,8 @@ +import Hexagon from './Hexagon.js'; +import ObjectFactory from '../../ObjectFactory.js'; + +ObjectFactory.register('hexagonGrid', function (config) { + return new Hexagon(config); +}); + +export default Hexagon; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/GetBounds.js b/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/GetBounds.js new file mode 100644 index 000000000..1f463afc7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/GetBounds.js @@ -0,0 +1,21 @@ +import Rectangle from '../../../utils/geom/rectangle/Rectangle.js'; + +var GetBounds = function (tileX, tileY, out) { + if (out === undefined) { + out = new Rectangle; + } else if (out === true) { + out = globalBounds; + } + + var worldXY = this.getWorldXY(tileX, tileY, true); + out.x = worldXY.x - (this.width * 0.5); + out.y = worldXY.y - (this.height * 0.5); + out.width = this.width; + out.height = this.height; + + return out; +} + +var globalBounds = new Rectangle(); + +export default GetBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/GetGridPoints.js b/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/GetGridPoints.js new file mode 100644 index 000000000..8dba9d18a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/GetGridPoints.js @@ -0,0 +1,33 @@ +import SetPoints from '../../../geom/hexagon/SetPoints.js'; +import InitPoints from '../../../geom/utils/InitPoints.js'; + +var GetGridPoints = function (tileX, tileY, points) { + if (points === undefined) { + points = InitPoints(6); + } else if (points === true) { + points = globPoints; + } + + if (tileX === undefined) { + globWorldXY.x = 0; + globWorldXY.y = 0; + } else { + this.getWorldXY(tileX, tileY, globWorldXY); + } + var size; + if (this.size !== undefined) { + size = this.size; + } else { + size = globSize; + size.width = this.width; + size.height = this.height; + } + SetPoints(globWorldXY.x, globWorldXY.y, size, this.staggeraxis, points); + return points; +} + +var globPoints = InitPoints(6); +var globWorldXY = {}; +var globSize = {}; + +export default GetGridPoints; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Hexagon.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Hexagon.d.ts new file mode 100644 index 000000000..69db2fb26 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Hexagon.d.ts @@ -0,0 +1,72 @@ +import { WorldXYType, TileXYType } from '../../types/Position'; +import Rectangle from '../../../utils/geom/rectangle/Rectangle'; + +export default Hexagon; + +declare namespace Hexagon { + + type HexagonGridStaggerAxisTypes = 0 | 1 | 'y' | 'x'; + type HexagonGridStaggerindexTypes = 0 | 1 | 'even' | 'odd'; + + interface IConfig { + x?: number, y?: number, + size?: number, + cellWidth?: number, cellHeight?: number, + + staggeraxis?: HexagonGridStaggerAxisTypes, + staggerindex?: HexagonGridStaggerindexTypes + } + +} + +declare class Hexagon { + constructor(config?: Hexagon.IConfig); + + setOriginPosition( + worldX: number, + worldY: number + ): this; + x: number; + y: number; + + setCellSize( + width: number, + height: number + ): this; + width: number; + height: number; + setCellRadius(size: number): this; + readonly size: number; + + setType( + staggeraxis: Hexagon.HexagonGridStaggerAxisTypes, + staggerindex: Hexagon.HexagonGridStaggerindexTypes + ): this; + readonly staggeraxis: number; + readonly staggerindex: number; + readonly mode: number; + + getWorldXY( + tileX: number, + tileY: number, + out?: WorldXYType | true + ): WorldXYType; + + getTileXY( + worldX: number, + worldY: number, + out?: TileXYType | true + ): TileXYType; + + getGridPoints( + tileX: number, + tileY: number, + points?: WorldXYType[] + ): WorldXYType[]; + + getBounds( + tileX: number, + tileY: number, + out?: Rectangle + ): Rectangle; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Hexagon.js b/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Hexagon.js new file mode 100644 index 000000000..a1e809ba1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/hexagon/Hexagon.js @@ -0,0 +1,70 @@ +import Hexagon from '../../../utils/grid/hexagon/Hexagon.js'; +import SaveOrigin from '../utils/SaveOrigin.js'; +import RestoreOrigin from '../utils/RestoreOrigin.js'; +import GetTileXYAtDirection from '../../../utils/grid/hexagon/GetTileXYAtDirection.js'; +import GetNeighborTileXY from '../../../utils/grid/hexagon/GetNeighborTileXY.js'; +import GetNeighborTileDirection from '../../../utils/grid/hexagon/GetNeighborTileDirection.js'; +import GetOppositeDirection from '../../../utils/grid/hexagon/GetOppositeDirection.js'; +import Offset from '../../../utils/grid/hexagon/Offset.js'; +import Mirror from '../../../utils/grid/hexagon/Mirror.js'; +import Rotate from '../../../utils/grid/hexagon/Rotate.js'; +import GetDistance from '../../../utils/grid/hexagon/GetDistance.js'; +import DirectionBetween from '../../../utils/grid/hexagon/DirectionBetween.js'; +import DirectionNormalize from '../utils/DirectionNormalize.js'; +import GetGridPoints from './GetGridPoints.js'; +import GetBounds from './GetBounds.js'; +import RingToTileXYArray from '../../../utils/grid/hexagon/RingToTileXYArray.js'; + +class HexagonGrid extends Hexagon { + constructor(config) { + super(config); + this.sides = 6; + } + + // resetFromJSON(o) { + // super.resetFromJSON(o); + // } + + // Direction of neighbors + get allDirections() { + return ALLDIR; + } + + // Board-match + get halfDirections() { + return HALFDIR; + } + + // setOriginPosition + // setCellSize + // setType + // getWorldXY + // getTileXY +} + +const ALLDIR = [0, 1, 2, 3, 4, 5]; +const HALFDIR = [0, 1, 2]; + +var methods = { + saveOrigin: SaveOrigin, + restoreOrigin: RestoreOrigin, + getTileXYAtDirection: GetTileXYAtDirection, + getNeighborTileXY: GetNeighborTileXY, + getNeighborTileDirection: GetNeighborTileDirection, + getOppositeDirection: GetOppositeDirection, + offset: Offset, + mirror: Mirror, + rotate: Rotate, + getDistance: GetDistance, + directionBetween: DirectionBetween, + directionNormalize: DirectionNormalize, + getGridPoints: GetGridPoints, + getBounds: GetBounds, + ringToTileXYArray: RingToTileXYArray, +} +Object.assign( + HexagonGrid.prototype, + methods +); + +export default HexagonGrid; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/index.js b/ui/src/phaser3-rex-plugins/plugins/board/grid/index.js new file mode 100644 index 000000000..5d781f8cb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/index.js @@ -0,0 +1,7 @@ +import QuadGrid from './quad/Quad.js'; +import HexagonGrid from './hexagon/Hexagon.js'; + +export default { + quadGrid: QuadGrid, + hexagonGrid: HexagonGrid +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Factory.d.ts new file mode 100644 index 000000000..5dd771499 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Factory.d.ts @@ -0,0 +1,5 @@ +import Quad from './Quad'; + +export default function ( + config?: Quad.IConfig +): Quad; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Factory.js b/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Factory.js new file mode 100644 index 000000000..fc1d23b2a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Factory.js @@ -0,0 +1,8 @@ +import Quad from './Quad.js'; +import ObjectFactory from '../../ObjectFactory.js'; + +ObjectFactory.register('quadGrid', function (config) { + return new Quad(config); +}); + +export default Quad; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/GetBounds.js b/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/GetBounds.js new file mode 100644 index 000000000..1f463afc7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/GetBounds.js @@ -0,0 +1,21 @@ +import Rectangle from '../../../utils/geom/rectangle/Rectangle.js'; + +var GetBounds = function (tileX, tileY, out) { + if (out === undefined) { + out = new Rectangle; + } else if (out === true) { + out = globalBounds; + } + + var worldXY = this.getWorldXY(tileX, tileY, true); + out.x = worldXY.x - (this.width * 0.5); + out.y = worldXY.y - (this.height * 0.5); + out.width = this.width; + out.height = this.height; + + return out; +} + +var globalBounds = new Rectangle(); + +export default GetBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/GetGridPoints.js b/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/GetGridPoints.js new file mode 100644 index 000000000..ca256f749 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/GetGridPoints.js @@ -0,0 +1,25 @@ +import SetPoints from '../../../geom/quad/SetPoints.js'; +import InitPoints from '../../../geom/utils/InitPoints.js'; + +var GetGridPoints = function (tileX, tileY, points) { + if (points === undefined) { + points = InitPoints(4); + } else if (points === true) { + points = globPoints; + } + + if (tileX === undefined) { + globWorldXY.x = 0; + globWorldXY.y = 0; + } else { + this.getWorldXY(tileX, tileY, globWorldXY); + } + var quadType = (this.mode === 0) ? 0 : 1; + SetPoints(globWorldXY.x, globWorldXY.y, this.width, this.height, quadType, points); + return points; +} + +var globWorldXY = {}; +var globPoints = InitPoints(4); + +export default GetGridPoints; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Quad.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Quad.d.ts new file mode 100644 index 000000000..9364dcc16 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Quad.d.ts @@ -0,0 +1,67 @@ +import { WorldXYType, TileXYType } from '../../types/Position'; +import Rectangle from '../../../utils/geom/rectangle/Rectangle'; + +export default Quad; + +declare namespace Quad { + + type QuadGridTypes = 0 | 1 | 'orthogonal' | 'isometric'; + type QuadGridDirTypes = 4 | 8 | '4dir' | '8dir'; + + interface IConfig { + x?: number, y?: number, + cellWidth?: number, cellHeight?: number, + + type?: QuadGridTypes, + + dir?: QuadGridDirTypes + } + +} + +declare class Quad { + constructor(config?: Quad.IConfig); + + setOriginPosition( + worldX: number, + worldY: number + ): this; + x: number; + y: number; + + setCellSize( + width: number, + height: number + ): this; + width: number; + height: number; + + setType( + type: Quad.QuadGridTypes + ): this; + readonly mode: number; + + getWorldXY( + tileX: number, + tileY: number, + out?: WorldXYType | true + ): WorldXYType; + + getTileXY( + worldX: number, + worldY: number, + out?: TileXYType | true + ): TileXYType; + + getGridPoints( + tileX: number, + tileY: number, + points?: WorldXYType[] + ): WorldXYType[]; + + getBounds( + tileX: number, + tileY: number, + out?: Rectangle + ): Rectangle; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Quad.js b/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Quad.js new file mode 100644 index 000000000..c0e6c66df --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/quad/Quad.js @@ -0,0 +1,73 @@ +import Quad from '../../../utils/grid/quad/Quad.js'; +import SaveOrigin from '../utils/SaveOrigin.js'; +import RestoreOrigin from '../utils/RestoreOrigin.js'; +import GetTileXYAtDirection from '../../../utils/grid/quad/GetTileXYAtDirection.js'; +import GetNeighborTileXY from '../../../utils/grid/quad/GetNeighborTileXY.js'; +import GetNeighborTileDirection from '../../../utils/grid/quad/GetNeighborTileDirection.js'; +import GetOppositeDirection from '../../../utils/grid/quad/GetOppositeDirection.js'; +import Offset from '../../../utils/grid/quad/Offset.js'; +import Mirror from '../../../utils/grid/quad/Mirror.js'; +import Rotate from '../../../utils/grid/quad/Rotate.js'; +import GetDistance from '../../../utils/grid/quad/GetDistance.js'; +import DirectionBetween from '../../../utils/grid/quad/DirectionBetween.js'; +import DirectionNormalize from '../utils/DirectionNormalize.js'; +import GetGridPoints from './GetGridPoints.js'; +import GetBounds from './GetBounds.js'; +import RingToTileXYArray from '../../../utils/grid/quad/RingToTileXYArray.js'; + +class QuadGrid extends Quad { + constructor(config) { + super(config); + this.sides = 4; + } + + // resetFromJSON(o) { + // super.resetFromJSON(o); + // } + + // Direction of neighbors + get allDirections() { + return (this.directions === 4) ? ALLDIR4 : ALLDIR8; + } + + // Board-match + get halfDirections() { + return (this.directions === 4) ? HALFDIR4 : HALFDIR8; + } + + // setOriginPosition + // setCellSize + // setType + // getWorldXY + // getTileXY + // getGridPolygon +} + +const ALLDIR4 = [0, 1, 2, 3]; +const ALLDIR8 = [0, 1, 2, 3, 4, 5, 6, 7]; +const HALFDIR4 = [0, 1]; +const HALFDIR8 = [0, 1, 4, 5]; + +var methods = { + saveOrigin: SaveOrigin, + restoreOrigin: RestoreOrigin, + getTileXYAtDirection: GetTileXYAtDirection, + getNeighborTileXY: GetNeighborTileXY, + getNeighborTileDirection: GetNeighborTileDirection, + getOppositeDirection: GetOppositeDirection, + offset: Offset, + mirror: Mirror, + rotate: Rotate, + getDistance: GetDistance, + directionBetween: DirectionBetween, + directionNormalize: DirectionNormalize, + getGridPoints: GetGridPoints, + getBounds: GetBounds, + ringToTileXYArray: RingToTileXYArray, +} +Object.assign( + QuadGrid.prototype, + methods +); + +export default QuadGrid; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/utils/DirectionNormalize.js b/ui/src/phaser3-rex-plugins/plugins/board/grid/utils/DirectionNormalize.js new file mode 100644 index 000000000..c82ea09b9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/utils/DirectionNormalize.js @@ -0,0 +1,7 @@ +import Wrap from '../../../utils/math/Wrap.js'; + +var DirectionNormalize = function (direction) { + return Wrap(direction, 0, this.directions); +} + +export default DirectionNormalize; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/utils/RestoreOrigin.js b/ui/src/phaser3-rex-plugins/plugins/board/grid/utils/RestoreOrigin.js new file mode 100644 index 000000000..a3d2c1bc2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/utils/RestoreOrigin.js @@ -0,0 +1,6 @@ +var RestoreOrigin = function () { + this.x = this._savedOriginX; + this.y = this._savedOriginY; + return this; +} +export default RestoreOrigin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/grid/utils/SaveOrigin.js b/ui/src/phaser3-rex-plugins/plugins/board/grid/utils/SaveOrigin.js new file mode 100644 index 000000000..60b4b6f04 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/grid/utils/SaveOrigin.js @@ -0,0 +1,6 @@ +var SaveOrigin = function () { + this._savedOriginX = this.x; + this._savedOriginY = this.y; + return this; +} +export default SaveOrigin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetHexagonMap.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetHexagonMap.d.ts new file mode 100644 index 000000000..fd96e1626 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetHexagonMap.d.ts @@ -0,0 +1,8 @@ +import { TileXYType } from '../types/Position'; +import Board from '../board/LogicBoard'; + +export default function GetHexagonMap( + board: Board, + radius: number, + out?: TileXYType[] +): TileXYType[]; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetHexagonMap.js b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetHexagonMap.js new file mode 100644 index 000000000..20f8e99d6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetHexagonMap.js @@ -0,0 +1,21 @@ +import { + cube2cr +} from '../../utils/grid/hexagon/CubeTransfer.js'; + +var GetHexagonMap = function (board, radius, out) { + if (out === undefined) { + out = []; + } + var mode = board.grid.mode; + var r1, r2; + for (var q = -radius; q <= radius; q++) { + r1 = Math.max(-radius, -q - radius); + r2 = Math.min(radius, -q + radius); + for (var r = r1; r <= r2; r++) { + out.push(cube2cr(mode, q, r, -q - r)); + } + } + + return out; +} +export default GetHexagonMap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetParallelogramMap.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetParallelogramMap.d.ts new file mode 100644 index 000000000..253e26ce6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetParallelogramMap.d.ts @@ -0,0 +1,10 @@ +import { TileXYType } from '../types/Position'; +import Board from '../board/LogicBoard'; + +export default function GetParallelogramMap( + board: Board, + type: 0 | 1 | 2, + width: number, + height: number, + out?: TileXYType[] +): TileXYType[]; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetParallelogramMap.js b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetParallelogramMap.js new file mode 100644 index 000000000..b8ed091de --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetParallelogramMap.js @@ -0,0 +1,36 @@ +import { + cube2cr +} from '../../utils/grid/hexagon/CubeTransfer.js'; + +var GetParallelogramMap = function (board, type, width, height, out) { + if (out === undefined) { + out = []; + } + var mode = board.grid.mode; + switch (type) { + case 1: + for (var s = 0; s <= width; s++) { + for (var q = 0; q <= height; q++) { + out.push(cube2cr(mode, q, -q - s, s)); + } + } + break; + case 2: + for (var r = 0; r <= width; r++) { + for (var s = 0; s <= height; s++) { + out.push(cube2cr(mode, -r - s, r, s)); + } + } + break; + default: // case 0 + for (var q = 0; q <= width; q++) { + for (var r = 0; r <= height; r++) { + out.push(cube2cr(mode, q, r, -q - r)); + } + } + break; + } + + return out; +} +export default GetParallelogramMap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetTriangleMap.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetTriangleMap.d.ts new file mode 100644 index 000000000..026dd7f24 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetTriangleMap.d.ts @@ -0,0 +1,9 @@ +import { TileXYType } from '../types/Position'; +import Board from '../board/LogicBoard'; + +export default function GetTriangleMap( + board: Board, + type: 0 | 1, + height: number, + out?: TileXYType[] +): TileXYType[]; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetTriangleMap.js b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetTriangleMap.js new file mode 100644 index 000000000..638c3de1d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/GetTriangleMap.js @@ -0,0 +1,28 @@ +import { + cube2cr +} from '../../utils/grid/hexagon/CubeTransfer.js'; + +var GetTriangleMap = function (board, type, height, out) { + if (out === undefined) { + out = []; + } + var mode = board.grid.mode; + var rStart, rEnd + for (var q = 0; q <= height; q++) { + if (type === 1) { + rStart = height - q; + rEnd = height; + } else { + rStart = 0; + rEnd = height - q; + } + + for (var r = rStart; r <= rEnd; r++) { + out.push(cube2cr(mode, q, r, -q - r)); + } + } + + return out; +} + +export default GetTriangleMap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/index.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/index.d.ts new file mode 100644 index 000000000..7d08bfd59 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/index.d.ts @@ -0,0 +1,11 @@ +import GetHexagonMap from './GetHexagonMap'; +import GetTriangleMap from './GetTriangleMap'; +import GetParallelogramMap from './GetParallelogramMap'; + +type Methods = { + hexagon: typeof GetHexagonMap, + triangle: typeof GetTriangleMap, + parallelogram: typeof GetParallelogramMap +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/index.js b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/index.js new file mode 100644 index 000000000..86325deda --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/hexagonmap/index.js @@ -0,0 +1,9 @@ +import GetHexagonMap from './GetHexagonMap.js'; +import GetTriangleMap from './GetTriangleMap.js'; +import GetParallelogramMap from './GetParallelogramMap.js'; + +export default { + hexagon: GetHexagonMap, + triangle: GetTriangleMap, + parallelogram: GetParallelogramMap, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/match/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/match/Factory.d.ts new file mode 100644 index 000000000..b2c8c09db --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/match/Factory.d.ts @@ -0,0 +1,5 @@ +import Match from './Match'; + +export default function ( + config?: Match.IConfig +): Match; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/match/Factory.js b/ui/src/phaser3-rex-plugins/plugins/board/match/Factory.js new file mode 100644 index 000000000..479224bf9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/match/Factory.js @@ -0,0 +1,11 @@ +import Match from './Match.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../utils/object/SetValue.js'; + +ObjectFactory.register('match', function (config) { + return new Match(config); +}); + +SetValue(window, 'RexPlugins.Board.Match', Match); + +export default Match; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/match/Group.js b/ui/src/phaser3-rex-plugins/plugins/board/match/Group.js new file mode 100644 index 000000000..8931ea6c1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/match/Group.js @@ -0,0 +1,83 @@ +import Clear from '../../utils/object/Clear.js'; +import IsPlainObject from '../../utils/object/IsPlainObject.js'; +import IsArray from '../../utils/object/IsArray.js'; + +var Group = function (startTileX, startTileY, out) { + if (out === undefined) { + out = []; + } + + var board = this.board; + var wildcard = this.wildcard; + var targetSymbol = this.getSymbol(startTileX, startTileY); + if ((targetSymbol == null) || (targetSymbol === wildcard)) { + return out; + } + + if (globalQueue === undefined) { + globalQueue = new Queue(); + } + + var curTileXY, symbol; + globalQueue.push(startTileX, startTileY); + while (globalQueue.length) { + curTileXY = globalQueue.pop(); + symbol = this.getSymbol(curTileXY.x, curTileXY.y); + if ((symbol === targetSymbol) || (symbol === wildcard)) { + out.push(curTileXY); + globalQueue.push(board.getNeighborTileXY(curTileXY)); + } + } + + globalQueue.clear(); + return out; +} + +class Queue { + constructor() { + this.data = []; + this.visited = {}; + } + + push(x, y) { + if (IsArray(x)) { + var xyArray = x; + for (var i = 0, cnt = xyArray.length; i < cnt; i++) { + this.push(xyArray[i]); + } + return this; + } + + if (IsPlainObject(x)) { + var xy = x; + x = xy.x; + y = xy.y; + } + var key = `${x},${y}`; + if (this.visited.hasOwnProperty(key)) { + return this; + } + + this.data.push({ x: x, y: y }); + this.visited[key] = true; + return this; + } + + pop() { + return this.data.pop(); + } + + get length() { + return this.data.length; + } + + clear() { + Clear(this.data); + Clear(this.visited); + return this; + } +} + +var globalQueue; + +export default Group; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/match/Match.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/match/Match.d.ts new file mode 100644 index 000000000..a69f224fe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/match/Match.d.ts @@ -0,0 +1,109 @@ +import Board from '../board/LogicBoard'; +import { TileXYType } from '../types/Position'; + + +export default Match; + +declare namespace Match { + + interface IConfig { + board?: Board, + wildcard?: string | number, + dirMask?: { [dir: number]: boolean }, + } + + type MatchResultType = { + tileXY: TileXYType[], + direction: number, + + pattern: string | number | + (string | number)[] + } + +} + +declare class Match { + constructor(config?: Match.IConfig); + + setBoard(board: Board): this; + readonly board: Board; + + refreshSymbols( + callback: ( + tileXY: TileXYType, + board: Board + ) => string | number | null, + scope?: object + ): this; + + setSymbol( + tileX: number, + tileY: number, + symbol: string | number | null + ): this; + + getSymbol( + tileX: number, + tileY: number + ): string | number | null; + + forEach( + callback: ( + tileXY: TileXYType, + symbol: string | number | null, + board: Board + ) => void | boolean, + scope?: object + ): this; + + setWildcard( + symbol: string | number + ): this; + wildcard: string | number; + + setDirMask( + dir: number, + value: boolean + ): this; + + match( + n: number, + callback: ( + result: { + tileXY: TileXYType[], + direction: number, + pattern: string | number + }, + board: Board + ) => void | boolean, + scope?: object + ): this; + + + match( + n: (string | number)[], + callback: ( + result: { + tileXY: TileXYType[], + direction: number, + pattern: (string | number)[] + }, + board: Board + ) => void | boolean, + scope?: object + ): this; + + anyMatch( + n: number + ): boolean; + + anyMatch( + n: (string | number)[] + ): boolean; + + group( + startTileX: number, + startTileY: number, + out?: TileXYType[] + ): TileXYType[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/match/Match.js b/ui/src/phaser3-rex-plugins/plugins/board/match/Match.js new file mode 100644 index 000000000..320529654 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/match/Match.js @@ -0,0 +1,167 @@ +import Methods from './Methods.js'; +import IsFunction from '../../utils/object/IsFunction.js'; +import GetValue from '../../utils/object/GetValue.js'; +import IsPlainObject from '../../utils/object/IsPlainObject.js'; + +class Match { + constructor(config) { + this.symbols = []; // tileX+(tileY*board.width) + this.dirMask = {}; + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.setBoard(GetValue(o, 'board', undefined)); + this.setWildcard(GetValue(o, 'wildcard', undefined)); + + var dirMask = GetValue(o, 'dirMask', undefined); + if (dirMask !== undefined) { + this.setDirMask(dirMask); + } + return this; + } + + boot() { } + + shutdown() { + this.board = undefined; + this.symbols = undefined; + this.dirMask = undefined; + return this; + } + + destroy() { + this.shutdown(); + return this; + } + + setBoard(board) { + this.board = board; + if (board) { + this.clearSymbols(); + } + return this; + } + + setDirMask(dir, value) { + if (IsPlainObject(dir)) { + var dirMask = dir; + for (dir in dirMask) { + this.dirMask[dir] = dirMask[dir]; + } + } else { + this.dirMask[dir] = value; + } + return this; + } + + setDirectionMode(mode) { + this.board.grid.setDirectionMode(mode); + return this; + } + + clearSymbols() { + this.refreshSymbols(null); + return this; + } + + setSymbol(tileX, tileY, symbol) { + var board = this.board; + if (!board.contains(tileX, tileY)) { + return this; + } + + this.symbols[this.tileXYToKey(tileX, tileY)] = symbol; + return this; + } + + getSymbol(tileX, tileY) { + return this.symbols[this.tileXYToKey(tileX, tileY)]; + } + + forEach(callback, scope) { + var board = this.board; + var tileXY, symbol; + var isBreak; + for (var i = 0, cnt = this.symbols.length; i < cnt; i++) { + symbol = this.symbols[i]; + tileXY = this.keyToTileXY(i); + if (scope) { + isBreak = callback.call(scope, tileXY, symbol, board); + } else { + isBreak = callback(tileXY, symbol, board); + } + if (isBreak) { + break; + } + } + return this; + } + + refreshSymbols(callback, scope) { + var board = this.board; + var width = board.width, + height = board.height; + this.symbols.length = width * height; + + var symbol, tileXY; + if (IsFunction(callback)) { + // Get symbol by callback + for (var i = 0, cnt = this.symbols.length; i < cnt; i++) { + tileXY = this.keyToTileXY(i, true); + if (scope) { + symbol = callback.call(scope, tileXY, board); + } else { + symbol = callback(tileXY, board); + } + this.symbols[i] = symbol; + } + + } else { + // Fill a given symbol + symbol = callback; + for (var i = 0, cnt = this.symbols.length; i < cnt; i++) { + this.symbols[i] = symbol; + } + } + return this; + } + + setWildcard(symbol) { + this.wildcard = symbol; + return this; + } + + tileXYToKey(tileX, tileY) { + return tileX + (tileY * this.board.width); + } + + keyToTileXY(key, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXY; + } + var width = this.board.width; + out.x = key % width; + out.y = Math.floor(key / width); + return out; + } + + anyMatch(pattern) { + return this.match(pattern, null, null, true); + } +} + +var globTileXY = { + x: 0, + y: 0 +}; + +Object.assign( + Match.prototype, + Methods +); + +export default Match; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/match/MatchAll.js b/ui/src/phaser3-rex-plugins/plugins/board/match/MatchAll.js new file mode 100644 index 000000000..8ea34483e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/match/MatchAll.js @@ -0,0 +1,47 @@ +var MatchBoard = function (pattern, callback, scope, getFirst) { + // pattern: pattern list or repeat count + var board = this.board, + grid = board.grid; + var directions = grid.halfDirections, + dir, + dirMask = this.dirMask; + var width = board.width, + height = board.height; + var result, isBreak; + for (var i = 0, cnt = directions.length; i < cnt; i++) { + dir = directions[i]; + if (dirMask[dir] === false) { + continue; + } + + for (var tileY = 0; tileY < height; tileY++) { + for (var tileX = 0; tileX < width; tileX++) { + result = this.matchAtDir(pattern, tileX, tileY, dir); + if (result === false) { + continue; + } + + if (callback) { + if (scope) { + isBreak = callback.call(scope, result, board); + } else { + isBreak = callback(result, board); + } + } + if (getFirst) { + return result; + } + + if (isBreak) { + break; + } + } + + if (isBreak) { + break; + } + } + } + return this; +} +export default MatchBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/match/MatchAtDir.js b/ui/src/phaser3-rex-plugins/plugins/board/match/MatchAtDir.js new file mode 100644 index 000000000..a1791d1ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/match/MatchAtDir.js @@ -0,0 +1,63 @@ +var MatchAtDir = function (pattern, startTileX, startTileY, direction) { + // pattern: pattern list or repeat count + var matchNMode = typeof (pattern) === 'number'; + var patternLength; + if (matchNMode) { + patternLength = pattern; + pattern = null; + } else { + patternLength = pattern.length; + } + + var symbol, wildcard = this.wildcard; + var curTileXY; + var board = this.board; + var matchedTileXY = result.tileXY; + matchedTileXY.length = 0; + for (var i = 0; i < patternLength; i++) { + if (curTileXY === undefined) { + curTileXY = { + x: startTileX, + y: startTileY + }; + } else { + // get next tileXY + curTileXY = board.getNeighborTileXY(curTileXY, direction, curTileXY); + if (curTileXY === null) { + return false; + } + } + + symbol = this.getSymbol(curTileXY.x, curTileXY.y); + if (symbol == null) { + return false; + } + if (symbol !== wildcard) { + if (matchNMode) { + if (pattern === null) { + pattern = symbol; + } else if (pattern !== symbol) { + return false; + } + } else if (pattern[i] !== symbol) { // pattern list mode + return false; + } + } + + matchedTileXY.push({ + x: curTileXY.x, + y: curTileXY.y + }); + } + + result.direction = direction; + result.pattern = pattern; + return result; +}; + +var result = { + tileXY: [], + direction: undefined, + pattern: undefined +}; +export default MatchAtDir; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/match/Methods.js b/ui/src/phaser3-rex-plugins/plugins/board/match/Methods.js new file mode 100644 index 000000000..789bf705d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/match/Methods.js @@ -0,0 +1,9 @@ +import MatchAll from './MatchAll.js'; +import MatchAtDir from './MatchAtDir.js'; +import Group from './Group.js'; + +export default { + match: MatchAll, + matchAtDir: MatchAtDir, + group: Group +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/Factory.d.ts new file mode 100644 index 000000000..3d317edb0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/Factory.d.ts @@ -0,0 +1,6 @@ +import MiniBoard from './MiniBoard'; + +export default function ( + x: number, y: number, + config?: MiniBoard.IConfig +): MiniBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/Factory.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/Factory.js new file mode 100644 index 000000000..02ac3a27d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/Factory.js @@ -0,0 +1,13 @@ +import MiniBoard from './MiniBoard.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../utils/object/SetValue.js'; + +ObjectFactory.register('miniBoard', function (x, y, config) { + var gameObject = new MiniBoard(this.scene, x, y, config); + this.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Board.MiniBoard', MiniBoard); + +export default MiniBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/IsMiniBoardObject.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/IsMiniBoardObject.js new file mode 100644 index 000000000..8a7f8c017 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/IsMiniBoardObject.js @@ -0,0 +1,5 @@ +var IsMiniBoardObject = function (object) { + return (object.type === 'rexMiniBoard'); +}; + +export default IsMiniBoardObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/Methods.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/Methods.js new file mode 100644 index 000000000..a6dcd37af --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/Methods.js @@ -0,0 +1,49 @@ +import AddChess from './chess/AddChess.js'; +import RemoveChess from './chess/RemoveChess.js'; +import RemoveAllChess from './chess/RemoveAllChess.js'; + +import SetMainBoard from './mainboard/SetMainboard.js'; +import CanPutOnMainBoard from './mainboard/CanPutOnMainBoard.js'; +import PutOnMainBoard from './mainboard/PutOnMainBoard.js'; +import PullOutFromMainBoard from './mainboard/PullOutFromMainBoard.js'; +import PutBack from './mainboard/PutBack.js'; +import IsOverlapping from './mainboard/IsOverlapping.js'; +import AlignToMainBoard from './mainboard/AlignToMainBoard.js'; + +import SetInteractive from './input/SetInteractive.js'; +import SetDraggable from './input/SetDraggable.js'; +import DragEnd from './input/DragEnd.js'; + +import CanMirror from './transform/CanMirror.js'; +import Mirror from './transform/Mirror.js'; +import CanRotate from './transform/CanRotate.js'; +import Rotate from './transform/Rotate.js'; +import CanRotateTo from './transform/CanRotateTo.js'; +import RotateTo from './transform/RotateTo.js'; +import SetOrigin from './transform/SetOrigin.js'; + +export default { + addChess: AddChess, + removeChess: RemoveChess, + removeAllChess: RemoveAllChess, + + pullOutFromMainBoard: PullOutFromMainBoard, + canPutOnMainBoard: CanPutOnMainBoard, + putOnMainBoard: PutOnMainBoard, + putBack: PutBack, + isOverlapping: IsOverlapping, + alignToMainBoard: AlignToMainBoard, + + setInteractive: SetInteractive, + setDraggable: SetDraggable, + dragEnd: DragEnd, + + setMainBoard: SetMainBoard, + canMirror: CanMirror, + mirror: Mirror, + canRotate: CanRotate, + rotate: Rotate, + canRotateTo: CanRotateTo, + rotateTo: RotateTo, + setOrigin: SetOrigin +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/MiniBoard.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/MiniBoard.d.ts new file mode 100644 index 000000000..057740627 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/MiniBoard.d.ts @@ -0,0 +1,166 @@ +import Container from '../../gameobjects/container/containerlite/ContainerLite'; +import Quad from '../grid/quad/Quad'; +import Hexagon from '../grid/hexagon/Hexagon'; +import { TileXYZType } from '../types/Position'; +import Board from '../board/Board'; + +export default MiniBoard; + +declare namespace MiniBoard { + + type PutTestCallbackType = ( + targetTileXY: TileXYZType, + mainBoard: Board, + chess: Phaser.GameObjects.GameObject + ) => boolean; + + interface IConfig { + grid: Quad | Hexagon, + + draggable?: boolean, + face?: number, + + putTestCallback?: PutTestCallbackType, + putTestCallbackScpe?: unknown, + } + + type MirrorModeType = 0 | 1 | 3 | 'x' | 'y' | 'x&y'; + + type TileXYZMapType = { [uid: number]: TileXYZType }; + + namespace Events { + type PointerDownCallbackType = ( + pointer: Phaser.Input.Pointer, + miniBoard: MiniBoard + ) => void; + + type ChessDownCallbackType = ( + pointer: Phaser.Input.Pointer, + gameObject: Phaser.GameObjects.GameObject + ) => void; + + type PointerUpCallbackType = ( + pointer: Phaser.Input.Pointer, + miniBoard: MiniBoard + ) => void; + + type ChessUpCallbackType = ( + pointer: Phaser.Input.Pointer, + gameObject: Phaser.GameObjects.GameObject + ) => void; + + type PointerMoveCallbackType = ( + pointer: Phaser.Input.Pointer, + miniBoard: MiniBoard + ) => void; + + type ChessMoveCallbackType = ( + pointer: Phaser.Input.Pointer, + gameObject: Phaser.GameObjects.GameObject + ) => void; + + type DragCallbackType = ( + pointer: Phaser.Input.Pointer, + dragX: number, dragY: number + ) => void; + } +} + +declare class MiniBoard extends Container { + constructor( + scene: Phaser.Scene, + x: number, y: number, + config?: MiniBoard.IConfig + ); + + setFace(direction: number): this; + face: number; + + addChess( + chess: Phaser.GameObjects.GameObject, + tileX: number, + tileY: number, + tileZ: number | string + ): this; + + removeChess( + chess: Phaser.GameObjects.GameObject, + tileX?: null, + tileY?: null, + tileZ?: null, + destroy?: boolean + ): this; + removeChess( + chess: null, + tileX: number, + tileY: number, + tileZ: number | string, + destroy?: boolean + ): this; + + removeAllChess( + destroy?: boolean + ): this; + + setOrigin( + originX: number, + originY?: number + ): this; + setOrigin( + origin: 'center' | 'top-left' | 'left-top' + ): this; + + setPutTestCallback( + callback: MiniBoard.PutTestCallbackType, + scope?: object + ): this; + + canPutOnMainBoard( + mainBoard: Board, + tileX?: number, + tileY?: number, + chessTileXYMap?: MiniBoard.TileXYZMapType, + ): boolean; + putOnMainBoard( + mainBoard: Board, + tileX?: number, + tileY?: number, + align?: boolean + ): this; + pullOutFromMainBoard(): this; + putBack(): this; + + isOverlapping( + mainBoard: Board + ): boolean; + + alignToMainBoard( + mainBoard: Board, + tileX?: number, + tileY?: number + ): this; + + readonly mainBoard: Board; + readonly tileX: number; + readonly tileY: number; + readonly grid: Quad | Hexagon; + + canRotate(n: number): boolean; + canRotateTo(direction: number): boolean; + rotate(n: number): this; + rotateTo(direction: number): this; + + canMirror( + mode: MiniBoard.MirrorModeType + ): boolean; + mirror( + mode: MiniBoard.MirrorModeType + ): this; + + readonly lastTransferResult: boolean; + + setInteractive(enable?: boolean): this; + + setDragEnable(enable?: boolean): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/MiniBoard.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/MiniBoard.js new file mode 100644 index 000000000..250ef5e2a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/MiniBoard.js @@ -0,0 +1,85 @@ +import Container from '../../gameobjects/container/containerlite/ContainerLite.js'; +import Methods from './Methods.js'; +import Board from '../board/LogicBoard.js'; +import MainBoardReference from './mainboard/MainBoardReference.js'; +import GetValue from '../../utils/object/GetValue.js'; + +class MiniBoard extends Container { + constructor(scene, x, y, config) { + super(scene, x, y, 0, 0); + this.type = 'rexMiniBoard'; + var boardConfig = { + isBoard: false, + grid: GetValue(config, 'grid', undefined), + infinity: true, + wrap: false + } + this.board = new Board(scene, boardConfig); + this.mainBoardRef = new MainBoardReference(); + this.lastMainBoardRef = new MainBoardReference(); + + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.setFace(GetValue(o, 'face', 0)); + var dragEnable = GetValue(o, 'draggable', undefined); + if (dragEnable !== undefined) { + this.setDraggable(dragEnable); + } + this.setPutTestCallback(GetValue(o, 'putTestCallback', undefined), GetValue(o, 'putTestCallbackScpe', undefined)); + this.lastTransferResult = GetValue(o, 'lastTransferResult', undefined); + return this; + } + + destroy(fromScene) { + if (!this.scene) { + return + } + + this.clear(!fromScene); + this.board.shutdown(fromScene); + this.board = undefined; + this.setPutTestCallback(undefined, undefined); + + super.destroy(fromScene); + } + + setFace(direction) { + this.face = this.board.grid.directionNormalize(direction); + return this; + } + + get mainBoard() { + return this.mainBoardRef.mainBoard; + } + + get tileX() { + return this.mainBoardRef.tileX; + } + + get tileY() { + return this.mainBoardRef.tileY; + } + + get grid() { + return this.board.grid; + } + + get tileXYZMap() { + return this.board.boardData.UIDToXYZ; // {uid:{x,y,z}} + } + + setPutTestCallback(callback, scope) { + this.putTestCallback = callback; + this.putTestCallbackScpe = scope; + return this; + } +} + +Object.assign( + MiniBoard.prototype, + Methods +); + +export default MiniBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/chess/AddChess.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/chess/AddChess.js new file mode 100644 index 000000000..2a500b0fb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/chess/AddChess.js @@ -0,0 +1,20 @@ +import IsUID from '../../chess/IsUID.js'; + +var AddChess = function (gameObject, tileX, tileY, tileZ) { + var grid = this.grid; + grid.saveOrigin(); + grid.setOriginPosition(this.x, this.y); + + // Add chess to borad + this.board.addChess(gameObject, tileX, tileY, tileZ, true); + // Add chess to container + if (IsUID(gameObject)) { + gameObject = this.board.uidToChess(gameObject); + } + this.add(gameObject); + + grid.restoreOrigin(); + return this; +} + +export default AddChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/chess/RemoveAllChess.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/chess/RemoveAllChess.js new file mode 100644 index 000000000..a196a5445 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/chess/RemoveAllChess.js @@ -0,0 +1,6 @@ +var RemoveAllChess = function (destroy) { + this.board.removeAllChess(destroy); + return this; +} + +export default RemoveAllChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/chess/RemoveChess.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/chess/RemoveChess.js new file mode 100644 index 000000000..8d454a304 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/chess/RemoveChess.js @@ -0,0 +1,6 @@ +var RemoveChess = function (gameObject, tileX, tileY, tileZ, destroy) { + this.board.removeChess(gameObject, tileX, tileY, tileZ, destroy); + return this; +} + +export default RemoveChess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/DragEnd.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/DragEnd.js new file mode 100644 index 000000000..cb69a238e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/DragEnd.js @@ -0,0 +1,18 @@ +var DragEnd = function (pointer) { + var dragData = this.input.drag; + // Not dragging + if (dragData.state === 0) { + return; + } + + if (pointer === undefined) { + pointer = this.input.pointer; + } + var dragPosition = dragData.position; + var dragX = pointer.x - dragPosition.x; + var dragY = pointer.y - dragPosition.y; + dragData.state = 0; + this.emit('dragend', pointer, dragX, dragY); + return this; +} +export default DragEnd; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/OnPointerDown.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/OnPointerDown.js new file mode 100644 index 000000000..672a6b1e4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/OnPointerDown.js @@ -0,0 +1,66 @@ +var OnPointerDown = function (pointer) { + if (!this.input.enable) { + return; + } + if (!pointer.isDown) { + return; + } + + if (this.input.pointer === null) { // Catch new touch pointer + this.input.pointer = pointer; + } + + var hitChess = OnTouchTileStart.call(this, pointer); + if (hitChess) { + OnDragStart.call(this, pointer); + } +} + +var OnTouchTileStart = function (pointer) { + // Get touched tileX, tileY + var grid = this.grid; + grid.saveOrigin(); + grid.setOriginPosition(this.x, this.y); + var out = this.board.worldXYToTileXY(pointer.x, pointer.y, true); + var tileX = out.x, + tileY = out.y; + grid.restoreOrigin(); + this.input.tilePosition.x = tileX; + this.input.tilePosition.y = tileY; + + // Get touched chess + var gameObjects = this.board.tileXYToChessArray(tileX, tileY, globChessArray); + var hitChess = (gameObjects.length > 0); + if (hitChess) { + // Fire events + var gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + gameObject = gameObjects[i]; + if (gameObject.emit) { + gameObject.emit('miniboard.pointerdown', pointer); + } + this.emit('gameobjectdown', pointer, gameObject); + } + this.emit('pointerdown', pointer, this); + } + globChessArray.length = 0; + return hitChess; +} + +var OnDragStart = function (pointer) { + var dragData = this.input.drag; + // Drag by another pointer + if (dragData.state === 1) { + return; + } + + var dragPosition = dragData.position; + dragPosition.x = pointer.x - this.x; + dragPosition.y = pointer.y - this.y; + dragData.state = 1; + this.emit('dragstart', pointer, dragPosition.x, dragPosition.y); +} + +var globChessArray = []; + +export default OnPointerDown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/OnPointerMove.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/OnPointerMove.js new file mode 100644 index 000000000..a36c3b288 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/OnPointerMove.js @@ -0,0 +1,76 @@ +var OnPointerMove = function (pointer) { + if (!this.input.enable) { + return; + } + + OnTouchTileMove.call(this, pointer); + OnDrag.call(this, pointer); +} + +var OnTouchTileMove = function (pointer) { + // Get touched tileX, tileY + var grid = this.grid; + grid.saveOrigin(); + grid.setOriginPosition(this.x, this.y); + var out = this.board.worldXYToTileXY(pointer.x, pointer.y, true); + var tileX = out.x, + tileY = out.y; + grid.restoreOrigin(); + + if ((this.input.tilePosition.x === tileX) && (this.input.tilePosition.y === tileY)) { + // Tile position dose not change + return; + } + this.input.tilePosition.x = tileX; + this.input.tilePosition.y = tileY; + + // Get touched chess + var gameObjects = this.board.tileXYToChessArray(tileX, tileY, globChessArray); + var hitChess = (gameObjects.length > 0); + if (hitChess) { + // Fire events + var gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + gameObject = gameObjects[i]; + if (gameObject.emit) { + gameObject.emit('miniboard.pointermove', pointer); + } + this.emit('gameobjectmove', pointer, gameObject); + } + this.emit('pointermove', pointer, this); + } else { + // Move outside + if (this.input.pointer === pointer) { // Release touch pointer + this.input.pointer = null; + } + } + globChessArray.length = 0; + + // Not dragging + if (this.input.drag.state === 0) { + if (this.input.pointer === pointer) { + if (!hitChess) { + this.input.pointer = null; // Release touch pointer + } + } else if (this.input.pointer === null) { + this.input.pointer = pointer; // Catch new touch pointer + } + } +} + +var OnDrag = function (pointer) { + var dragData = this.input.drag; + // Not dragging + if (dragData.state === 0) { + return; + } + + var dragPosition = dragData.position; + var dragX = pointer.x - dragPosition.x; + var dragY = pointer.y - dragPosition.y; + this.emit('drag', pointer, dragX, dragY); +} + +var globChessArray = []; + +export default OnPointerMove; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/OnPointerUp.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/OnPointerUp.js new file mode 100644 index 000000000..a4d50dacb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/OnPointerUp.js @@ -0,0 +1,49 @@ +import OnDragEnd from './DragEnd.js'; + +var OnPointerUp = function (pointer) { + if (!this.input.enable) { + return; + } + + OnTouchTileEnd.call(this, pointer); + OnDragEnd.call(this, pointer); + + if (this.input.pointer === pointer) { // Release touch pointer + this.input.pointer = null; + } +} + +var OnTouchTileEnd = function (pointer) { + // Get touched tileX, tileY + var grid = this.grid; + grid.saveOrigin(); + grid.setOriginPosition(this.x, this.y); + var out = this.board.worldXYToTileXY(pointer.x, pointer.y, true); + var tileX = out.x, + tileY = out.y; + grid.restoreOrigin(); + this.input.tilePosition.x = tileX; + this.input.tilePosition.y = tileY; + + // Get touched chess + var gameObjects = this.board.tileXYToChessArray(tileX, tileY, globChessArray); + var hitChess = (gameObjects.length > 0); + if (hitChess) { + // Fire events + var gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + gameObject = gameObjects[i]; + if (gameObject.emit) { + gameObject.emit('miniboard.pointerup', pointer); + } + this.emit('gameobjectup', pointer, gameObject); + } + this.emit('pointerup', pointer, this); + } + globChessArray.length = 0; + return hitChess; +} + +var globChessArray = []; + +export default OnPointerUp; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/SetDraggable.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/SetDraggable.js new file mode 100644 index 000000000..72617124f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/SetDraggable.js @@ -0,0 +1,12 @@ +var SetDraggable = function (enable) { + if (enable === undefined) { + enable = true; + } + this.setInteractive(); + this.input.drag.enable = enable; + if (!enable) { + this.input.drag.state = 0; + } + return this; +} +export default SetDraggable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/SetInteractive.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/SetInteractive.js new file mode 100644 index 000000000..aeb50b1a1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/input/SetInteractive.js @@ -0,0 +1,46 @@ +import OnPointerDown from './OnPointerDown.js'; +import OnPointerUp from './OnPointerUp.js'; +import OnPointerMove from './OnPointerMove.js'; + +var SetInteractive = function (enable) { + if (enable === undefined) { + enable = true; + } + if (!this.input) { + this.input = { + enable: true, + tilePosition: { + x: undefined, + y: undefined + }, + pointer: undefined, + drag: { + enable: false, + state: 0, + position: { + x: undefined, + y: undefined + } + } + }; + this.scene.input.on('pointerdown', OnPointerDown, this); + this.scene.input.on('pointerup', OnPointerUp, this); + this.scene.input.on('pointermove', OnPointerMove, this); + + this.once('destroy', function () { + if (this.scene) { + this.scene.input.off('pointerdown', OnPointerDown, this); + this.scene.input.off('pointerup', OnPointerUp, this); + this.scene.input.off('pointermove', OnPointerMove, this); + } + }, this); + } + + this.input.enable = enable; + if (!enable) { + this.input.pointer = null; + } + return this; +}; + +export default SetInteractive; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/AlignToMainBoard.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/AlignToMainBoard.js new file mode 100644 index 000000000..84ed761dd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/AlignToMainBoard.js @@ -0,0 +1,15 @@ +var AlignToMainBoard = function (mainBoard, tileX, tileY) { + if (!mainBoard) { + return this; + } + + if (tileX === undefined) { + var out = mainBoard.worldXYToTileXY(this.x, this.y, true); + tileX = out.x; + tileY = out.y; + } + mainBoard.gridAlign(this, tileX, tileY); + return this; +} + +export default AlignToMainBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/CanPutOnMainBoard.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/CanPutOnMainBoard.js new file mode 100644 index 000000000..31dec274e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/CanPutOnMainBoard.js @@ -0,0 +1,41 @@ +var CanPutOnMainBoard = function (mainBoard, tileX, tileY, chessTileXYMap) { + if (!mainBoard) { + return false; + } + if (chessTileXYMap === undefined) { + chessTileXYMap = this.tileXYZMap; // {uid:{x,y,z}} + } + + var chessTileXYZ, mappedTileXY, isOccupied; + for (var uid in chessTileXYMap) { + chessTileXYZ = chessTileXYMap[uid]; + mappedTileXY = mainBoard.offset(chessTileXYZ, tileX, tileY, true); + if (!mainBoard.contains(mappedTileXY.x, mappedTileXY.y)) { + return false; + } + + if (this.putTestCallback) { + // Custom test function + targetTileXY.x = mappedTileXY.x; + targetTileXY.y = mappedTileXY.x; + targetTileXY.z = chessTileXYZ.z; + var chess = this.board.uidToChess(uid); + if (this.putTestCallbackScpe) { + isOccupied = this.putTestCallback.call(this.putTestCallbackScpe, targetTileXY, mainBoard, chess); + } else { + isOccupied = this.putTestCallback(targetTileXY, mainBoard, chess); + } + } else { + // Default test function + isOccupied = mainBoard.contains(mappedTileXY.x, mappedTileXY.y, chessTileXYZ.z); + } + if (isOccupied) { + return false; + } + } + return true; +} + +var targetTileXY = { x: 0, y: 0, z: 0, }; + +export default CanPutOnMainBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/IsOverlapping.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/IsOverlapping.js new file mode 100644 index 000000000..3df9788d0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/IsOverlapping.js @@ -0,0 +1,15 @@ +var IsOverlapping = function (mainBoard, tileZ) { + if (!mainBoard) { + return false; + } + + var gameObject; + for (var uid in this.tileXYZMap) { + gameObject = this.board.uidToChess(uid); + if (mainBoard.isOverlappingPoint(gameObject.x, gameObject.y, tileZ)) { + return true; + } + } + return false; +} +export default IsOverlapping; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/MainBoardReference.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/MainBoardReference.js new file mode 100644 index 000000000..7fb4c0adf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/MainBoardReference.js @@ -0,0 +1,17 @@ +class MainBoardReference { + constructor(miniBoard) { + this.miniBoard = miniBoard; + this.set(null); + } + set(mainBoard, tileX, tileY) { + if (!mainBoard) { + mainBoard = null; + tileX = null; + tileY = null; + } + this.mainBoard = mainBoard; + this.tileX = tileX; + this.tileY = tileY; + } +} +export default MainBoardReference; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/PullOutFromMainBoard.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/PullOutFromMainBoard.js new file mode 100644 index 000000000..56156ac55 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/PullOutFromMainBoard.js @@ -0,0 +1,14 @@ +var PullOutFromMainBoard = function () { + var mainBoard = this.mainBoard; + if (mainBoard === null) { + return this; + } + + var tileXYZMap = this.tileXYZMap; // {uid:{x,y,z}} + for (var uid in tileXYZMap) { + mainBoard.removeChess(parseInt(uid)); + } + this.setMainBoard(null); + return this; +} +export default PullOutFromMainBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/PutBack.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/PutBack.js new file mode 100644 index 000000000..eb112de82 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/PutBack.js @@ -0,0 +1,8 @@ +var PutBack = function () { + var mainBoard = this.lastMainBoardRef.mainBoard; + var tileX = this.lastMainBoardRef.tileX; + var tileY = this.lastMainBoardRef.tileY; + this.putOnMainBoard(mainBoard, tileX, tileY, false); + return this; +} +export default PutBack; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/PutOnMainBoard.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/PutOnMainBoard.js new file mode 100644 index 000000000..e24cef2dd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/PutOnMainBoard.js @@ -0,0 +1,36 @@ +var PutOnMainBoard = function (mainBoard, tileX, tileY, align) { + if (!mainBoard) { + return this; + } + + if (tileX === undefined) { + var out = mainBoard.worldXYToTileXY(this.x, this.y, true); + tileX = out.x; + tileY = out.y; + } + if (align === undefined) { + align = true; + } + + this.pullOutFromMainBoard(); + if (!this.canPutOnMainBoard(mainBoard, tileX, tileY)) { + return this; + } + + this.setMainBoard(mainBoard, tileX, tileY); + var tileXYZMap = this.tileXYZMap; // {uid:{x,y,z}} + var chessTileXYZ, mappedTileXY; + for (var uid in tileXYZMap) { + chessTileXYZ = tileXYZMap[uid]; + uid = parseInt(uid); + + mappedTileXY = mainBoard.offset(chessTileXYZ, tileX, tileY, true); + mainBoard.addChess(uid, mappedTileXY.x, mappedTileXY.y, chessTileXYZ.z, false); + } + if (align) { + this.alignToMainBoard(mainBoard, tileX, tileY); + } + + return this; +} +export default PutOnMainBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/SetMainboard.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/SetMainboard.js new file mode 100644 index 000000000..a9b192ea3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/mainboard/SetMainboard.js @@ -0,0 +1,8 @@ +var SetMainBoard = function (mainBoard, tileX, tileY) { + this.mainBoardRef.set(mainBoard, tileX, tileY); + if (mainBoard) { + this.lastMainBoardRef.set(mainBoard, tileX, tileY); + } + return this; +} +export default SetMainBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/CanMoveToTile.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/CanMoveToTile.js new file mode 100644 index 000000000..8f8f08533 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/CanMoveToTile.js @@ -0,0 +1,48 @@ +var CanMoveToTile = function (tileX, tileY, direction) { + var miniBoard = this.parent; + var mainBoard = miniBoard.mainBoard; + // Not on a mainBoard + if (mainBoard == null) { + return false; + } + + myTileXYZ.x = miniBoard.tileX; + myTileXYZ.y = miniBoard.tileY; + targetTileXYZ.x = tileX; + targetTileXYZ.y = tileY; + // Move to current position + if ((targetTileXYZ.x === myTileXYZ.x) && (targetTileXYZ.y === myTileXYZ.y)) { + return true; + } + + miniBoard.pullOutFromMainBoard(); + // Can not put on main board + if (!miniBoard.canPutOnMainBoard(mainBoard, targetTileXYZ.x, targetTileXYZ.y)) { + miniBoard.putBack(); + return false; + } + + // Custom moveable test + if (this.moveableTestCallback) { + if (direction === undefined) { + direction = mainBoard.getNeighborTileDirection(myTileXYZ, targetTileXYZ); + } + if (this.moveableTestScope) { + var moveable = this.moveableTestCallback.call(this.moveableTestScope, myTileXYZ, targetTileXYZ, direction, mainBoard); + } else { + var moveable = this.moveableTestCallback(myTileXYZ, targetTileXYZ, direction, mainBoard); + } + if (!moveable) { + miniBoard.putBack(); + return false; + } + } + + miniBoard.putBack(); + return true; +} + +var myTileXYZ = { x: 0, y: 0, z: 0 }; +var targetTileXYZ = { x: 0, y: 0, z: 0 }; + +export default CanMoveToTile; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveTo.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveTo.js new file mode 100644 index 000000000..27cef9e58 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveTo.js @@ -0,0 +1,135 @@ +import MoveToTask from '../../../behaviors/moveto/MoveTo.js'; +import TickTask from '../../../utils/componentbase/SceneUpdateTickTask.js'; + +import CanMoveToTile from './CanMoveToTile.js'; +import MoveToTile from './MoveToTile.js'; +import MoveToward from './MoveToward.js'; +import MoveToRandomNeighbor from './MoveToRandomNeighbor.js'; + +import GetValue from '../../../utils/object/GetValue.js'; + +class MoveTo extends TickTask { + constructor(miniBoard, config) { + super(miniBoard, config); + // this.parent = miniBoard; + + this.moveToTask = new MoveToTask(miniBoard, { tickingMode: 0 }); + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.isRunning = GetValue(o, 'isRunning', false); + this.setEnable(GetValue(o, 'enable', true)); + this.timeScale = GetValue(o, 'timeScale', 1); + this.setSpeed(GetValue(o, 'speed', 400)); + this.destinationTileX = GetValue(o, 'destinationTileX', null); + this.destinationTileY = GetValue(o, 'destinationTileY', null); + this.destinationDirection = GetValue(o, 'destinationDirection', null); + this.lastMoveResult = GetValue(o, 'lastMoveResult', undefined); + return this; + } + + toJSON() { + return { + isRunning: this.isRunning, + enable: this.enable, + timeScale: this.timeScale, + speed: this.speed, + moveableTest: this.moveableTestCallback, + moveableTestScope: this.moveableTestScope, + destinationTileX: this.destinationTileX, + destinationTileY: this.destinationTileY, + destinationDirection: this.destinationDirection, + lastMoveResult: this.lastMoveResult, + tickingMode: this.tickingMode + }; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.moveToTask.shutdown(fromScene); + super.shutdown(fromScene); + } + + set enable(value) { + this.moveToTask.setEnable(value); + } + + get enable() { + return this.moveToTask.enable; + } + + setEnable(e) { + if (e == undefined) { + e = true; + } + this.enable = e; + return this; + } + + get timeScale() { + return this.moveToTask.timeScale; + } + + set timeScale(value) { + this.moveToTask.timeScale = value; + } + + set speed(value) { + this.moveToTask.setSpeed(value); + } + + get speed() { + return this.moveToTask.speed; + } + + setSpeed(speed) { + this.speed = speed; + return this; + } + + moveAlongLine(startX, startY, endX, endY) { + if (startX !== undefined) { + this.parent.x = startX; + } + if (startY !== undefined) { + this.parent.y = startY; + } + this.moveToTask.moveTo(endX, endY); + return this; + }; + + update(time, delta) { + if ((!this.isRunning) || (!this.enable)) { + return this; + } + + var moveToTask = this.moveToTask; + moveToTask.update(time, delta); + if (!moveToTask.isRunning) { + this.complete(); + return this; + } + return this; + } +} + +var methods = { + canMoveTo: CanMoveToTile, + moveTo: MoveToTile, + moveToward: MoveToward, + moveToRandomNeighbor: MoveToRandomNeighbor, +}; +Object.assign( + MoveTo.prototype, + methods +); + + +export default MoveTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveToRandomNeighbor.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveToRandomNeighbor.js new file mode 100644 index 000000000..1010157c3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveToRandomNeighbor.js @@ -0,0 +1,28 @@ +import Clone from '../../../utils/object/Clone.js'; +import Shuffle from '../../../utils/array/Shuffle.js'; + +var MoveToRandomNeighbor = function () { + var miniBoard = this.parent; + var mainBoard = miniBoard.mainBoard; + // Not on a mainBoard + if (mainBoard == null) { + this.lastMoveResult = false; + return this; + } + + var directions = mainBoard.grid.allDirections; + if (globDirections.length !== directions.length) { + Clone(directions, globDirections); + } + Shuffle(globDirections); + for (var i = 0, cnt = globDirections.length; i < cnt; i++) { + this.moveToward(globDirections[i]); + if (this.lastMoveResult) { + return this; + } + } + return this; +} + +var globDirections = []; +export default MoveToRandomNeighbor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveToTile.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveToTile.js new file mode 100644 index 000000000..035fddeb6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveToTile.js @@ -0,0 +1,64 @@ +import GetValue from '../../../utils/object/GetValue.js'; + +var MoveToTile = function (tileX, tileY, direction) { + var miniBoard = this.parent; + var mainBoard = miniBoard.mainBoard; + // Not on a mainBoard + if (mainBoard == null) { + this.lastMoveResult = false; + return this; + } + + if ((tileX != null) && (typeof (tileX) !== 'number')) { + var config = tileX; + tileX = GetValue(config, 'x', undefined); + tileY = GetValue(config, 'y', undefined); + direction = GetValue(config, 'direction', undefined); + } + myTileXY.x = miniBoard.tileX; + myTileXY.y = miniBoard.tileY; + if ((direction !== undefined) && + (tileX == null) || (tileY == null)) { + // Get neighbor tile position if direction is not undefined + var out = mainBoard.getNeighborTileXY(myTileXY, direction, true); + if (out !== null) { + tileX = out.x; + tileY = out.y; + } else { + tileX = null; + tileY = null; + } + } + + // invalid tile position + if ((tileX == null) || (tileY == null)) { + this.lastMoveResult = false; + return this; + } + if (direction === undefined) { + targetTileXY.x = tileX; + targetTileXY.y = tileY; + direction = board.getNeighborTileDirection(myTileXY, targetTileXY); + } + if (!this.canMoveTo(tileX, tileY, direction)) { + this.lastMoveResult = false; + return this; + } + this.destinationTileX = tileX; + this.destinationTileY = tileY; + this.destinationDirection = direction; + + // Not support wrap mode + var out = mainBoard.tileXYToWorldXY(tileX, tileY, true); + this.moveToTask.moveTo(out.x, out.y); + miniBoard.putOnMainBoard(mainBoard, tileX, tileY, false); + + this.isRunning = true; + this.lastMoveResult = true; + return this; +} + +var myTileXY = {}; +var targetTileXY = {}; + +export default MoveToTile; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveToward.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveToward.js new file mode 100644 index 000000000..aadd49649 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/moveto/MoveToward.js @@ -0,0 +1,5 @@ +var MoveToward = function (direction) { + this.moveTo(undefined, undefined, direction); + return this; +} +export default MoveToward; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/CanMirror.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/CanMirror.js new file mode 100644 index 000000000..c7e4091a4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/CanMirror.js @@ -0,0 +1,9 @@ +import MirrorTransfer from './transferfunctions/Mirror.js'; +var CanMirror = function(mode) { + if (this.mainBoard === null) { + return true; + } + var newTileXYZMap = MirrorTransfer.call(this, mode); + return this.canPutOnMainBoard(this.mainBoard, tileX, tileY, newTileXYZMap); +} +export default CanMirror; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/CanRotate.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/CanRotate.js new file mode 100644 index 000000000..a4906e6ae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/CanRotate.js @@ -0,0 +1,10 @@ +import RotateTransfer from './transferfunctions/Rotate.js'; + +var CanRotate = function (direction) { + if (this.mainBoard === null) { + return true; + } + var newTileXYZMap = RotateTransfer.call(this, direction); + return this.canPutOnMainBoard(this.mainBoard, tileX, tileY, newTileXYZMap); +} +export default CanRotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/CanRotateTo.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/CanRotateTo.js new file mode 100644 index 000000000..e3fb3b6fb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/CanRotateTo.js @@ -0,0 +1,5 @@ +var CanRotateTo = function(direction) { + direction -= this.face; + return this.canRotate(direction); +} +export default CanRotateTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/Mirror.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/Mirror.js new file mode 100644 index 000000000..6a78ab8f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/Mirror.js @@ -0,0 +1,27 @@ +import MirrorTransfer from './transferfunctions/Mirror.js'; +import ResetChessTileXYZ from './ResetChessTileXYZ.js'; + +var Mirror = function (mode) { + var isOnMainBoard = (this.mainBoard != null); + if (isOnMainBoard) { + this.pullOutFromMainBoard(); + } + + var newTileXYZMap = MirrorTransfer.call(this, mode); + + if (isOnMainBoard) { + var mainBoard = this.lastMainBoardRef.mainBoard; + var tileX = this.lastMainBoardRef.tileX; + var tileY = this.lastMainBoardRef.tileY; + this.lastTransferResult = this.canPutOnMainBoard(mainBoard, tileX, tileY, newTileXYZMap); + if (this.lastTransferResult) { + ResetChessTileXYZ.call(this, newTileXYZMap); + } + this.putBack(); + } else { + this.lastTransferResult = true; + ResetChessTileXYZ.call(this, newTileXYZMap); + } + return this; +} +export default Mirror; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/ResetChessTileXYZ.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/ResetChessTileXYZ.js new file mode 100644 index 000000000..10ea0f4b8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/ResetChessTileXYZ.js @@ -0,0 +1,11 @@ +var ResetChessTileXYZ = function(newTileXYZMap) { + this.removeAllChess(); + var newTileXYZ; + for(var uid in newTileXYZMap) { + newTileXYZ = newTileXYZMap[uid]; + uid = parseInt(uid); + this.addChess(uid, newTileXYZ.x, newTileXYZ.y, newTileXYZ.z, false); + } + return this; +} +export default ResetChessTileXYZ; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/Rotate.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/Rotate.js new file mode 100644 index 000000000..1c9e98f89 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/Rotate.js @@ -0,0 +1,35 @@ +import RotateTransfer from './transferfunctions/Rotate.js'; +import ResetChessTileXYZ from './ResetChessTileXYZ.js'; + +var Rotate = function (direction) { + if (direction === 0) { + return this; + } + + var isOnMainBoard = (this.mainBoard != null); + if (isOnMainBoard) { + this.pullOutFromMainBoard(); + } + + var newTileXYZMap = RotateTransfer.call(this, direction); + + if (isOnMainBoard) { + var mainBoard = this.lastMainBoardRef.mainBoard; + var tileX = this.lastMainBoardRef.tileX; + var tileY = this.lastMainBoardRef.tileY; + this.lastTransferResult = this.canPutOnMainBoard(mainBoard, tileX, tileY, newTileXYZMap); + if (this.lastTransferResult) { + ResetChessTileXYZ.call(this, newTileXYZMap); + } + this.putBack(); + } else { + this.lastTransferResult = true; + ResetChessTileXYZ.call(this, newTileXYZMap); + } + + if (this.lastTransferResult) { + this.setFace(this.face + direction); + } + return this; +} +export default Rotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/RotateTo.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/RotateTo.js new file mode 100644 index 000000000..4b6e0ff8b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/RotateTo.js @@ -0,0 +1,6 @@ +var RotateTo = function (direction) { + direction -= this.face; + this.rotate(direction); + return this; +} +export default RotateTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/SetOrigin.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/SetOrigin.js new file mode 100644 index 000000000..9a81fae50 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/SetOrigin.js @@ -0,0 +1,41 @@ +import GetMinMaxTileXY from '../utils/GetMinMaxTileXY.js'; +import Linear from '../../../utils/math/Linear.js'; +import OffsetTransfer from './transferfunctions/Offset.js'; +import ResetChessTileXYZ from './ResetChessTileXYZ.js'; + +var SetOrigin = function (originX, originY) { + switch (originX) { + case 'center': + originX = 0.5; + originY = 0.5; + break; + case 'top-left': + case 'left-top': + originX = 0; + originY = 0; + break; + } + if (originX === undefined) { + originX = 0.5; + } + if (originY === undefined) { + originY = originX; + } + var minMaxTileXY = GetMinMaxTileXY.call(this, undefined, true); + var offsetX = -Math.floor(Linear(minMaxTileXY.minX, minMaxTileXY.maxX, originX)); + var offsetY = -Math.floor(Linear(minMaxTileXY.minY, minMaxTileXY.maxY, originY)); + + if ((offsetX !== 0) || (offsetY !== 0)) { + var newTileXYZMap = OffsetTransfer.call(this, offsetX, offsetY); + ResetChessTileXYZ.call(this, newTileXYZMap); + var worldOffsetXY = this.board.tileXYToWorldXY(offsetX, offsetY); + var world0 = this.board.tileXYToWorldXY(0, 0); + this.setPosition( + (this.x + (world0.x - worldOffsetXY.x)), + (this.y + (world0.y - worldOffsetXY.y)) + ); + } + return this; +} + +export default SetOrigin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/transferfunctions/Mirror.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/transferfunctions/Mirror.js new file mode 100644 index 000000000..d0840f988 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/transferfunctions/Mirror.js @@ -0,0 +1,28 @@ +var Mirror = function (mode, chessTileXYZMap, out) { + if (mode === undefined) { + mode = 1; + } else if (typeof (mode) === 'string') { + mode = MODE[mode]; + } + if (chessTileXYZMap === undefined) { + chessTileXYZMap = this.tileXYZMap; // {uid:{x,y,z}} + } + if (out === undefined) { + out = {}; + } + var chessTileXYZ, newTileXYZ; + for (var uid in chessTileXYZMap) { + chessTileXYZ = chessTileXYZMap[uid]; + newTileXYZ = this.board.mirror(chessTileXYZ, mode); + newTileXYZ.z = chessTileXYZ.z; + out[uid] = newTileXYZ; + } + return out; // {uid:{x,y,z}} +} + +const MODE = { + x: 1, + y: 2, + 'x&y': 3 +} +export default Mirror; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/transferfunctions/Offset.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/transferfunctions/Offset.js new file mode 100644 index 000000000..1e8e0cb9c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/transferfunctions/Offset.js @@ -0,0 +1,18 @@ +var Offset = function (tileX, tileY, chessTileXYZMap, out) { + if (chessTileXYZMap === undefined) { + chessTileXYZMap = this.tileXYZMap; // {uid:{x,y,z}} + } + if (out === undefined) { + out = {}; + } + var chessTileXYZ, newTileXYZ; + for (var uid in chessTileXYZMap) { + chessTileXYZ = chessTileXYZMap[uid]; + newTileXYZ = this.board.offset(chessTileXYZ, tileX, tileY); + newTileXYZ.z = chessTileXYZ.z; + out[uid] = newTileXYZ; + } + return out; // {uid:{x,y,z}} +} + +export default Offset; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/transferfunctions/Rotate.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/transferfunctions/Rotate.js new file mode 100644 index 000000000..088e6001e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/transform/transferfunctions/Rotate.js @@ -0,0 +1,21 @@ +var Rotate = function (direction, chessTileXYZMap, out) { + if (direction === undefined) { + direction = 0; + } + if (chessTileXYZMap === undefined) { + chessTileXYZMap = this.tileXYZMap; // {uid:{x,y,z}} + } + if (out === undefined) { + out = {}; + } + var chessTileXYZ, newTileXYZ; + for (var uid in chessTileXYZMap) { + chessTileXYZ = chessTileXYZMap[uid]; + newTileXYZ = this.board.rotate(chessTileXYZ, direction); + newTileXYZ.z = chessTileXYZ.z; + out[uid] = newTileXYZ; + } + return out; // {uid:{x,y,z}} +} + +export default Rotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/miniboard/utils/GetMinMaxTileXY.js b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/utils/GetMinMaxTileXY.js new file mode 100644 index 000000000..baae35404 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/miniboard/utils/GetMinMaxTileXY.js @@ -0,0 +1,37 @@ +var GetMinMaxTileXY = function (chessTileXYZMap, out) { + if (chessTileXYZMap === undefined) { + chessTileXYZMap = this.tileXYZMap; // {uid:{x,y,z}} + } + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globResult; + } + var minX = Infinity, maxX = -Infinity; + var minY = Infinity, maxY = -Infinity; + var chessTileXYZ; + for (var uid in this.tileXYZMap) { + chessTileXYZ = this.tileXYZMap[uid]; + if (chessTileXYZ.x < minX) { + minX = chessTileXYZ.x; + } + if (chessTileXYZ.x > maxX) { + maxX = chessTileXYZ.x; + } + if (chessTileXYZ.y < minY) { + minY = chessTileXYZ.y; + } + if (chessTileXYZ.y > maxY) { + maxY = chessTileXYZ.y; + } + } + out.minX = minX; + out.minY = minY; + out.maxX = maxX; + out.maxY = maxY; + return out; +} + +var globResult = {}; + +export default GetMinMaxTileXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Factory.d.ts new file mode 100644 index 000000000..91a9ddcb6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Factory.d.ts @@ -0,0 +1,6 @@ +import Monopoly from './Monopoly'; + +export default function ( + gameObject: Phaser.GameObjects.GameObject, + config?: Monopoly.IConfig +): Monopoly; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Factory.js b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Factory.js new file mode 100644 index 000000000..976627bea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Factory.js @@ -0,0 +1,11 @@ +import Monopoly from './Monopoly.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../utils/object/SetValue.js'; + +ObjectFactory.register('monopoly', function (gameObject, config) { + return new Monopoly(gameObject, config); +}); + +SetValue(window, 'RexPlugins.Board.Monopoly', Monopoly); + +export default Monopoly; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/monopoly/GetCost.js b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/GetCost.js new file mode 100644 index 000000000..4a05cc7db --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/GetCost.js @@ -0,0 +1,11 @@ +var GetCost = function (curTileXY, preTileXY) { + if (typeof (this.costCallback) === 'number') { + return this.costCallback; + } + if (this.costCallbackScope) { + return this.costCallback.call(this.costCallbackScope, curTileXY, preTileXY, this); + } else { + return this.costCallback(curTileXY, preTileXY, this); + } +} +export default GetCost; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/monopoly/GetNextTile.js b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/GetNextTile.js new file mode 100644 index 000000000..073f7ed55 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/GetNextTile.js @@ -0,0 +1,66 @@ +import { CreateTileData } from './TileData.js'; +import AreTileXYEqual from '../utils/AreTileXYEqual.js'; + +import GetRandom from '../../utils/array/GetRandom.js'; + +var GetNextTile = function (curTileData, preTileData) { + var board = this.board; + var directions = board.grid.allDirections; + var forwardTileData = null, + backwardTileData = null; + var neighborTileXArray = []; // forward and other neighbors, exclude backward + var neighborTileXY, neighborTileData = null; + for (var i = 0, cnt = directions.length; i < cnt; i++) { + neighborTileXY = board.getNeighborTileXY(curTileData, directions[i], true); + if (neighborTileXY === null) { + continue; + } + if (!board.contains(neighborTileXY.x, neighborTileXY.y, this.pathTileZ)) { + continue; + } + neighborTileData = CreateTileData(neighborTileXY.x, neighborTileXY.y, directions[i]); + + if (directions[i] === curTileData.direction) { + forwardTileData = neighborTileData; + } + if ((preTileData !== undefined) && (AreTileXYEqual(neighborTileXY, preTileData))) { + backwardTileData = neighborTileData; + } else { + neighborTileXArray.push(neighborTileData); + } + } + + var nextTileData; + if ((backwardTileData === null) && (neighborTileXArray.length === 0)) { + // no valid neighbor + nextTileData = null; + } else if ((backwardTileData === null) && (neighborTileXArray.length === 1)) { + // 1 neighbor + nextTileData = neighborTileXArray[0]; + } else if ((backwardTileData !== null) && (neighborTileXArray.length === 0)) { + // 1 backward neighbor + nextTileData = backwardTileData; + } else { + // 2 or more neighobrs + switch (this.pickMode) { + case 1: // random all + if (backwardTileData !== null) { + neighborTileXArray.push(backwardTileData); + } + nextTileData = GetRandom(neighborTileXArray); + break; + + default: // case 0: forward first + if (forwardTileData !== null) { + nextTileData = forwardTileData; + } else { + nextTileData = GetRandom(neighborTileXArray); + } + break; + } + } + + return nextTileData; +} + +export default GetNextTile; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/monopoly/GetPath.js b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/GetPath.js new file mode 100644 index 000000000..0e26fbb4a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/GetPath.js @@ -0,0 +1,47 @@ +import { CreateTileData } from './TileData.js'; +import CONST from './const.js'; + +const STOP = CONST.STOP; + +var GetPath = function (movingPoints, out) { + if (out === undefined) { + out = []; + } + if (this.board === null) { // chess is not in board + return out; + } + var curTileXYZ = this.chessData.tileXYZ, + curTileData = CreateTileData(curTileXYZ.x, curTileXYZ.y, this.face), + nextTileData; + var cost; + while (movingPoints > 0) { + nextTileData = this.getNextTile(curTileData, this.preTileXY); + if (nextTileData === null) { + break; + } + cost = this.getCost(nextTileData, curTileData); + if (cost === STOP) { + cost = movingPoints; + } + nextTileData.cost = cost; + if (movingPoints >= cost) { + out.push(nextTileData); + } + movingPoints -= cost; + + this.preTileXY = curTileData; + curTileData = nextTileData; + } + + // remove cost = 0 at tail + for (var i = out.length - 1; i >= 0; i--) { + if (out[i].cost === 0) { + out.length = i; + } else { + break; + } + } + return out; +} + +export default GetPath; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Methods.js b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Methods.js new file mode 100644 index 000000000..5cd7f94cc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Methods.js @@ -0,0 +1,9 @@ +import GetPath from './GetPath.js'; +import GetNextTile from './GetNextTile.js'; +import GetCost from './GetCost.js'; + +export default { + getPath: GetPath, + getNextTile: GetNextTile, + getCost: GetCost, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Monopoly.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Monopoly.d.ts new file mode 100644 index 000000000..8ca26ab2e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Monopoly.d.ts @@ -0,0 +1,58 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; +import { TileXYType } from '../types/Position'; +import Board from '../board/Board'; + +export default Monopoly; + +declare namespace Monopoly { + + type STOP = -1; + type BLOCKER = null; + + type NodeType = { + x: number, y: number, + direction: number + } + + type CostCallbackType = ( + curTile: NodeType | null, preTile: NodeType | null, + pathFinder: Monopoly + ) + => number | STOP | BLOCKER; + + interface IConfig { + face?: number, + + pathTileZ?: number, + cost?: number, + costCallback?: CostCallbackType, + costCallbackScope?: object, + } +} + +declare class Monopoly extends ComponentBase { + constructor( + gameObject: ChessType, + config?: Monopoly.IConfig + ); + + readonly gameObject: ChessType; + readonly board: Board; + + setCostFunction(cost: number): this; + setCostFunction( + callback: Monopoly.CostCallbackType, + scope?: object + ): this; + + setFace(direction: number): this; + + getPath( + movingPoints: number, + out?: TileXYType[] + ): TileXYType[]; + + readonly STOP: Monopoly.STOP; + readonly BLOCKER: Monopoly.BLOCKER; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Monopoly.js b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Monopoly.js new file mode 100644 index 000000000..7271ac3c4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/Monopoly.js @@ -0,0 +1,95 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import Methods from './Methods.js'; +import GetChessData from '../chess/GetChessData.js'; +import CONST from './const.js'; +import GetValue from '../../utils/object/GetValue.js'; + +const BLOCKER = CONST.BLOCKER; +const STOP = CONST.STOP; + +class Monopoly extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, { eventEmitter: false }); + // No event emitter + // this.parent = gameObject; + + this.chessData = GetChessData(gameObject); + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.preTileXY = GetValue(o, 'preTileXY', undefined); + var costCallback = GetValue(o, 'costCallback', undefined); + var costCallbackScope = GetValue(o, 'costCallbackScope', undefined); + if (costCallback === undefined) { + costCallback = GetValue(o, 'cost', 1); + } + this.setFace(GetValue(o, 'face', 0)); + this.setPathMode(GetValue(o, 'pathMode', 0)); + this.setPathTileZ(GetValue(o, 'pathTileZ', 0)); + this.setCostFunction(costCallback, costCallbackScope); + return this; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.chessData = undefined; + + super.shutdown(fromScene); + } + + setFace(direction) { + direction = this.board.grid.directionNormalize(direction); + this.face = direction; + return this; + } + + setPathMode(mode) { + if (typeof (mode) === 'string') { + mode = PATHMODE[mode]; + } + this.pathMode = mode; + return this; + } + + setCostFunction(callback, scope) { + this.costCallback = callback; + this.costCallbackScope = scope; + return this; + } + + setPathTileZ(value) { + if (value === undefined) { + value = true; + } + this.pathTileZ = value; + return this; + } + + get BLOCKER() { + return BLOCKER; + } + + get STOP() { + return STOP; + } + + get board() { + return this.chessData.board; + } +} + +Object.assign( + Monopoly.prototype, + Methods +); + +const PATHMODE = { + 'forward': 0, + 'random': 1 +} +export default Monopoly; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/monopoly/TileData.js b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/TileData.js new file mode 100644 index 000000000..f8aae631e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/TileData.js @@ -0,0 +1,5 @@ +var CreateTileData = function (x, y, direction) { + return { x: x, y: y, direction: direction }; +} + +export { CreateTileData }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/monopoly/const.js b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/const.js new file mode 100644 index 000000000..e172005b9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/monopoly/const.js @@ -0,0 +1,5 @@ +export default { + // special cost + 'BLOCKER': null, + 'STOP': -1, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/moveto/CanMoveToTile.js b/ui/src/phaser3-rex-plugins/plugins/board/moveto/CanMoveToTile.js new file mode 100644 index 000000000..d7c570af5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/moveto/CanMoveToTile.js @@ -0,0 +1,74 @@ +var CanMoveToTile = function (tileX, tileY, direction) { + var board = this.chessData.board; + // Chess is not in a board + if (board == null) { + return false; + } + var myTileXYZ = this.chessData.tileXYZ; + var myTileX = myTileXYZ.x, + myTileY = myTileXYZ.y, + myTileZ = myTileXYZ.z; + + // Move to current position + if ((tileX === myTileX) && (tileY === myTileY)) { + return true; + } + + // Target position is not in board + if (!board.contains(tileX, tileY)) { + return false; + } + + // Blocker test + if (this.blockerTest) { + if (board.hasBlocker(tileX, tileY)) { + return false; + } + } + + // Edge-blocker test + if (this.edgeBlockerTest) { + // // TODO + } + + // Custom moveable test + if (this.moveableTestCallback) { + if (direction === undefined) { + direction = this.chessData.getTileDirection(tileX, tileY); + } + targetTileXY.x = tileX; + targetTileXY.y = tileY; + targetTileXY.z = myTileZ; + if (this.moveableTestScope) { + var moveable = this.moveableTestCallback.call(this.moveableTestScope, myTileXYZ, targetTileXY, direction, board); + } else { + var moveable = this.moveableTestCallback(myTileXYZ, targetTileXY, direction, board); + } + if (!moveable) { + return false; + } + } + + // Sneak mode, change tileZ in MoveToTile method + // if (this.sneakMode) { + // } + + // Occupied test + if (this.occupiedTest && !this.sneakMode) { + var occupiedChess = board.tileXYZToChess(tileX, tileY, myTileZ); + if (occupiedChess) { + this.emit('occupy', occupiedChess, this.parent, this); + // Try to move occupiedChess away in this event + // Still ooccupied? + if (board.contains(tileX, tileY, myTileZ)) { + return false; + } + } + } + + return true; +} + +var targetTileXY = { x: 0, y: 0, z: 0, }; + +export default CanMoveToTile; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/moveto/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/moveto/Factory.d.ts new file mode 100644 index 000000000..fef3dda0a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/moveto/Factory.d.ts @@ -0,0 +1,6 @@ +import MoveTo from './MoveTo'; + +export default function ( + gameObject: Phaser.GameObjects.GameObject, + config?: MoveTo.IConfig +): MoveTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/moveto/Factory.js b/ui/src/phaser3-rex-plugins/plugins/board/moveto/Factory.js new file mode 100644 index 000000000..a60005bf0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/moveto/Factory.js @@ -0,0 +1,15 @@ +import MoveTo from './MoveTo.js'; +import MiniBoardMoveTo from '../miniboard/moveto/MoveTo.js'; +import IsMiniBoardObject from '../miniboard/IsMiniBoardObject.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../utils/object/SetValue.js'; + +ObjectFactory.register('moveTo', function (gameObject, config) { + var klass = (IsMiniBoardObject(gameObject)) ? MiniBoardMoveTo : MoveTo; + return new klass(gameObject, config); +}); + +SetValue(window, 'RexPlugins.Board.MoveTo', MoveTo); +SetValue(window, 'RexPlugins.Board.MiniBoardMoveTo', MiniBoardMoveTo); + +export default MoveTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/moveto/GetSneakTileZ.js b/ui/src/phaser3-rex-plugins/plugins/board/moveto/GetSneakTileZ.js new file mode 100644 index 000000000..65a24f5e1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/moveto/GetSneakTileZ.js @@ -0,0 +1,10 @@ +var GetSneakTileZ = function (moveTo, tileX, tileY, tileZ) { + var board = moveTo.chessData.board; + var sneakTileZ = tileZ.toString(); + do { + sneakTileZ += '.'; + } while (board.contains(tileX, tileY, sneakTileZ)) + return sneakTileZ; +} + +export default GetSneakTileZ; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/moveto/Methods.js b/ui/src/phaser3-rex-plugins/plugins/board/moveto/Methods.js new file mode 100644 index 000000000..0a8608b40 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/moveto/Methods.js @@ -0,0 +1,15 @@ +import CanMoveToTile from './CanMoveToTile.js'; +import MoveToTile from './MoveToTile.js'; +import MoveToward from './MoveToward.js'; +import MoveToRandomNeighbor from './MoveToRandomNeighbor.js'; +import MoveAway from './MoveAway.js'; +import MoveCloser from './MoveCloser.js'; + +export default { + canMoveTo: CanMoveToTile, + moveTo: MoveToTile, + moveToward: MoveToward, + moveToRandomNeighbor: MoveToRandomNeighbor, + moveAway: MoveAway, + moveCloser: MoveCloser, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveAway.js b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveAway.js new file mode 100644 index 000000000..7ff45d237 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveAway.js @@ -0,0 +1,110 @@ +var MoveAway = function (tileX, tileY, moveAwayMode) { + var board = this.chessData.board; + if (board === null) { // chess is not in a board + this.lastMoveResult = false; + return this; + } + + if (typeof (tileX) !== 'number') { + var config = tileX; + tileX = config.x; + tileY = config.y; + } + targetTileXY.x = tileX; + targetTileXY.y = tileY; + if (moveAwayMode === undefined) { + moveAwayMode = true; + } + + var myTileXYZ = this.chessData.tileXYZ; + var directions = board.grid.allDirections; + + // Get tileXY and distance of each neighbor, and current tile position + for (var i = 0, cnt = directions.length + 1; i < cnt; i++) { + var chessInfo = globChessInfo[i]; + if (!chessInfo) { + chessInfo = {}; + globChessInfo.push(chessInfo); + } + + if (i < (cnt - 1)) { + // Neighbors + var out = board.getNeighborTileXY(myTileXYZ, i, chessInfo); + if (out === null) { // Invalid neighbor tile position + chessInfo.x = undefined; + chessInfo.y = undefined; + chessInfo.distance = undefined; + } else { + chessInfo.distance = board.getDistance(chessInfo, targetTileXY, true); + } + } else { + // Current tile + chessInfo.direction = undefined; + chessInfo.x = myTileXYZ.x; + chessInfo.y = myTileXYZ.y; + chessInfo.distance = board.getDistance(chessInfo, targetTileXY, true); + } + + } + globChessInfo.length = directions.length + 1; + + // Sort chess info + var previousDirection = this.destinationDirection; + globChessInfo.sort(function (infoA, infoB) { + var distanceA = infoA.distance, + distanceB = infoB.distance; + // Invalid tile position + if (distanceA === undefined) { + return 1; + } + if (distanceB === undefined) { + return -1; + } + + if (distanceA > distanceB) { + return (moveAwayMode) ? -1 : 1; + } + if (distanceA < distanceB) { + return (moveAwayMode) ? 1 : -1; + } + + // Equal-to case + var directionA = infoA.direction, + directionB = infoB.direction; + // Diagonal + if (directionA === previousDirection) { + return 1; + } + if (directionB === previousDirection) { + return -1; + } + // Current tile position + if (directionA === undefined) { + return 1; + } + if (directionB === undefined) { + return -1; + } + return 0; + }); + + // Try move to neighbor, or current tile position + for (var i = 0, cnt = globChessInfo.length; i < cnt; i++) { + chessInfo = globChessInfo[i]; + if (chessInfo.distance === null) { // Invalid tile position + return this; + } + this.moveTo(chessInfo); + if (this.lastMoveResult) { + return this; + } + } + return this; +} + +var targetTileXY = { + x: 0, + y: 0 +} +var globChessInfo = []; +export default MoveAway; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveCloser.js b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveCloser.js new file mode 100644 index 000000000..1f40c7c17 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveCloser.js @@ -0,0 +1,5 @@ +var MoveCloser = function(tileX, tileY) { + this.moveAway(tileX, tileY, false); + return this; +} +export default MoveCloser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveTo.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveTo.d.ts new file mode 100644 index 000000000..00e573e81 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveTo.d.ts @@ -0,0 +1,94 @@ +import TickTask from '../../utils/componentbase/TickTask'; +import { TileXYType, TileXYZType } from '../types/Position'; +import Board from '../board/Board'; + + +export default MoveTo; + +declare namespace MoveTo { + + type MoveableTestCallbackType = ( + fromTileXYZ: TileXYZType, + toTileXYZ: TileXYZType, + direction: number, + board: Board + ) => boolean; + + interface IConfig { + speed?: number, + rotateToTarget?: boolean, + + occupiedTest?: boolean, + blockerTest?: boolean, + moveableTest?: MoveableTestCallbackType, + moveableTestScope?: object, + + sneak?: boolean, + } + + namespace Events { + type CompleteCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + moveTo: MoveTo + ) => void; + + type OccupyCallbackType = ( + occupiedGameObject: Phaser.GameObjects.GameObject, + gameObject: Phaser.GameObjects.GameObject, + moveTo: MoveTo + ) => void; + } +} + +declare class MoveTo extends TickTask { + constructor( + gameObject: ChessType, + config?: MoveTo.IConfig + ); + + moveTo(tileX: number, tileY: number): this; + moveTo(tileXY: TileXYType): this; + + moveToward(direction: number): this; + + moveToRandomNeighbor(): this; + + moveAway(tileX: number, tileY: number): this; + moveAway(tileXY: TileXYType): this; + + moveCloser(tileX: number, tileY: number): this; + moveCloser(tileXY: TileXYType): this; + + canMoveTo(tileX: number, tileY: number): boolean; + + readonly lastMoveResult: boolean; + + readonly destinationTileX: number; + readonly destinationTileY: number; + readonly destinationDirection: number; + + setEnable(enable?: boolean): this; + enable: boolean; + + setSpeed(speed: number): this; + speed: number; + + timeScale: number; + + setRotateToTarget(enable?: boolean): this; + rotateToTarget: boolean; + + setSneakEnable(enable?: boolean): this; + + setOccupiedTest(enable?: boolean): this; + occupiedTest: boolean; + setBlockerTest(enable?: boolean): this; + blockerTest: boolean; + setEdgeBlockerTest(enable?: boolean): this; + edgeBlockerTest: boolean; + + setMoveableTestCallback( + callback: MoveTo.MoveableTestCallbackType, + scope?: object + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveTo.js b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveTo.js new file mode 100644 index 000000000..830a520f3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveTo.js @@ -0,0 +1,214 @@ +import TickTask from '../../utils/componentbase/SceneUpdateTickTask.js'; +import Methods from './Methods.js'; +import MoveToTask from '../../behaviors/moveto/MoveTo.js'; +import GetChessData from '../chess/GetChessData.js'; +import GetValue from '../../utils/object/GetValue.js'; + +class MoveTo extends TickTask { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this.chessData = GetChessData(gameObject); + this.scene = gameObject.scene; + this.moveToTask = new MoveToTask(gameObject, { tickingMode: 0 }); + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.isRunning = GetValue(o, 'isRunning', false); + this.setEnable(GetValue(o, 'enable', true)); + this.timeScale = GetValue(o, 'timeScale', 1); + this.setSpeed(GetValue(o, 'speed', 400)); + this.setRotateToTarget(GetValue(o, 'rotateToTarget', false)); + this.setOccupiedTest(GetValue(o, 'occupiedTest', false)); + this.setBlockerTest(GetValue(o, 'blockerTest', false)); + this.setEdgeBlockerTest(GetValue(o, 'edgeBlockerTest', false)); + this.setMoveableTestCallback(GetValue(o, 'moveableTest', undefined), GetValue(o, 'moveableTestScope', undefined)); + this.setSneakEnable(GetValue(o, 'sneak', false)); + this.destinationTileX = GetValue(o, 'destinationTileX', null); + this.destinationTileY = GetValue(o, 'destinationTileY', null); + this.destinationDirection = GetValue(o, 'destinationDirection', null); + this.lastMoveResult = GetValue(o, 'lastMoveResult', undefined); + return this; + } + + toJSON() { + return { + isRunning: this.isRunning, + enable: this.enable, + timeScale: this.timeScale, + speed: this.speed, + occupiedTest: this.occupiedTest, + blockerTest: this.blockerTest, + edgeBlockerTest: this.edgeBlockerTest, + moveableTest: this.moveableTestCallback, + moveableTestScope: this.moveableTestScope, + rotateToTarget: this.rotateToTarget, + destinationTileX: this.destinationTileX, + destinationTileY: this.destinationTileY, + destinationDirection: this.destinationDirection, + lastMoveResult: this.lastMoveResult, + tickingMode: this.tickingMode + }; + } + + shutdown(fromScene) { + this.moveToTask.shutdown(fromScene); + super.shutdown(fromScene); + } + + set enable(value) { + this.moveToTask.setEnable(value); + } + + get enable() { + return this.moveToTask.enable; + } + + setEnable(e) { + if (e == undefined) { + e = true; + } + this.enable = e; + return this; + } + + get timeScale() { + return this.moveToTask.timeScale; + } + + set timeScale(value) { + this.moveToTask.timeScale = value; + } + + set speed(value) { + this.moveToTask.setSpeed(value); + } + + get speed() { + return this.moveToTask.speed; + } + + setSpeed(speed) { + this.speed = speed; + return this; + } + + set rotateToTarget(value) { + this.moveToTask.setRotateToTarget(value); + } + + get rotateToTarget() { + return this.moveToTask.rotateToTarget; + } + + setRotateToTarget(enable) { + if (enable === undefined) { + enable = true; + } + this.rotateToTarget = enable; + return this; + } + + setOccupiedTest(enable) { + if (enable === undefined) { + enable = true; + } + this.occupiedTest = enable; + return this; + } + + setBlockerTest(enable) { + if (enable === undefined) { + enable = true; + } + this.blockerTest = enable; + return this; + } + + setEdgeBlockerTest(enable) { + if (enable === undefined) { + enable = true; + } + this.edgeBlockerTest = enable; + return this; + } + + setMoveableTestCallback(callback, scope) { + this.moveableTestCallback = callback; + this.moveableTestScope = scope; + return this; + } + + setSneakEnable(enable) { + if (enable === undefined) { + enable = true; + } + + this.sneakMode = enable; + this.tileZSave = undefined; + return this; + } + + moveAlongLine(startX, startY, endX, endY) { + if (startX !== undefined) { + this.parent.x = startX; + } + if (startY !== undefined) { + this.parent.y = startY; + } + this.moveToTask.moveTo(endX, endY); + return this; + }; + + addMoveLine(startX, startY, endX, endY) { + if (!this.moveToTask.hasOwnProperty('nextlines')) { + this.moveToTask.nextlines = []; + } + this.moveToTask.nextlines.push( + [startX, startY, endX, endY] + ); + return this; + }; + + moveNextLine() { + var nextlines = this.moveToTask.nextlines; + if (!nextlines) { + return false; + } + if (nextlines.length === 0) { + return false; + } + // has next line + this.moveAlongLine.apply(this, nextlines[0]); + nextlines.length = 0; + return true; + } + + update(time, delta) { + if ((!this.isRunning) || (!this.enable)) { + return this; + } + + var moveToTask = this.moveToTask; + moveToTask.update(time, delta); + if (!moveToTask.isRunning) { + if (!this.moveNextLine()) { + this.complete(); + } + return this; + } + return this; + } +} + +Object.assign( + MoveTo.prototype, + Methods +); + + +export default MoveTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveToRandomNeighbor.js b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveToRandomNeighbor.js new file mode 100644 index 000000000..ba5c76a57 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveToRandomNeighbor.js @@ -0,0 +1,26 @@ +import Clone from '../../utils/object/Clone.js'; +import Shuffle from '../../utils/array/Shuffle.js'; + +var MoveToRandomNeighbor = function () { + var board = this.chessData.board; + if (board === null) { // chess is not in a board + this.lastMoveResult = false; + return this; + } + + var directions = board.grid.allDirections; + if (globDirections.length !== directions.length) { + Clone(directions, globDirections); + } + Shuffle(globDirections); + for (var i = 0, cnt = globDirections.length; i < cnt; i++) { + this.moveToward(globDirections[i]); + if (this.lastMoveResult) { + return this; + } + } + return this; +} + +var globDirections = []; +export default MoveToRandomNeighbor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveToTile.js b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveToTile.js new file mode 100644 index 000000000..dc80cffcf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveToTile.js @@ -0,0 +1,115 @@ +import GetValue from '../../utils/object/GetValue.js'; +import GetSneakTileZ from './GetSneakTileZ.js'; + +var MoveToTile = function (tileX, tileY, direction) { + var board = this.chessData.board; + if (board === null) { // chess is not in a board + this.lastMoveResult = false; + return this; + } + + if ((tileX != null) && (typeof (tileX) !== 'number')) { + var config = tileX; + tileX = GetValue(config, 'x', undefined); + tileY = GetValue(config, 'y', undefined); + direction = GetValue(config, 'direction', undefined); + } + var myTileXYZ = this.chessData.tileXYZ; + if ((direction !== undefined) && + (tileX == null) || (tileY == null)) { + // Get neighbor tile position if direction is not undefined + var targetTileXY = board.getNeighborTileXY(myTileXYZ, direction, true); + if (targetTileXY !== null) { + tileX = targetTileXY.x; + tileY = targetTileXY.y; + } else { + tileX = null; + tileY = null; + } + } + + // invalid tile position + if ((tileX == null) || (tileY == null)) { + this.lastMoveResult = false; + return this; + } + if (direction === undefined) { + globTileXYZ.x = tileX; + globTileXYZ.y = tileY + direction = board.getNeighborTileDirection(myTileXYZ, globTileXYZ); + } + if (!this.canMoveTo(tileX, tileY, direction)) { + this.lastMoveResult = false; + return this; + } + this.destinationTileX = tileX; + this.destinationTileY = tileY; + this.destinationDirection = direction; + + if (board.wrapMode && (direction !== null)) { + board.grid.getNeighborTileXY(myTileXYZ.x, myTileXYZ.y, direction, neighborTileXY); + // wrap mode && neighbor + if ((neighborTileXY.x === tileX) && (neighborTileXY.y === tileY)) { + // not a wrapped neighbor + var out = board.tileXYToWorldXY(tileX, tileY, true); + this.moveAlongLine(undefined, undefined, out.x, out.y); + } else { + // wrapped neighbor + // line 0 + var out = board.tileXYToWorldXY(neighborTileXY.x, neighborTileXY.y, true); + var originNeighborWorldX = out.x; + var originNeighborWorldY = out.y; + out = board.tileXYToWorldXY(myTileXYZ.x, myTileXYZ.y, true); + var startX = out.x; + var startY = out.y; + var endX = (startX + originNeighborWorldX) / 2; + var endY = (startY + originNeighborWorldY) / 2; + this.moveAlongLine(undefined, undefined, endX, endY); + // line 1 + var oppositeDirection = board.getOppositeDirection(tileX, tileY, direction); + board.grid.getNeighborTileXY(tileX, tileY, oppositeDirection, neighborTileXY); + out = board.tileXYToWorldXY(neighborTileXY.x, neighborTileXY.y, true); + originNeighborWorldX = out.x; + originNeighborWorldY = out.y; + out = board.tileXYToWorldXY(tileX, tileY, true); + endX = out.x; + endY = out.y; + startX = (originNeighborWorldX + endX) / 2; + startY = (originNeighborWorldY + endY) / 2; + this.addMoveLine(startX, startY, endX, endY); + } + } else { + var out = board.tileXYToWorldXY(tileX, tileY, true); + this.moveAlongLine(undefined, undefined, out.x, out.y); + } + + var tileZ = myTileXYZ.z; + if (this.sneakMode) { + if (this.tileZSave === undefined) { + if (board.contains(tileX, tileY, tileZ)) { + // Sneak + this.tileZSave = tileZ; + tileZ = GetSneakTileZ(this, tileX, tileY, this.tileZSave); + } + } else { + if (board.contains(tileX, tileY, this.tileZSave)) { + // Sneak + tileZ = GetSneakTileZ(this, tileX, tileY, this.tileZSave); + } else { + // Go back + tileZ = this.tileZSave; + this.tileZSave = undefined; + } + } + } + board.moveChess(this.parent, tileX, tileY, tileZ, false); + + this.isRunning = true; + this.lastMoveResult = true; + return this; +} + +var globTileXYZ = {}; +var neighborTileXY = {}; + +export default MoveToTile; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveToward.js b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveToward.js new file mode 100644 index 000000000..aadd49649 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/moveto/MoveToward.js @@ -0,0 +1,5 @@ +var MoveToward = function (direction) { + this.moveTo(undefined, undefined, direction); + return this; +} +export default MoveToward; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/Factory.d.ts new file mode 100644 index 000000000..73eac1b4f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/Factory.d.ts @@ -0,0 +1,10 @@ +import PathFinder from './PathFinder'; + +export default function ( + gameObject: Phaser.GameObjects.GameObject, + config?: PathFinder.IConfig +): PathFinder; + +export default function( + config?: PathFinder.IConfig +): PathFinder; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/Factory.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/Factory.js new file mode 100644 index 000000000..4bd801766 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/Factory.js @@ -0,0 +1,11 @@ +import PathFinder from './PathFinder.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../utils/object/SetValue.js'; + +ObjectFactory.register('pathFinder', function (gameObject, config) { + return new PathFinder(gameObject, config); +}); + +SetValue(window, 'RexPlugins.Board.PathFinder', PathFinder); + +export default PathFinder; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/FindArea.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/FindArea.js new file mode 100644 index 000000000..952eb36d8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/FindArea.js @@ -0,0 +1,54 @@ +import CONST from './const.js'; + +const AREA_MODE = CONST.AREA_MODE; +const INFINITY = CONST.INFINITY; // undefined + +var FindArea = function (movingPoints, out) { + if (out === undefined) { + out = []; + } + if (this.board === null) { // chess is not in board + return out; + } + if ((movingPoints !== INFINITY) && (movingPoints <= 0)) { + return out; + } + + var startTileXYZ = this.chessData.tileXYZ, + startTileX = startTileXYZ.x, + startTileY = startTileXYZ.y; + this.aStarSearch(startTileXYZ, null, movingPoints, AREA_MODE); + // output : this.nodeManager.getAllNodes() + var nodes = this.nodeManager.getAllNodes(), + node, nodesList = []; + for (var key in nodes) { + node = nodes[key]; + // not include start node + if ((node.x === startTileX) && (node.y === startTileY)) { + continue; + } + // not include open node + if (!node.closed) { + continue; + } + nodesList.push(node); + } + // sort by sn (creating order) + nodesList.sort(function (nodeA, nodeB) { + var snA = nodeA.sn; + var snB = nodeB.sn; + return (snA > snB) ? 1 : + (snA < snB) ? -1 : + 0; + }); + for (var i = 0, cnt = nodesList.length; i < cnt; i++) { + node = nodesList[i]; + out.push({ + x: node.x, + y: node.y, + cost: node.g + }); + } + return out; +} +export default FindArea; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/FindPath.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/FindPath.js new file mode 100644 index 000000000..b07fbe3b0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/FindPath.js @@ -0,0 +1,29 @@ +import CONST from './const.js'; + +const PATH_MODE = CONST.PATH_MODE; +const INFINITY = CONST.INFINITY; // undefined + +var FindPath = function (endTileXY, movingPoints, isClosest, out) { + if (isClosest === undefined) { + isClosest = true; + } + if (out === undefined) { + out = []; + } + if (this.board === null) { // chess is not in board + return out; + } + if ((movingPoints !== INFINITY) && (movingPoints <= 0)) { + return out; + } + + var startTileXYZ = this.chessData.tileXYZ; + this.aStarSearch(startTileXYZ, endTileXY, movingPoints, PATH_MODE); + var nodeManager = this.nodeManager; + var endNode = (isClosest) ? nodeManager.closestNode : nodeManager.getNode(endTileXY); + if (endNode === null) { + return out; + } + return this.getPath(endNode, out); +} +export default FindPath; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/GetCost.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/GetCost.js new file mode 100644 index 000000000..f4c6a7079 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/GetCost.js @@ -0,0 +1,38 @@ +import CONST from './const.js'; + +const BLOCKER = CONST.BLOCKER; + +var GetCost = function (curNode, preNode) { + // Occupied test + if (this.occupiedTest) { + if (this.board.contains(curNode.x, curNode.y, this.chessData.tileXYZ.z)) { + return BLOCKER; + } + } + // Blocker test + if (this.blockerTest) { + if (this.board.hasBlocker(curNode.x, curNode.y)) { + return BLOCKER; + } + } + // Edge-blocker test + if (this.edgeBlockerTest) { + // TODO + } + + if (typeof (this.costCallback) === 'number') { + return this.costCallback; + } else { + var cost; + if (this.costCallbackScope) { + cost = this.costCallback.call(this.costCallbackScope, curNode, preNode, this); + } else { + cost = this.costCallback(curNode, preNode, this); + } + if (cost === undefined) { + cost = BLOCKER; + } + return cost; + } +} +export default GetCost; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/GetPath.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/GetPath.js new file mode 100644 index 000000000..b0dedd297 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/GetPath.js @@ -0,0 +1,30 @@ +import GetNodePath from './astartsearch/GetNodePath.js'; +var GetPath = function (endTileXY, out) { + if (out === undefined) { + out = []; + } + if (this.board === undefined) { + return out; + } + var nodeManager = this.nodeManager; + if (nodeManager === undefined) { + return out; + } + var startNode = nodeManager.getNode(this.chessData.tileXYZ, false); + var endNode = nodeManager.getNode(endTileXY, false); + if ((startNode === null) || (endNode === null)) { + return out; + } + var nodes = GetNodePath(startNode, endNode, this.pathMode); + var node; + for (var i = 0, cnt = nodes.length; i < cnt; i++) { + node = nodes[i]; + out.push({ + x: node.x, + y: node.y, + cost: node.g + }); + } + return out; +} +export default GetPath; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/Methods.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/Methods.js new file mode 100644 index 000000000..38ee9db00 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/Methods.js @@ -0,0 +1,16 @@ + +import AStarSearch from './astartsearch/AStarSearch.js'; +import GetCost from './GetCost.js'; +import FindArea from './FindArea.js'; +import GetPath from './GetPath.js'; +import FindPath from './FindPath.js'; +import TileXYToCost from './TileXYToCost.js'; + +export default { + aStarSearch: AStarSearch, + getCost: GetCost, + findArea: FindArea, + getPath: GetPath, + findPath: FindPath, + tileXYToCost: TileXYToCost, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/PathFinder.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/PathFinder.d.ts new file mode 100644 index 000000000..1bcc8d974 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/PathFinder.d.ts @@ -0,0 +1,93 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; +import { TileXYType } from '../types/Position'; +import Board from '../board/Board'; + +export default PathFinder; + +declare namespace PathFinder { + + type PathModeTypes = 'random' | 'diagonal' | 'straight' | 'line' | + 'A*' | 'A*-random' | 'A*-line' | + 0 | 1 | 2 | 3 | + 10 | 11 | 12; + + type NodeType = { + x: number, y: number, + pathCost: number, + preNodes: NodeType[] + } + + type BLOCKER = null; + type INFINITY = undefined; + + type CostCallbackType = ( + curTile: NodeType, preTile: NodeType, + pathFinder: PathFinder + ) + => number | BLOCKER | INFINITY; + + interface IConfig { + occupiedTest?: boolean, + blockerTest?: boolean, + + cost?: number, + costCallback?: CostCallbackType, + costCallbackScope?: object, + cacheCost?: boolean, + + pathMode?: PathModeTypes, + weight?: number, + shuffleNeighbors?: boolean, + } + +} + +declare class PathFinder extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: PathFinder.IConfig + ); + + constructor( + config?: PathFinder.IConfig + ); + + setChess(gameObject: Phaser.GameObjects.GameObject): this; + readonly gameObject: Phaser.GameObjects.GameObject; + readonly board: Board; + + setCostFunction(cost: number): this; + setCostFunction( + callback: PathFinder.CostCallbackType, + scope?: object + ): this; + + setPathMode( + pathMode: PathFinder.PathModeTypes + ): this; + + findArea( + movingPoints?: number | PathFinder.INFINITY, + out?: PathFinder.NodeType[] + ): PathFinder.NodeType[]; + + getPath( + endTileXY: TileXYType + ): PathFinder.NodeType[]; + + findPath( + endTileXY: TileXYType, + movingPoints?: number | PathFinder.INFINITY, + isClosest?: boolean, + out?: PathFinder.NodeType[] + ): PathFinder.NodeType[]; + + tileXYToCost( + tileX: number, + tileY: number, + pathCost?: boolean + ): number; + + readonly BLOCKER: PathFinder.BLOCKER; + readonly INFINITY: PathFinder.INFINITY; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/PathFinder.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/PathFinder.js new file mode 100644 index 000000000..ae9433f14 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/PathFinder.js @@ -0,0 +1,167 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import GetChessData from '../chess/GetChessData.js'; +import Methods from './Methods.js'; +import CONST from './const.js'; +import IsPlainObject from '../../utils/object/IsPlainObject.js'; +import GetValue from '../../utils/object/GetValue.js'; + +const BLOCKER = CONST.BLOCKER; +const INFINITY = CONST.INFINITY; + +class PathFinder extends ComponentBase { + constructor(gameObject, config) { + if (IsPlainObject(gameObject)) { + config = gameObject; + gameObject = undefined; + } + super(gameObject, { eventEmitter: false }); + + this.setChess(gameObject); + this.nodeManager = undefined; + this.resetFromJSON(config); + } + + resetFromJSON(o) { + var costCallback = GetValue(o, 'costCallback', undefined); + var costCallbackScope = GetValue(o, 'costCallbackScope', undefined); + if (costCallback === undefined) { + costCallback = GetValue(o, 'cost', 1); + } + this.setOccupiedTest(GetValue(o, 'occupiedTest', false)); + this.setBlockerTest(GetValue(o, 'blockerTest', false)); + this.setEdgeBlockerTest(GetValue(o, 'edgeBlockerTest', false)); + this.setCostFunction(costCallback, costCallbackScope); + this.setPathMode(GetValue(o, 'pathMode', 0)); + this.setCacheCostMode(GetValue(o, 'cacheCost', true)); + this.setWeight(GetValue(o, 'weight', 10)); + this.setShuffleNeighborsMode(GetValue(o, 'shuffleNeighbors', false)); + return this; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + if (this.nodeManager !== undefined) { + this.nodeManager.destroy(); + } + this.chessData = undefined; + + super.shutdown(fromScene); + } + + get gameObject() { + return this.parent; + } + + setChess(gameObject) { + if (gameObject) { + this.chessData = GetChessData(gameObject); + if (this.parent !== gameObject) { + // Remove attatched event from previous gameObject + if (this.parent && this.parent.once) { + this.parent.off('destroy', this.onParentDestroy, this); + } + // Attach event + this.setParent(gameObject); + if (this.parent && this.parent.once) { + this.parent.once('destroy', this.onParentDestroy, this); + } + } + } else { + this.setParent(); + this.chessData = undefined; + } + return this; + } + + setCostFunction(callback, scope) { + this.costCallback = callback; + this.costCallbackScope = scope; + return this; + } + + setPathMode(mode) { + if (typeof (mode) === 'string') { + mode = CONST[mode]; + } + this.pathMode = mode; + return this; + } + + setCacheCostMode(value) { + if (value === undefined) { + value = true; + } + this.cacheCost = value; + return this; + } + + setOccupiedTest(enable) { + if (enable === undefined) { + enable = true; + } + this.occupiedTest = enable; + return this; + } + + setBlockerTest(enable) { + if (enable === undefined) { + enable = true; + } + this.blockerTest = enable; + return this; + } + + setEdgeBlockerTest(enable) { + if (enable === undefined) { + enable = true; + } + this.edgeBlockerTest = enable; + return this; + } + + setWeight(value) { + this.weight = value; + return this; + } + + setShuffleNeighborsMode(value) { + if (value === undefined) { + value = true; + } + this.shuffleNeighbors = value; + return this; + } + + get BLOCKER() { + return BLOCKER; + } + + get INFINITY() { + return INFINITY; + } + + get board() { + return this.chessData.board; + } +} + +Object.assign( + PathFinder.prototype, + Methods +); + +const PATHMODE = { + 'random': 0, + 'diagonal': 1, + 'straight': 2, + 'A*': 3, + 'line': 4, + 'A*-line': 5, + 'A*-random': 6 +} + +export default PathFinder; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/TileXYToCost.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/TileXYToCost.js new file mode 100644 index 000000000..502554d1e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/TileXYToCost.js @@ -0,0 +1,14 @@ +var TileXYToCost = function (tileX, tileY, pathCost) { + if (this.nodeManager === undefined) { + return null; + } + var node = this.nodeManager.getNode(tileX, tileY); + if (node === null) { + return null; + } + if (pathCost === undefined) { + pathCost = true; + } + return (pathCost)? node.g:node.cost; +} +export default TileXYToCost; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/AStarSearch.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/AStarSearch.js new file mode 100644 index 000000000..c7ad6af52 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/AStarSearch.js @@ -0,0 +1,152 @@ +/* + +javascript-astar 0.3.0 +http://github.com/bgrins/javascript-astar +Freely distributable under the MIT License. +Implements the astar search algorithm in javascript using a Binary Heap. +Includes Binary Heap (with modifications) from Marijn Haverbeke. +http://eloquentjavascript.net/appendix2.html + +*/ + +import NodeManager from './NodeManager.js'; +import BinaryHeap from './BinaryHeap.js'; +import CONST from '../const.js'; + +const AREA_MODE = CONST.AREA_MODE; +const PATH_MODE = CONST.PATH_MODE; + +const ASTAR = CONST['A*']; +const ASTAR_LINE = CONST['A*-line']; +const ASTAR_RANDOM = CONST['A*-random']; + +const BLOCKER = CONST.BLOCKER; +const INFINITY = CONST.INFINITY; + +// global object +var gOpenHeap = new BinaryHeap(function (node) { + return node.f; +}); +// global object + +var AStarSerach = function (startTileXYZ, endTileXY, movingPoints, mode) { + if (this.nodeManager === undefined) { + this.nodeManager = new NodeManager(this); + } + var nodeManager = this.nodeManager; + nodeManager.freeAllNodes(); + + // const isAreaSearch = (mode === AREA_MODE); + const isPathSearch = (mode === PATH_MODE); + const isAStarMode = (this.pathMode === ASTAR) || (this.pathMode === ASTAR_LINE) || (this.pathMode === ASTAR_RANDOM); + const astarHeuristicEnable = isPathSearch && isAStarMode; + const shortestPathEnable = isPathSearch && (!isAStarMode); + const astarHeuristicMode = + (!astarHeuristicEnable) ? null : + (this.pathMode == ASTAR) ? 0 : + (this.pathMode == ASTAR_LINE) ? 1 : + (this.pathMode == ASTAR_RANDOM) ? 2 : + null; + + var end = (endTileXY !== null) ? nodeManager.getNode(endTileXY.x, endTileXY.y, true) : null; + var start = nodeManager.getNode(startTileXYZ.x, startTileXYZ.y, true); + start.h = start.heuristic(end, astarHeuristicMode); + + // NEAREST NODE + var closestNode; + if (isPathSearch) { + closestNode = start; + closestNode.closerH = closestNode.h || closestNode.heuristic(end, 0); + } + // NEAREST NODE + + gOpenHeap.push(start); + while (gOpenHeap.size() > 0) { + // Grab the lowest f(x) to process next. Heap keeps this sorted for us. + var curNode = gOpenHeap.pop(); + + // End case -- result has been found, return the traced path. + if (isPathSearch && (curNode === end)) { + closestNode = end; + break; + } + + // Normal case -- move curNode from open to closed, process each of its neighbors. + curNode.closed = true; + + // Find all neighbors for the current node. + var neighbors = curNode.getNeighborNodes(); + + var neighbor, neighborCost, isNeighborMoreCloser; + for (var i = 0, cnt = neighbors.length; i < cnt; ++i) { + neighbor = neighbors[i]; + neighborCost = neighbor.getCost(curNode); + if (neighbor.closed || (neighborCost === BLOCKER)) { + // Not a valid node to process, skip to next neighbor. + //log("("+neighbor.x+","+neighbor.y+") is closed"); + continue; + } + + // The g score is the shortest distance from start to current node. + // We need to check if the path we have arrived at this neighbor is the shortest one we have seen yet. + var gScore = curNode.g + neighborCost, + beenVisited = neighbor.visited; + + //log("("+curNode.x+","+curNode.y+") -> ("+neighbor.x+","+neighbor.y+")="+neighborCost+" ,acc="+gScore); + if ((movingPoints != INFINITY) && (gScore > movingPoints)) { + //log("("+neighbor.x+","+neighbor.y+") out of range"); + continue; + } + + if ((!beenVisited) || (gScore < neighbor.g)) { + + // Found an optimal (so far) path to this node. Take score for node to see how good it is. + neighbor.visited = true; + neighbor.preNodes.length = 0; + neighbor.preNodes.push(curNode); + neighbor.h = neighbor.h || neighbor.heuristic(end, astarHeuristicMode, start); + neighbor.g = gScore; + neighbor.f = neighbor.g + neighbor.h; + + // NEAREST NODE + if (isPathSearch) { + neighbor.closerH = neighbor.h || neighbor.heuristic(end, 0); + isNeighborMoreCloser = (neighbor.closerH < closestNode.closerH) || + ((neighbor.closerH === closestNode.closerH) && (neighbor.g < closestNode.g)); + + if (isNeighborMoreCloser) { + closestNode = neighbor; + } + } + // NEAREST NODE + + if (!beenVisited) { + // Pushing to heap will put it in proper place based on the 'f' value. + gOpenHeap.push(neighbor); + //log("push ("+neighbor.x+","+neighbor.y+") ") + } else { + // Already seen the node, but since it has been rescored we need to reorder it in the heap + gOpenHeap.rescoreElement(neighbor); + //log("reorder ("+neighbor.x+","+neighbor.y+") ") + } + } else if (shortestPathEnable && (gScore == neighbor.g)) { + neighbor.preNodes.push(curNode); + + //if (neighbor.preNodes.indexOf(curNode) == -1) + // neighbor.preNodes.push(curNode); + //else + // debugger; + + //log("drop ("+neighbor.x+","+neighbor.y+") ") + } else { + //log("drop ("+neighbor.x+","+neighbor.y+") ") + } + } + + } + + nodeManager.closestNode = (isPathSearch) ? closestNode : null; + gOpenHeap.clear(); + return this; +} +export default AStarSerach; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/BinaryHeap.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/BinaryHeap.js new file mode 100644 index 000000000..2cf51e11b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/BinaryHeap.js @@ -0,0 +1,130 @@ +class BinaryHeap { + constructor(scoreFunction) { + this.content = []; + this.scoreFunction = scoreFunction; + } + + clear() { + this.content.length = 0; + } + + push(element) { + // Add the new element to the end of the array. + this.content.push(element); + + // Allow it to sink down. + this.sinkDown(this.content.length - 1); + } + + pop() { + // Store the first element so we can return it later. + var result = this.content[0]; + // Get the element at the end of the array. + var end = this.content.pop(); + // If there are any elements left, put the end element at the + // start, and let it bubble up. + if (this.content.length > 0) { + this.content[0] = end; + this.bubbleUp(0); + } + return result; + } + + remove(node) { + var i = this.content.indexOf(node); + + // When it is found, the process seen in 'pop' is repeated + // to fill up the hole. + var end = this.content.pop(); + + if (i !== this.content.length - 1) { + this.content[i] = end; + + if (this.scoreFunction(end) < this.scoreFunction(node)) { + this.sinkDown(i); + } else { + this.bubbleUp(i); + } + } + } + + size() { + return this.content.length; + } + + rescoreElement(node) { + this.sinkDown(this.content.indexOf(node)); + } + + sinkDown(n) { + // Fetch the element that has to be sunk. + var element = this.content[n]; + + // When at 0, an element can not sink any further. + while (n > 0) { + + // Compute the parent element's index, and fetch it. + var parentN = ((n + 1) >> 1) - 1, + parent = this.content[parentN]; + // Swap the elements if the parent is greater. + if (this.scoreFunction(element) < this.scoreFunction(parent)) { + this.content[parentN] = element; + this.content[n] = parent; + // Update 'n' to continue at the new position. + n = parentN; + } + // Found a parent that is less, no need to sink any further. + else { + break; + } + } + } + + bubbleUp(n) { + // Look up the target element and its score. + var length = this.content.length, + element = this.content[n], + elemScore = this.scoreFunction(element); + + while (true) { + // Compute the indices of the child elements. + var child2N = (n + 1) << 1, + child1N = child2N - 1; + // This is used to store the new position of the element, if any. + var swap = null, + child1Score; + // If the first child exists (is inside the array)... + if (child1N < length) { + // Look it up and compute its score. + var child1 = this.content[child1N]; + child1Score = this.scoreFunction(child1); + + // If the score is less than our element's, we need to swap. + if (child1Score < elemScore) { + swap = child1N; + } + } + + // Do the same checks for the other child. + if (child2N < length) { + var child2 = this.content[child2N], + child2Score = this.scoreFunction(child2); + if (child2Score < (swap === null ? elemScore : child1Score)) { + swap = child2N; + } + } + + // If the element needs to be moved, swap it, and continue. + if (swap !== null) { + this.content[n] = this.content[swap]; + this.content[swap] = element; + n = swap; + } + // Otherwise, we are done. + else { + break; + } + } + } +} +export default BinaryHeap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/GetNodePath.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/GetNodePath.js new file mode 100644 index 000000000..6c9b04377 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/GetNodePath.js @@ -0,0 +1,89 @@ +import CONST from '../const.js'; +import RandomInt from '../../../utils/math/Between.js'; + +const RANDOM = CONST['random']; +const DIAGONAL = CONST['diagonal']; +const STRAIGN = CONST['straight']; +const LINE = CONST['line']; +const ASTAR = CONST['A*']; +const ASTAR_LINE = CONST['A*-line']; +const ASTAR_RANDOM = CONST['A*-random']; + + +var GetNodePath = function (startNode, endNode, pathMode) { + var board = startNode.board; + + var curDir, preNodeDir; // DIAGONAL, STRAIGN + var targetAngle; // LINE + + var curNode = endNode, + preNode, preNodeKeysCnt; + var path = []; + while (curNode.preNodes.length > 0) { + path.push(curNode); + preNodeKeysCnt = curNode.preNodes.length; + + switch (pathMode) { + case ASTAR: + case ASTAR_LINE: + case ASTAR_RANDOM: + preNode = curNode.preNodes[0]; + curNode = preNode; + break; + + case RANDOM: + preNode = (preNodeKeysCnt === 1) ? curNode.preNodes[0] : curNode.preNodes[RandomInt(0, preNodeKeysCnt - 1)]; + curNode = preNode; + break; + + case DIAGONAL: + for (var i = 0; i < preNodeKeysCnt; i++) { + preNode = curNode.preNodes[i]; + preNodeDir = board.getNeighborTileDirection(curNode, preNode); + if (preNodeDir !== curDir) { + curDir = preNodeDir; + break; + } + } + curNode = preNode; + break; + + case STRAIGN: + for (i = 0; i < preNodeKeysCnt; i++) { + preNode = curNode.preNodes[i]; + preNodeDir = board.getNeighborTileDirection(curNode, preNode); + if (preNodeDir === curDir) { + break; + } + } + curDir = preNodeDir; + curNode = preNode; + break; + + case LINE: + if (targetAngle === undefined) { + targetAngle = endNode.angleTo(startNode); + } + if (preNodeKeysCnt === 1) { + preNode = curNode.preNodes[0]; + curNode = preNode; + targetAngle = endNode.angleTo(curNode); + } else { + preNode = curNode.preNodes[0]; + var deltaAngle = Math.abs(endNode.angleTo(preNode) - targetAngle); + var preNodeB, deltaAngleB; + for (var i = 1; i < preNodeKeysCnt; i++) { + preNodeB = curNode.preNodes[i]; + deltaAngleB = Math.abs(endNode.angleTo(preNodeB) - targetAngle); + if (deltaAngleB < deltaAngle) { + preNode = preNodeB; + } + } + curNode = preNode; + } + break; + } + } + return path.reverse(); +} +export default GetNodePath; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/Node.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/Node.js new file mode 100644 index 000000000..27419d59e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/Node.js @@ -0,0 +1,112 @@ +import Shuffle from '../../../utils/array/Shuffle.js'; +import AngleBetween from '../../../utils/math/angle/Between.js'; + +class Node { + constructor() { + this.preNodes = []; + this.manager = undefined; + } + + reset(manager) { + this.manager = manager; + // overwrite + this.sn = undefined; // for sorting by created order + this.key = undefined; + this.x = undefined; + this.y = undefined; + this.isTileXYZ = true; + // overwrite + + this._px = undefined; + this._py = undefined; + this.cost = undefined; // cost cache + this.f = 0; + this.g = 0; // path cost + this.h = 0; + this.closerH = 0; + this.visited = false; + this.closed = false; + this.preNodes.length = 0; + } + + destroy() { + this.preNodes.length = 0; + this.manager = undefined; + } + + heuristic(endNode, pathMode, baseNode) { + if (pathMode === null) { + return 0; + } + + var h, dist = this.board.getDistance(endNode, this, true) * this.pathFinder.weight; + + if ((pathMode === 1) && (baseNode !== undefined)) { + var deltaAngle = endNode.angleTo(baseNode) - this.angleTo(baseNode); + h = dist + Math.abs(deltaAngle); + } else if (pathMode === 2) { + h = dist + Math.random(); + } else { + h = dist; + } + + return h; + } + + getNeighborNodes() { + var neighborsTileXY = this.board.getNeighborTileXY(this); + if (this.pathFinder.shuffleNeighbors) { + Shuffle(neighborsTileXY); + } + + var node, neighborNodes = []; + for (var i = 0, cnt = neighborsTileXY.length; i < cnt; i++) { + node = this.manager.getNode(neighborsTileXY[i], true); + neighborNodes.push(node) + } + return neighborNodes; + } + + getCost(preNode) { + if (this.pathFinder.cacheCost) { + if (this.cost === undefined) { + this.cost = this.pathFinder.getCost(this, preNode); + } + } else { + this.cost = this.pathFinder.getCost(this, preNode); + } + return this.cost; + } + + angleTo(endNode) { + return AngleBetween(this.worldX, this.wroldY, endNode.worldX, endNode.wroldY); + } + + get pathFinder() { + return this.manager.pathFinder; + } + + get board() { + return this.manager.pathFinder.board; + } + + get worldX() { + if (this._px === undefined) { + this._px = this.board.tileXYToWroldX(this.x, this.y); + } + return this._px; + } + + get wroldY() { + if (this._py === undefined) { + this._py = this.board.tileXYToWroldY(this.x, this.y); + } + return this._py; + } + + get pathCost() { + return this.g; + } +} + +export default Node; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/NodeManager.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/NodeManager.js new file mode 100644 index 000000000..424c1f4b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/astartsearch/NodeManager.js @@ -0,0 +1,86 @@ +import Pool from '../../../pool.js'; +import TileXYToKey from '../../utils/tilexyzkey/TileXYToKey.js'; +import Node from './Node.js'; + +// global object +var NodesPool = new Pool(); // recycle dead nodes +// global object + +class NodeCache { + constructor(pathFinder) { + this.sn = 0; + this.pool = NodesPool; + this.nodes = {}; // {tileXYKey:node} + this.pathFinder = pathFinder; + this.closestNode = null; + } + + destroy() { + this.freeAllNodes(); + this.pathFinder = null; + this.pool = undefined; + return this; + } + + getNode(tileX, tileY, createNewNode) { + var key; + switch (typeof (tileX)) { + case 'number': // (tileX, tileY, createNewNode) + key = TileXYToKey(tileX, tileY); + break; + case 'string': // (key, createNewNode) + key = tileX; + createNewNode = tileY; + break; + default: // (tileXY, createNewNode) + var tileXY = tileX; + createNewNode = tileY; + tileX = tileXY.x; + tileY = tileXY.y; + key = TileXYToKey(tileX, tileY); + break; + } + if (createNewNode === undefined) { + createNewNode = false; + } + + this.sn++; + if (!this.nodes.hasOwnProperty(key)) { + if (!createNewNode) { + return null; + } + + var node = this.pool.pop(); + if (node === null) { + node = new Node(); + } + node.reset(this); + node.sn = this.sn; + node.key = key; + node.x = tileX; + node.y = tileY; + this.nodes[key] = node; + } + return this.nodes[key]; + } + + freeAllNodes() { + this.closestNode = null; + var nodes = this.nodes, + pool = this.pool; + var node; + for (var key in nodes) { + node = nodes[key]; + node.destroy(); + pool.push(node); + delete nodes[key]; + } + this.sn = 0; + return this; + } + + getAllNodes() { + return this.nodes; + } +} +export default NodeCache; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/const.js b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/const.js new file mode 100644 index 000000000..05e60a361 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/pathfinder/const.js @@ -0,0 +1,20 @@ +export default { + // a* search mode + AREA_MODE: 16, + PATH_MODE: 0, + + // path mode + 'random': 0, + 'diagonal': 1, + 'straight': 2, + 'line': 3, + 'A*': 10, + 'A*-random': 11, + 'A*-line': 12, + + // special cost + 'BLOCKER': null, + + // special moving point + 'INFINITY': undefined, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/shape/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/shape/Factory.d.ts new file mode 100644 index 000000000..e1d342640 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/shape/Factory.d.ts @@ -0,0 +1,10 @@ +import Shape from './Shape'; +import Board from '../board/LogicBoard'; +import MiniBoard from '../miniboard/MiniBoard'; + +export default function ( + board: Board | MiniBoard, + tileX: number, tileY: number, tileZ?: number, + fillColor?: number | null, fillAlpha?: number | null, + addToBoard?: boolean +): Shape; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/shape/Factory.js b/ui/src/phaser3-rex-plugins/plugins/board/shape/Factory.js new file mode 100644 index 000000000..94bf77ec7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/shape/Factory.js @@ -0,0 +1,13 @@ +import Shape from './Shape.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../utils/object/SetValue.js'; + +ObjectFactory.register('shape', function (board, tileX, tileY, tileZ, fillColor, fillAlpha, addToBoard) { + var gameObject = new Shape(board, tileX, tileY, tileZ, fillColor, fillAlpha, addToBoard); + board.scene.add.existing(gameObject); + return gameObject; +}); + +SetValue(window, 'RexPlugins.Board.Shape', Shape); + +export default Shape; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/shape/Shape.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/shape/Shape.d.ts new file mode 100644 index 000000000..754e5974a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/shape/Shape.d.ts @@ -0,0 +1,12 @@ +// import * as Phaser from 'phaser'; +import Board from '../board/Board'; +import MiniBoard from '../miniboard/MiniBoard'; + +export default class Shape extends Phaser.GameObjects.Polygon { + constructor( + board: Board | MiniBoard, + tileX: number, tileY: number, tileZ?: number, + fillColor?: number | null, fillAlpha?: number | null, + addToBoard?: boolean + ); +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/shape/Shape.js b/ui/src/phaser3-rex-plugins/plugins/board/shape/Shape.js new file mode 100644 index 000000000..1f50a8251 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/shape/Shape.js @@ -0,0 +1,64 @@ +import CreateChessData from '../chess/GetChessData.js'; +import IsMiniBoardObject from './../miniboard/IsMiniBoardObject.js'; + +const Base = Phaser.GameObjects.Polygon; +class Shape extends Base { + constructor(board, tileX, tileY, tileZ, fillColor, fillAlpha, addToBoard) { + if (addToBoard === undefined) { + addToBoard = true; + } + + // Chess-Container + var isMiniBoard = IsMiniBoardObject(board), + miniBoard; + if (isMiniBoard) { + miniBoard = board; + board = miniBoard.board; + } + + var scene = board.scene; + var worldX, worldY; + if (addToBoard) { + worldX = 0; + worldY = 0; + } else { + worldX = tileX; + worldY = tileY; + } + var points = board.getGridPoints(undefined, undefined, true); + ShiftToO(points); + super(scene, worldX, worldY, points, fillColor, fillAlpha); + + if (addToBoard) { + if (isMiniBoard) { // Chess-Container + miniBoard.addChess(this, tileX, tileY, tileZ); + } else { + board.addChess(this, tileX, tileY, tileZ, true); + } + } else { + CreateChessData(this); + } + } +} + +var ShiftToO = function (points) { + var minX = Infinity; + var minY = Infinity; + var point; + for (var i = 0, cnt = points.length; i < cnt; i++) { + point = points[i]; + minX = Math.min(minX, point.x); + minY = Math.min(minY, point.y); + } + if ((minX === 0) && (minY === 0)) { + return points; + } + for (var i = 0, cnt = points.length; i < cnt; i++) { + point = points[i]; + point.x -= minX; + point.y -= minY; + } + return points; +} + +export default Shape; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/texture/CreateTileTexture.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/texture/CreateTileTexture.d.ts new file mode 100644 index 000000000..45dc4bd41 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/texture/CreateTileTexture.d.ts @@ -0,0 +1,20 @@ +import Board from '../board/Board'; + +export default function CreateTileTexture( + board: Board, + key: string, + fillStyle: number | string | undefined, + strokeStyle?: number | string | undefined, + lineWidth?: number, + lineJoin?: 'round' | 'bevel' | 'miter' +): void; + +export default function CreateTileTexture( + board: Board, + key: string, + fillStyle: number | string | undefined, + strokeStyle?: number | string | undefined, + lineWidth?: number, + overlapGrid?: boolean, + lineJoin?: 'round' | 'bevel' | 'miter' +): void; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/texture/CreateTileTexture.js b/ui/src/phaser3-rex-plugins/plugins/board/texture/CreateTileTexture.js new file mode 100644 index 000000000..c77f71e0e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/texture/CreateTileTexture.js @@ -0,0 +1,27 @@ +import CreatePolygonTexture from '../../utils/texture/CreatePolygonTexture.js'; + +var CreateTileTexture = function (board, key, fillStyle, strokeStyle, lineWidth, overlapGrid, lineJoin) { + if (typeof (overlapGrid) === 'string') { + lineJoin = overlapGrid; + overlapGrid = undefined; + } + + if (overlapGrid === undefined) { + overlapGrid = true; + } + if (lineJoin === undefined) { + lineJoin = 'miter'; + } + + CreatePolygonTexture( + board.scene, + key, + board.getGridPoints(0, 0, true), + fillStyle, + strokeStyle, lineWidth, + overlapGrid, + lineJoin + ); +} + +export default CreateTileTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/tilemap/AddLayers.js b/ui/src/phaser3-rex-plugins/plugins/board/tilemap/AddLayers.js new file mode 100644 index 000000000..8f69fd8ae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/tilemap/AddLayers.js @@ -0,0 +1,35 @@ +import IsGameObject from '../../utils/system/IsGameObject.js'; + +var AddLayers = function (board, tilemap, layers) { + if (layers === undefined) { + layers = tilemap.layers; + } else if (!Array.isArray(layers)) { + layers = [layers]; + } + + for (var i = 0, cnt = layers.length; i < cnt; i++) { + var layer = layers[i]; + if (typeof (layer) === 'string') { + layer = tilemap.getLayer(layer); + } + if (IsGameObject(layer)) { + layer = layer.layer; + } + + AddLayer(board, layer); + } +} + +var AddLayer = function (board, layer) { + var tileZ = layer.name; + var layerData = layer.data; + for (var y = 0, ycnt = layerData.length; y < ycnt; y++) { + var layerRow = layerData[y]; + for (var x = 0, xcnt = layerRow.length; x < xcnt; x++) { + var tile = layerRow[x]; + board.addChess(tile, x, y, tileZ, false); + } + } +} + +export default AddLayers; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/tilemap/CreateBoard.js b/ui/src/phaser3-rex-plugins/plugins/board/tilemap/CreateBoard.js new file mode 100644 index 000000000..6fee4e5f4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/tilemap/CreateBoard.js @@ -0,0 +1,51 @@ +import Board from '../board/Board.js'; + +var CreateBoard = function (tilemap) { + var board = new Board(tilemap.scene, { + grid: CreateGridConfig(tilemap), + width: tilemap.width, + height: tilemap.height + }) + + return board; +} + +var CreateGridConfig = function (tilemap) { + var grid = { + cellWidth: tilemap.tileWidth, + cellHeight: tilemap.tileHeight, + } + + switch (tilemap.orientation) { + case 0: // ORTHOGONAL + grid.gridType = 'quadGrid'; + grid.type = 'orthogonal'; + break; + + case 1: // ISOMETRIC + grid.gridType = 'quadGrid'; + grid.type = 'isometric'; + break; + + case 3: // HEXAGONAL + grid.gridType = 'hexagonGrid'; + grid.staggeraxis = 'y'; + grid.staggerindex = 'odd'; + break; + + default: // ORTHOGONAL + grid.gridType = 'quadGrid'; + grid.type = 'orthogonal'; + break; + } + + var layer = tilemap.layers[0]; + if (layer) { + grid.x = layer.x; + grid.y = layer.y; + } + + return grid; +} + +export default CreateBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/tilemap/CreateBoardFromTilemap.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/tilemap/CreateBoardFromTilemap.d.ts new file mode 100644 index 000000000..a5c12ec6c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/tilemap/CreateBoardFromTilemap.d.ts @@ -0,0 +1,8 @@ +import Board from '../board/Board'; + +export default CreateBoardFromTilemap; + +declare function CreateBoardFromTilemap( + tilemap: Phaser.Tilemaps.Tilemap, + layers?: Phaser.Tilemaps.TilemapLayer[] | Phaser.Tilemaps.TilemapLayer | string[] | string +): Board; diff --git a/ui/src/phaser3-rex-plugins/plugins/board/tilemap/CreateBoardFromTilemap.js b/ui/src/phaser3-rex-plugins/plugins/board/tilemap/CreateBoardFromTilemap.js new file mode 100644 index 000000000..f6eae795c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/tilemap/CreateBoardFromTilemap.js @@ -0,0 +1,10 @@ +import CreateBoard from './CreateBoard.js'; +import AddLayers from './AddLayers.js'; + +var CreateBoardFromTilemap = function (tilemap, layers) { + var board = CreateBoard(tilemap); + AddLayers(board, tilemap, layers); + return board; +} + +export default CreateBoardFromTilemap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/types/Position.d.ts b/ui/src/phaser3-rex-plugins/plugins/board/types/Position.d.ts new file mode 100644 index 000000000..2d9e5cd2f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/types/Position.d.ts @@ -0,0 +1,5 @@ +export type TileXYZType = { x: number, y: number, z: (number | string) }; +export type TileXYType = { x: number, y: number, z?: (number | string) }; +export type TileXYDirectionType = { x: number, y: number, direction: number }; + +export type WorldXYType = { x: number, y: number }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/utils/AreTileXYArrayEqual.js b/ui/src/phaser3-rex-plugins/plugins/board/utils/AreTileXYArrayEqual.js new file mode 100644 index 000000000..e4ae827b4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/utils/AreTileXYArrayEqual.js @@ -0,0 +1,15 @@ +import AreTileXYEqual from './AreTileXYEqual.js'; + +var AreTileXYArrayEqual = function (tileArrayA, tileArrayB) { + if (tileArrayA.length !== tileArrayB.length) { + return false; + } else { + for (var i = 0, cnt = tileArrayA.length; i < cnt; i++) { + if (!AreTileXYEqual(tileArrayA[i], tileArrayB[i])) { + return false; + } + } + return true; + } +} +export default AreTileXYArrayEqual; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/utils/AreTileXYEqual.js b/ui/src/phaser3-rex-plugins/plugins/board/utils/AreTileXYEqual.js new file mode 100644 index 000000000..c506efdbc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/utils/AreTileXYEqual.js @@ -0,0 +1,4 @@ +var AreTileXYEqual = function (tileA, tileB) { + return tileA && tileB && (tileA.x === tileB.x) && (tileA.y === tileB.y); +} +export default AreTileXYEqual; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/utils/IsTileXYInArray.js b/ui/src/phaser3-rex-plugins/plugins/board/utils/IsTileXYInArray.js new file mode 100644 index 000000000..96f84eda2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/utils/IsTileXYInArray.js @@ -0,0 +1,12 @@ +import AreTileXYEqual from './AreTileXYEqual.js'; + +var IsTileXYInArray = function (tile, arr) { + for (var i = 0, cnt = arr.length; i < cnt; i++) { + if (AreTileXYEqual(tile, arr[i])) { + return true; + } + } + return false; +} + +export default IsTileXYInArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/utils/IsTileXYZ.js b/ui/src/phaser3-rex-plugins/plugins/board/utils/IsTileXYZ.js new file mode 100644 index 000000000..2090e0ba9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/utils/IsTileXYZ.js @@ -0,0 +1,8 @@ +import IsPlainObject from '../../utils/object/IsPlainObject.js'; + +var IsTileXYZ = function (obj) { + return (obj) && + (IsPlainObject(obj) || obj.isTileXYZ); +} + +export default IsTileXYZ; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/utils/tilexyzkey/KeyToTileXYZ.js b/ui/src/phaser3-rex-plugins/plugins/board/utils/tilexyzkey/KeyToTileXYZ.js new file mode 100644 index 000000000..43cb8ccb0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/utils/tilexyzkey/KeyToTileXYZ.js @@ -0,0 +1,19 @@ +var KeyToTileXYZ = function (key, out, separator) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globTileXYZ; + } + + if (separator === undefined) { + separator = ','; + } + var items = key.split(separator); + out.x = items[0]; + out.y = items[1]; + out.z = items[2]; + return out; +} + +var globTileXYZ = {}; +export default KeyToTileXYZ; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/utils/tilexyzkey/TileXYToKey.js b/ui/src/phaser3-rex-plugins/plugins/board/utils/tilexyzkey/TileXYToKey.js new file mode 100644 index 000000000..3a64f9c92 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/utils/tilexyzkey/TileXYToKey.js @@ -0,0 +1,7 @@ +var TileXYToKey = function (tileX, tileY, separator) { + if (separator === undefined) { + separator = ','; + } + return `${tileX}${separator}${tileY}`; +} +export default TileXYToKey; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/board/utils/tilexyzkey/TileXYZToKey.js b/ui/src/phaser3-rex-plugins/plugins/board/utils/tilexyzkey/TileXYZToKey.js new file mode 100644 index 000000000..5508e2f2e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/board/utils/tilexyzkey/TileXYZToKey.js @@ -0,0 +1,7 @@ +var TileXYZToKey = function (tileX, tileY, tileZ, separator) { + if (separator === undefined) { + separator = ','; + } + return `${tileX}${separator}${tileY}${separator}${tileZ}`; +} +export default TileXYZToKey; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/boids-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/boids-plugin.d.ts new file mode 100644 index 000000000..f27110fe5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/boids-plugin.d.ts @@ -0,0 +1,9 @@ +import Boids from './boids'; + +export default class BoidsPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Boids.IConfig + ): Boids; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/boids-plugin.js b/ui/src/phaser3-rex-plugins/plugins/boids-plugin.js new file mode 100644 index 000000000..5a26632e6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/boids-plugin.js @@ -0,0 +1,18 @@ +import Boids from './boids.js'; + +class BoidsPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Boids(gameObject, config); + } +} +export default BoidsPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/boids.d.ts b/ui/src/phaser3-rex-plugins/plugins/boids.d.ts new file mode 100644 index 000000000..6070dffcb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/boids.d.ts @@ -0,0 +1,2 @@ +import Boids from './behaviors/boids/Boids'; +export default Boids; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/boids.js b/ui/src/phaser3-rex-plugins/plugins/boids.js new file mode 100644 index 000000000..ae1e000a8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/boids.js @@ -0,0 +1,2 @@ +import Boids from './behaviors/boids/Boids.js'; +export default Boids; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bounds-plugin.js b/ui/src/phaser3-rex-plugins/plugins/bounds-plugin.js new file mode 100644 index 000000000..bed402497 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bounds-plugin.js @@ -0,0 +1,20 @@ +import Bounds from './bounds.js'; + +class BoundsPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Bounds(gameObject, config); + } + +} + +export default BoundsPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bounds.js b/ui/src/phaser3-rex-plugins/plugins/bounds.js new file mode 100644 index 000000000..01a70f717 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bounds.js @@ -0,0 +1,2 @@ +import Bounds from './behaviors/bounds/Bounds.js'; +export default Bounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bracketparser-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/bracketparser-plugin.d.ts new file mode 100644 index 000000000..d8e5cb502 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bracketparser-plugin.d.ts @@ -0,0 +1,8 @@ +import BracketParser from './bracketparser'; + +export default class BracketParserPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: BracketParser.IConfig + ): BracketParser; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bracketparser-plugin.js b/ui/src/phaser3-rex-plugins/plugins/bracketparser-plugin.js new file mode 100644 index 000000000..d05d583ab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bracketparser-plugin.js @@ -0,0 +1,18 @@ +import BracketParser from './bracketparser.js'; + +class BracketParserPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new BracketParser(config); + } +} + +export default BracketParserPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bracketparser.d.ts b/ui/src/phaser3-rex-plugins/plugins/bracketparser.d.ts new file mode 100644 index 000000000..42c377ff0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bracketparser.d.ts @@ -0,0 +1,2 @@ +import BracketParser from './logic/bracketparser/bracketparser/BracketParser'; +export default BracketParser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bracketparser.js b/ui/src/phaser3-rex-plugins/plugins/bracketparser.js new file mode 100644 index 000000000..528b159a8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bracketparser.js @@ -0,0 +1,2 @@ +import BracketParser from './logic/bracketparser/bracketparser/BracketParser.js'; +export default BracketParser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bracketparser2-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/bracketparser2-plugin.d.ts new file mode 100644 index 000000000..35c21e4e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bracketparser2-plugin.d.ts @@ -0,0 +1,8 @@ +import BracketParser from './bracketparser2'; + +export default class BracketParserPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: BracketParser.IConfig + ): BracketParser; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bracketparser2-plugin.js b/ui/src/phaser3-rex-plugins/plugins/bracketparser2-plugin.js new file mode 100644 index 000000000..c1583f3d3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bracketparser2-plugin.js @@ -0,0 +1,18 @@ +import BracketParser from './bracketparser2.js'; + +class BracketParserPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new BracketParser(config); + } +} + +export default BracketParserPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bracketparser2.d.ts b/ui/src/phaser3-rex-plugins/plugins/bracketparser2.d.ts new file mode 100644 index 000000000..cf23a4005 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bracketparser2.d.ts @@ -0,0 +1,2 @@ +import BracketParser from './logic/bracketparser/bracketparser2/BracketParser'; +export default BracketParser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bracketparser2.js b/ui/src/phaser3-rex-plugins/plugins/bracketparser2.js new file mode 100644 index 000000000..58aa5da06 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bracketparser2.js @@ -0,0 +1,2 @@ +import BracketParser from './logic/bracketparser/bracketparser2/BracketParser.js'; +export default BracketParser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/buffdata-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/buffdata-plugin.d.ts new file mode 100644 index 000000000..359870cc7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/buffdata-plugin.d.ts @@ -0,0 +1,11 @@ +import DataManager from './buffdata'; +import Extend from './data/buff/Extend'; + +export default class DataManagerPlugin extends Phaser.Plugins.BasePlugin { + add( + parent: object, + eventEmitter?: Phaser.Events.EventEmitter, + ): DataManager; + + extend: typeof Extend +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/buffdata-plugin.js b/ui/src/phaser3-rex-plugins/plugins/buffdata-plugin.js new file mode 100644 index 000000000..549667dec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/buffdata-plugin.js @@ -0,0 +1,24 @@ +import DataManager from './data/buff/DataManager.js'; +import Extend from './data/buff/Extend.js'; + +class DataManagerPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(parent, eventEmitter) { + return new DataManager(parent, eventEmitter); + } + + extend(dataManager) { + return Extend(dataManager); + } +} + +export default DataManagerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/buffdata.d.ts b/ui/src/phaser3-rex-plugins/plugins/buffdata.d.ts new file mode 100644 index 000000000..a20e27628 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/buffdata.d.ts @@ -0,0 +1,2 @@ +import DataManager from './data/buff/DataManager'; +export default DataManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/buffdata.js b/ui/src/phaser3-rex-plugins/plugins/buffdata.js new file mode 100644 index 000000000..e5e824262 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/buffdata.js @@ -0,0 +1,2 @@ +import DataManager from './data/buff/DataManager.js'; +export default DataManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/buildarcadeobject-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/buildarcadeobject-plugin.d.ts new file mode 100644 index 000000000..b5d71457f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/buildarcadeobject-plugin.d.ts @@ -0,0 +1,6 @@ +import BuildArcadeObject from './buildarcadeobject'; + +export default class BuildArcadeObjectPlugin extends Phaser.Plugins.BasePlugin { + build: typeof BuildArcadeObject + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/buildarcadeobject-plugin.js b/ui/src/phaser3-rex-plugins/plugins/buildarcadeobject-plugin.js new file mode 100644 index 000000000..6ce193fa9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/buildarcadeobject-plugin.js @@ -0,0 +1,19 @@ +import BuildArcadeObject from './buildarcadeobject.js'; + +class BuildArcadeObjectPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + build(gameObject, isStatic) { + return BuildArcadeObject(gameObject, isStatic); + } +} + +export default BuildArcadeObjectPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/buildarcadeobject.d.ts b/ui/src/phaser3-rex-plugins/plugins/buildarcadeobject.d.ts new file mode 100644 index 000000000..321651837 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/buildarcadeobject.d.ts @@ -0,0 +1,2 @@ +import BuildArcadeObject from './utils/arcade/BuildArcadeObject'; +export default BuildArcadeObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/buildarcadeobject.js b/ui/src/phaser3-rex-plugins/plugins/buildarcadeobject.js new file mode 100644 index 000000000..65ac9df68 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/buildarcadeobject.js @@ -0,0 +1,2 @@ +import BuildArcadeObject from './utils/arcade/BuildArcadeObject.js'; +export default BuildArcadeObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bullet-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/bullet-plugin.d.ts new file mode 100644 index 000000000..c61ee9f26 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bullet-plugin.d.ts @@ -0,0 +1,9 @@ +import Bullet from './bullet'; + +export default class BulletPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Bullet.IConfig + ): Bullet; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bullet-plugin.js b/ui/src/phaser3-rex-plugins/plugins/bullet-plugin.js new file mode 100644 index 000000000..a59149c5f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bullet-plugin.js @@ -0,0 +1,20 @@ +import Bullet from './bullet.js'; + +class BulletPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Bullet(gameObject, config); + } + +} + +export default BulletPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bullet.d.ts b/ui/src/phaser3-rex-plugins/plugins/bullet.d.ts new file mode 100644 index 000000000..cefcd6ad8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bullet.d.ts @@ -0,0 +1,2 @@ +import Bullet from './behaviors/bullet/Bullet'; +export default Bullet; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/bullet.js b/ui/src/phaser3-rex-plugins/plugins/bullet.js new file mode 100644 index 000000000..e631247df --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/bullet.js @@ -0,0 +1,2 @@ +import Bullet from './behaviors/bullet/Bullet.js'; +export default Bullet; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/button-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/button-plugin.d.ts new file mode 100644 index 000000000..7e33447ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/button-plugin.d.ts @@ -0,0 +1,9 @@ +import Button from './button'; + +export default class ButtonPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Button.IConfig + ): Button; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/button-plugin.js b/ui/src/phaser3-rex-plugins/plugins/button-plugin.js new file mode 100644 index 000000000..00c8dfe75 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/button-plugin.js @@ -0,0 +1,20 @@ +import Button from './button.js'; + +class ButtonPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Button(gameObject, config); + } + +} + +export default ButtonPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/button.d.ts b/ui/src/phaser3-rex-plugins/plugins/button.d.ts new file mode 100644 index 000000000..5ec0cf4b3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/button.d.ts @@ -0,0 +1,2 @@ +import Button from './input/button/Button'; +export default Button; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/button.js b/ui/src/phaser3-rex-plugins/plugins/button.js new file mode 100644 index 000000000..737893eed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/button.js @@ -0,0 +1,2 @@ +import Button from './input/button/Button.js'; +export default Button; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvas-plugin.js b/ui/src/phaser3-rex-plugins/plugins/canvas-plugin.js new file mode 100644 index 000000000..59802b274 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvas-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/canvas/canvas/Factory.js'; +import Creator from './gameobjects/canvas/canvas/Creator.js'; +import Canvas from './gameobjects/canvas/canvas/Canvas.js'; +import SetValue from './utils/object/SetValue.js'; + +class CanvasPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexCanvas', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.Canvas', Canvas); + +export default CanvasPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvas.d.ts b/ui/src/phaser3-rex-plugins/plugins/canvas.d.ts new file mode 100644 index 000000000..832d2f676 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvas.d.ts @@ -0,0 +1,2 @@ +import Canvas from './gameobjects/canvas/canvas/Canvas'; +export default Canvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvas.js b/ui/src/phaser3-rex-plugins/plugins/canvas.js new file mode 100644 index 000000000..bfa9ce6fd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvas.js @@ -0,0 +1,2 @@ +import Canvas from './gameobjects/canvas/canvas/Canvas.js'; +export default Canvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvasdata-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/canvasdata-plugin.d.ts new file mode 100644 index 000000000..dbc0b119f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvasdata-plugin.d.ts @@ -0,0 +1,8 @@ +import CanvasObjectToBitmap from './data/canvasdata/CanvasObjectToBitmap'; +import TextureTColorMap from './data/canvasdata/TextureToColormap'; + +export default class CanvasDataPlugin extends Phaser.Plugins.BasePlugin { + textObjectToBitmap: typeof CanvasObjectToBitmap; + canvasObjectToBitmap: typeof CanvasObjectToBitmap; + textureTColorMap: typeof TextureTColorMap; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvasdata-plugin.js b/ui/src/phaser3-rex-plugins/plugins/canvasdata-plugin.js new file mode 100644 index 000000000..adeab6b04 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvasdata-plugin.js @@ -0,0 +1,34 @@ +import Methods from './canvasdata.js'; + +const CanvasPool = Phaser.Display.Canvas.CanvasPool; + +class CanvasDataPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + + this._tmpCanvas = CanvasPool.create2D(this); + } + + destroy() { + CanvasPool.remove(this._tmpCanvas); + this._tmpCanvas = undefined; + super.destroy(); + } + + get textureManager() { + return this.game.textures; + } +} + +Object.assign( + CanvasDataPlugin.prototype, + Methods +); + +export default CanvasDataPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvasdata.d.ts b/ui/src/phaser3-rex-plugins/plugins/canvasdata.d.ts new file mode 100644 index 000000000..8e83fa61c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvasdata.d.ts @@ -0,0 +1,10 @@ +import CanvasObjectToBitmap from './data/canvasdata/CanvasObjectToBitmap'; +import TextureTColorMap from './data/canvasdata/TextureToColormap'; + +declare var Methods: { + textObjectToBitmap: typeof CanvasObjectToBitmap, + canvasObjectToBitmap: typeof CanvasObjectToBitmap, + textureTColorMap: typeof TextureTColorMap, +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvasdata.js b/ui/src/phaser3-rex-plugins/plugins/canvasdata.js new file mode 100644 index 000000000..d37c960f3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvasdata.js @@ -0,0 +1,2 @@ +import Methods from './data/canvasdata/Methods.js'; +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvasframemanager-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/canvasframemanager-plugin.d.ts new file mode 100644 index 000000000..0e149c73a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvasframemanager-plugin.d.ts @@ -0,0 +1,19 @@ +import CanvasFrameManager from './canvasframemanager'; + +export default class CanvasFrameManagerPlugin extends Phaser.Plugins.BasePlugin { + add( + scene: Phaser.Scene, + key: string, + width?: number, + height?: number, + cellWidth?: number, + cellHeight?: number, + fillColor?: string + ): CanvasFrameManager; + + add( + scene: Phaser.Scene, + config: CanvasFrameManager.IConfig + ): CanvasFrameManager; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvasframemanager-plugin.js b/ui/src/phaser3-rex-plugins/plugins/canvasframemanager-plugin.js new file mode 100644 index 000000000..b540b03c2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvasframemanager-plugin.js @@ -0,0 +1,19 @@ +import CanvasFrameManager from './canvasframemanager.js'; + +class CanvasFrameManagerPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, key, width, height, cellWidth, cellHeight, fillColor) { + return new CanvasFrameManager(scene, key, width, height, cellWidth, cellHeight, fillColor); + } +} + +export default CanvasFrameManagerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvasframemanager.d.ts b/ui/src/phaser3-rex-plugins/plugins/canvasframemanager.d.ts new file mode 100644 index 000000000..aac6907ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvasframemanager.d.ts @@ -0,0 +1,2 @@ +import CanvasFrameManager from './texture/canvasframemanager/CanvasFrameManager'; +export default CanvasFrameManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvasframemanager.js b/ui/src/phaser3-rex-plugins/plugins/canvasframemanager.js new file mode 100644 index 000000000..5f1dbe0a3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvasframemanager.js @@ -0,0 +1,2 @@ +import CanvasFrameManager from './texture/CanvasFrameManager/CanvasFrameManager.js'; +export default CanvasFrameManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvasinput-plugin.js b/ui/src/phaser3-rex-plugins/plugins/canvasinput-plugin.js new file mode 100644 index 000000000..22d4ff704 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvasinput-plugin.js @@ -0,0 +1,24 @@ +import CanvasInputFactory from './gameobjects/dynamictext/canvasinput/Factory'; +import CanvasInputCreator from './gameobjects/dynamictext/canvasinput/Creator.js'; +import CanvasInput from './gameobjects/dynamictext/canvasinput/CanvasInput.js'; + +import SetValue from './utils/object/SetValue.js'; + +class CanvasInputPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexCanvasInput', CanvasInputFactory, CanvasInputCreator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.CanvasInput', CanvasInput); + +export default CanvasInputPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvasinput.d.ts b/ui/src/phaser3-rex-plugins/plugins/canvasinput.d.ts new file mode 100644 index 000000000..f9a8b7915 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvasinput.d.ts @@ -0,0 +1,2 @@ +import CanvasInput from './gameobjects/dynamictext/canvasinput/CanvasInput'; +export default CanvasInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/canvasinput.js b/ui/src/phaser3-rex-plugins/plugins/canvasinput.js new file mode 100644 index 000000000..721c242b4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/canvasinput.js @@ -0,0 +1,2 @@ +import CanvasInput from './gameobjects/dynamictext/canvasinput/CanvasInput.js'; +export default CanvasInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/carousel-plugin.js b/ui/src/phaser3-rex-plugins/plugins/carousel-plugin.js new file mode 100644 index 000000000..f60d8ab7c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/carousel-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/container/carousel/Factory.js'; +import Creator from './gameobjects/container/carousel/Creator.js'; +import Carousel from './gameobjects/container/carousel/Carousel.js'; +import SetValue from './utils/object/SetValue.js'; + +class CarouselPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexCarousel', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.Carousel', Carousel); + +export default CarouselPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/carousel.js b/ui/src/phaser3-rex-plugins/plugins/carousel.js new file mode 100644 index 000000000..315cf76da --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/carousel.js @@ -0,0 +1,2 @@ +import Carousel from './gameobjects/container/carousel/Carousel.js'; +export default Carousel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/charactercache-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/charactercache-plugin.d.ts new file mode 100644 index 000000000..74673f76f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/charactercache-plugin.d.ts @@ -0,0 +1,9 @@ +import CharacterCache from './charactercache'; + +export default class CharacterCachePlugin extends Phaser.Plugins.BasePlugin { + add( + scene: Phaser.Scene, + config: CharacterCache.IConfig + ): CharacterCache; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/charactercache-plugin.js b/ui/src/phaser3-rex-plugins/plugins/charactercache-plugin.js new file mode 100644 index 000000000..b4856ac25 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/charactercache-plugin.js @@ -0,0 +1,19 @@ +import CharacterCache from './charactercache.js'; + +class CharacterCachePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new CharacterCache(scene, config); + } +} + +export default CharacterCachePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/charactercache.d.ts b/ui/src/phaser3-rex-plugins/plugins/charactercache.d.ts new file mode 100644 index 000000000..da95671f8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/charactercache.d.ts @@ -0,0 +1,2 @@ +import CharacterCache from './texture/charactercache/CharacterCache'; +export default CharacterCache; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/charactercache.js b/ui/src/phaser3-rex-plugins/plugins/charactercache.js new file mode 100644 index 000000000..90db3abd5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/charactercache.js @@ -0,0 +1,2 @@ +import CharacterCache from './texture/charactercache/CharacterCache.js'; +export default CharacterCache; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/checkbox-plugin.js b/ui/src/phaser3-rex-plugins/plugins/checkbox-plugin.js new file mode 100644 index 000000000..ad39adf6a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/checkbox-plugin.js @@ -0,0 +1,28 @@ +import Factory from './gameobjects/shape/checkbox/Factory.js'; +import Creator from './gameobjects/shape/checkbox/Creator.js'; +import Checkbox from './gameobjects/shape/checkbox/Checkbox.js'; +import CheckboxShapeFactory from './gameobjects/shape/checkbox/CheckboxShapeFactory.js'; +import CheckboxShapeCreator from './gameobjects/shape/checkbox/CheckboxShapeCreator.js'; +import CheckboxShape from './gameobjects/shape/checkbox/CheckboxShape.js'; +import SetValue from './utils/object/SetValue.js'; + +class CheckboxPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexCheckbox', Factory, Creator); + pluginManager.registerGameObject('rexCheckboxShape', CheckboxShapeFactory, CheckboxShapeCreator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.Checkbox', Checkbox); +SetValue(window, 'RexPlugins.GameObjects.CheckboxShape', CheckboxShape); + +export default CheckboxPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/checkbox.d.ts b/ui/src/phaser3-rex-plugins/plugins/checkbox.d.ts new file mode 100644 index 000000000..12dc7ce2c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/checkbox.d.ts @@ -0,0 +1,2 @@ +import Checkbox from './gameobjects/shape/checkbox/Checkbox'; +export default Checkbox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/checkbox.js b/ui/src/phaser3-rex-plugins/plugins/checkbox.js new file mode 100644 index 000000000..dfda9bdd4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/checkbox.js @@ -0,0 +1,2 @@ +import Checkbox from './gameobjects/shape/checkbox/Checkbox.js'; +export default Checkbox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/checkboxshape.d.ts b/ui/src/phaser3-rex-plugins/plugins/checkboxshape.d.ts new file mode 100644 index 000000000..c18f1180c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/checkboxshape.d.ts @@ -0,0 +1,2 @@ +import CheckboxShape from './gameobjects/shape/checkbox/CheckboxShape'; +export default CheckboxShape; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/checkboxshape.js b/ui/src/phaser3-rex-plugins/plugins/checkboxshape.js new file mode 100644 index 000000000..8fd1f63e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/checkboxshape.js @@ -0,0 +1,2 @@ +import CheckboxShape from './gameobjects/shape/checkbox/CheckboxShape.js'; +export default CheckboxShape; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/circlemaskimage-plugin.js b/ui/src/phaser3-rex-plugins/plugins/circlemaskimage-plugin.js new file mode 100644 index 000000000..22fd9b671 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/circlemaskimage-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/canvas/circlemaskimage/Factory.js'; +import Creator from './gameobjects/canvas/circlemaskimage/Creator.js'; +import CircleMaskImage from './gameobjects/canvas/circlemaskimage/CircleMaskImage.js'; +import SetValue from './utils/object/SetValue.js'; + +class CircleMaskImagePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexCircleMaskImage', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.CircleMaskImage', CircleMaskImage); + +export default CircleMaskImagePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/circlemaskimage.d.ts b/ui/src/phaser3-rex-plugins/plugins/circlemaskimage.d.ts new file mode 100644 index 000000000..c749f37be --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/circlemaskimage.d.ts @@ -0,0 +1,2 @@ +import CircleMaskImage from './gameobjects/canvas/circlemaskimage/CircleMaskImage'; +export default CircleMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/circlemaskimage.js b/ui/src/phaser3-rex-plugins/plugins/circlemaskimage.js new file mode 100644 index 000000000..fb90716b9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/circlemaskimage.js @@ -0,0 +1,2 @@ +import CircleMaskImage from './gameobjects/canvas/circlemaskimage/CircleMaskImage.js'; +export default CircleMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/circularprogress-plugin.js b/ui/src/phaser3-rex-plugins/plugins/circularprogress-plugin.js new file mode 100644 index 000000000..0677893f0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/circularprogress-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/shape/circularprogress/Factory.js'; +import Creator from './gameobjects/shape/circularprogress/Creator.js'; +import CircularProgress from './gameobjects/shape/circularprogress/CircularProgress.js'; +import SetValue from './utils/object/SetValue.js'; + +class CircularProgressPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexCircularProgress', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.CircularProgress', CircularProgress); + +export default CircularProgressPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/circularprogress.d.ts b/ui/src/phaser3-rex-plugins/plugins/circularprogress.d.ts new file mode 100644 index 000000000..f604f5db8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/circularprogress.d.ts @@ -0,0 +1,2 @@ +import CircularProgress from './gameobjects/shape/circularprogress/CircularProgress'; +export default CircularProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/circularprogress.js b/ui/src/phaser3-rex-plugins/plugins/circularprogress.js new file mode 100644 index 000000000..d0d4b8029 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/circularprogress.js @@ -0,0 +1,2 @@ +import CircularProgress from './gameobjects/shape/circularprogress/CircularProgress.js'; +export default CircularProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/circularprogresscanvas-plugin.js b/ui/src/phaser3-rex-plugins/plugins/circularprogresscanvas-plugin.js new file mode 100644 index 000000000..92d5e13f3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/circularprogresscanvas-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/canvas/circularprogress/Factory.js'; +import Creator from './gameobjects/canvas/circularprogress/Creator.js'; +import CircularProgressCanvas from './gameobjects/canvas/circularprogress/CircularProgress.js'; +import SetValue from './utils/object/SetValue.js'; + +class CircularProgressCanvasPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexCircularProgressCanvas', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.CircularProgressCanvas', CircularProgressCanvas); + +export default CircularProgressCanvasPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/circularprogresscanvas.d.ts b/ui/src/phaser3-rex-plugins/plugins/circularprogresscanvas.d.ts new file mode 100644 index 000000000..51b3c3ed6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/circularprogresscanvas.d.ts @@ -0,0 +1,2 @@ +import CircularProgressCanvas from './gameobjects/canvas/circularprogress/CircularProgress'; +export default CircularProgressCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/circularprogresscanvas.js b/ui/src/phaser3-rex-plugins/plugins/circularprogresscanvas.js new file mode 100644 index 000000000..78cdca507 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/circularprogresscanvas.js @@ -0,0 +1,2 @@ +import CircularProgressCanvas from './gameobjects/canvas/circularprogress/CircularProgress.js'; +export default CircularProgressCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/clickoutside-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/clickoutside-plugin.d.ts new file mode 100644 index 000000000..3e14c9772 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/clickoutside-plugin.d.ts @@ -0,0 +1,9 @@ +import ClickOutside from './clickoutside'; + +export default class ClickOutsidePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: ClickOutside.IConfig + ): ClickOutside; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/clickoutside-plugin.js b/ui/src/phaser3-rex-plugins/plugins/clickoutside-plugin.js new file mode 100644 index 000000000..35178b742 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/clickoutside-plugin.js @@ -0,0 +1,20 @@ +import ClickOutside from './clickoutside'; + +class ClickOutsidePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new ClickOutside(gameObject, config); + } + +} + +export default ClickOutsidePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/clickoutside.d.ts b/ui/src/phaser3-rex-plugins/plugins/clickoutside.d.ts new file mode 100644 index 000000000..fcbb03cb5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/clickoutside.d.ts @@ -0,0 +1,2 @@ +import ClickOutside from './input/clickoutside/ClickOutside'; +export default ClickOutside; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/clickoutside.js b/ui/src/phaser3-rex-plugins/plugins/clickoutside.js new file mode 100644 index 000000000..b033f5f79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/clickoutside.js @@ -0,0 +1,2 @@ +import ClickOutside from './input/clickoutside/ClickOutside.js'; +export default ClickOutside; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/clock-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/clock-plugin.d.ts new file mode 100644 index 000000000..1c6d503ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/clock-plugin.d.ts @@ -0,0 +1,9 @@ +import Clock from './clock'; + +export default class ClockPlugin extends Phaser.Plugins.BasePlugin { + add( + parent: Phaser.Scene | Phaser.GameObjects.GameObject, + config?: Clock.IConfig + ): Clock; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/clock-plugin.js b/ui/src/phaser3-rex-plugins/plugins/clock-plugin.js new file mode 100644 index 000000000..4b5cb0671 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/clock-plugin.js @@ -0,0 +1,20 @@ +import Clock from './clock.js'; + +class ClockPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new Clock(scene, config); + } + +} + +export default ClockPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/clock.d.ts b/ui/src/phaser3-rex-plugins/plugins/clock.d.ts new file mode 100644 index 000000000..f1575f4e1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/clock.d.ts @@ -0,0 +1,2 @@ +import Clock from './time/clock/Clock'; +export default Clock; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/clock.js b/ui/src/phaser3-rex-plugins/plugins/clock.js new file mode 100644 index 000000000..edf6c603a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/clock.js @@ -0,0 +1,2 @@ +import Clock from './time/clock/Clock.js'; +export default Clock; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline-plugin.d.ts new file mode 100644 index 000000000..736db5fe2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline-plugin.d.ts @@ -0,0 +1,30 @@ +// import * as Phaser from 'phaser'; +import ColorReplacePostFxPipeline from './colorreplacepipeline'; + + +export default ColorReplacePipelinePlugin; + +declare namespace ColorReplacePipelinePlugin { + + interface IConfig extends ColorReplacePostFxPipeline.IConfig { + name?: string, + } + +} + +declare class ColorReplacePipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: ColorReplacePipelinePlugin.IConfig + ): ColorReplacePostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): ColorReplacePostFxPipeline | ColorReplacePostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline-plugin.js new file mode 100644 index 000000000..882977c15 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline-plugin.js @@ -0,0 +1,14 @@ +import ColorReplacePostFxPipeline from './colorreplacepipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class ColorReplacePipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(ColorReplacePostFxPipeline, 'rexColorReplacePostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.ColorReplacePostFx', ColorReplacePostFxPipeline); + +export default ColorReplacePipelinePlugin; diff --git a/ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline.d.ts new file mode 100644 index 000000000..ddcfc1f0c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline.d.ts @@ -0,0 +1,2 @@ +import ColorReplacePostFxPipeline from './shaders/colorreplace/ColorReplacePostFxPipeline'; +export default ColorReplacePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline.js b/ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline.js new file mode 100644 index 000000000..9f770a18f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/colorreplacepipeline.js @@ -0,0 +1,2 @@ +import ColorReplacePostFxPipeline from './shaders/colorreplace/ColorReplacePostFxPipeline.js'; +export default ColorReplacePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/conditionstable-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/conditionstable-plugin.d.ts new file mode 100644 index 000000000..3afa74fce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/conditionstable-plugin.d.ts @@ -0,0 +1,6 @@ +import ConditionsTable from './conditionstable'; + +export default class ConditionsTablePlugin extends Phaser.Plugins.BasePlugin { + add(): ConditionsTable; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/conditionstable-plugin.js b/ui/src/phaser3-rex-plugins/plugins/conditionstable-plugin.js new file mode 100644 index 000000000..7b6fca46f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/conditionstable-plugin.js @@ -0,0 +1,18 @@ +import ConditionsTable from './conditionstable.js' + +class ConditionsTablePlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add() { + return new ConditionsTable(); + } +} + +export default ConditionsTablePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/conditionstable.d.ts b/ui/src/phaser3-rex-plugins/plugins/conditionstable.d.ts new file mode 100644 index 000000000..63181a014 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/conditionstable.d.ts @@ -0,0 +1,2 @@ +import ConditionsTable from './logic/conditionstable/csvconditiontable/ConditionsTable'; +export default ConditionsTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/conditionstable.js b/ui/src/phaser3-rex-plugins/plugins/conditionstable.js new file mode 100644 index 000000000..4ead37c12 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/conditionstable.js @@ -0,0 +1,2 @@ +import ConditionsTable from './logic/conditionstable/csvconditiontable/ConditionsTable.js'; +export default ConditionsTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/containerlite-plugin.js b/ui/src/phaser3-rex-plugins/plugins/containerlite-plugin.js new file mode 100644 index 000000000..bac1f94a2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/containerlite-plugin.js @@ -0,0 +1,27 @@ +import Factory from './gameobjects/container/containerlite/Factory.js'; +import Creator from './gameobjects/container/containerlite/Creator.js'; +import ContainerLite from './gameobjects/container/containerlite/ContainerLite.js'; +import SetValue from './utils/object/SetValue.js'; + +class ContainerLitePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexContainerLite', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + getParent(child) { + return ContainerLite.GetParent(child); + } +} + +SetValue(window, 'RexPlugins.GameObjects.ContainerLite', ContainerLite); + +export default ContainerLitePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/containerlite.d.ts b/ui/src/phaser3-rex-plugins/plugins/containerlite.d.ts new file mode 100644 index 000000000..e8416ad9b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/containerlite.d.ts @@ -0,0 +1,2 @@ +import ContainerLite from './gameobjects/container/containerlite/ContainerLite'; +export default ContainerLite; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/containerlite.js b/ui/src/phaser3-rex-plugins/plugins/containerlite.js new file mode 100644 index 000000000..59a8eed73 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/containerlite.js @@ -0,0 +1,2 @@ +import ContainerLite from './gameobjects/container/containerlite/ContainerLite.js'; +export default ContainerLite; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/cover-plugin.js b/ui/src/phaser3-rex-plugins/plugins/cover-plugin.js new file mode 100644 index 000000000..f9d9a37b7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/cover-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/shape/cover/Factory.js'; +import Creator from './gameobjects/shape/cover/Creator.js'; +import Cover from './gameobjects/shape/cover/Cover.js'; +import SetValue from './utils/object/SetValue.js'; + +class CoverPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexCover', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.Cover', Cover); + +export default CoverPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/cover.d.ts b/ui/src/phaser3-rex-plugins/plugins/cover.d.ts new file mode 100644 index 000000000..55ab3b3ed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/cover.d.ts @@ -0,0 +1,2 @@ +import Cover from './gameobjects/shape/cover/Cover.js'; +export default Cover; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/cover.js b/ui/src/phaser3-rex-plugins/plugins/cover.js new file mode 100644 index 000000000..55ab3b3ed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/cover.js @@ -0,0 +1,2 @@ +import Cover from './gameobjects/shape/cover/Cover.js'; +export default Cover; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline-plugin.d.ts new file mode 100644 index 000000000..9a0e70b63 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline-plugin.d.ts @@ -0,0 +1,30 @@ +// import * as Phaser from 'phaser'; +import CrossStitchingPostFxPipeline from './crossstitchingpipeline'; + + +export default CrossStitchingPipelinePlugin; + +declare namespace CrossStitchingPipelinePlugin { + + interface IConfig extends CrossStitchingPostFxPipeline.IConfig { + name?: string, + } + +} + +declare class CrossStitchingPipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: CrossStitchingPipelinePlugin.IConfig + ): CrossStitchingPostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): CrossStitchingPostFxPipeline | CrossStitchingPostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline-plugin.js new file mode 100644 index 000000000..1c8ead1b0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline-plugin.js @@ -0,0 +1,14 @@ +import CrossStitchingPostFxPipeline from './crossstitchingpipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class CrossStitchingPipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(CrossStitchingPostFxPipeline, 'rexCrossStitchingPostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.CrossStitchingPostFx', CrossStitchingPostFxPipeline); + +export default CrossStitchingPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline.d.ts new file mode 100644 index 000000000..c9051a0df --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline.d.ts @@ -0,0 +1,2 @@ +import CrossStitchingPostFxPipeline from './shaders/crossstitching/CrossStitchingPostFxPipeline.js'; +export default CrossStitchingPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline.js b/ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline.js new file mode 100644 index 000000000..c9051a0df --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/crossstitchingpipeline.js @@ -0,0 +1,2 @@ +import CrossStitchingPostFxPipeline from './shaders/crossstitching/CrossStitchingPostFxPipeline.js'; +export default CrossStitchingPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/csvscenario-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/csvscenario-plugin.d.ts new file mode 100644 index 000000000..c4a2482bd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/csvscenario-plugin.d.ts @@ -0,0 +1,9 @@ +import CSVScenario from './csvscenario'; + +export default class CSVScenarioPlugin extends Phaser.Plugins.BasePlugin { + add( + scene: Phaser.Scene, + config?: CSVScenario.IConfig + ): CSVScenario; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/csvscenario-plugin.js b/ui/src/phaser3-rex-plugins/plugins/csvscenario-plugin.js new file mode 100644 index 000000000..b3a2b87e2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/csvscenario-plugin.js @@ -0,0 +1,18 @@ +import CSVScenario from './csvscenario.js'; + +class CSVScenarioPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new CSVScenario(scene, config); + } +} + +export default CSVScenarioPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/csvscenario.d.ts b/ui/src/phaser3-rex-plugins/plugins/csvscenario.d.ts new file mode 100644 index 000000000..b5cbb262e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/csvscenario.d.ts @@ -0,0 +1,2 @@ +import CSVScenario from './logic/runcommands/csvscenario/CSVScenario'; +export default CSVScenario; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/csvscenario.js b/ui/src/phaser3-rex-plugins/plugins/csvscenario.js new file mode 100644 index 000000000..f1fbead09 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/csvscenario.js @@ -0,0 +1,2 @@ +import CSVScenario from './logic/runcommands/csvscenario/CSVScenario.js'; +export default CSVScenario; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/csvtoarray-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/csvtoarray-plugin.d.ts new file mode 100644 index 000000000..471bd0b93 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/csvtoarray-plugin.d.ts @@ -0,0 +1,9 @@ +import CSVToArray from './csvtoarray'; + +export default class CSVToArrayPlugin extends Phaser.Plugins.BasePlugin { + convert( + csvString: string, + config?: CSVToArray.IConfig + ): any[][]; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/csvtoarray-plugin.js b/ui/src/phaser3-rex-plugins/plugins/csvtoarray-plugin.js new file mode 100644 index 000000000..d1f439fef --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/csvtoarray-plugin.js @@ -0,0 +1,18 @@ +import CSVToArray from './csvtoarray.js' + +class CSVToArrayPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + convert(csvString, config) { + return CSVToArray(csvString, config); + } +} + +export default CSVToArrayPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/csvtoarray.d.ts b/ui/src/phaser3-rex-plugins/plugins/csvtoarray.d.ts new file mode 100644 index 000000000..f99d1e698 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/csvtoarray.d.ts @@ -0,0 +1,2 @@ +import CSVToArray from './data/csvtoarray/CSVToArray'; +export default CSVToArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/csvtoarray.js b/ui/src/phaser3-rex-plugins/plugins/csvtoarray.js new file mode 100644 index 000000000..69aa8cff4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/csvtoarray.js @@ -0,0 +1,2 @@ +import CSVToArray from './data/csvtoarray/CSVToArray.js'; +export default CSVToArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/csvtohashtable-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/csvtohashtable-plugin.d.ts new file mode 100644 index 000000000..17aae8624 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/csvtohashtable-plugin.d.ts @@ -0,0 +1,6 @@ +import CSVToHashTable from './csvtohashtable'; + +export default class CSVToHashTablePlugin extends Phaser.Plugins.BasePlugin { + add(): CSVToHashTable; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/csvtohashtable-plugin.js b/ui/src/phaser3-rex-plugins/plugins/csvtohashtable-plugin.js new file mode 100644 index 000000000..97aa88109 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/csvtohashtable-plugin.js @@ -0,0 +1,18 @@ +import CSVToHashTable from './csvtohashtable.js'; + +class CSVToHashTablePlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new CSVToHashTable(config); + } +} + +export default CSVToHashTablePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/csvtohashtable.d.ts b/ui/src/phaser3-rex-plugins/plugins/csvtohashtable.d.ts new file mode 100644 index 000000000..6deb1d487 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/csvtohashtable.d.ts @@ -0,0 +1,2 @@ +import CSVToHashTable from './data/csvtohashtable/CsvToHashTable'; +export default CSVToHashTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/csvtohashtable.js b/ui/src/phaser3-rex-plugins/plugins/csvtohashtable.js new file mode 100644 index 000000000..748db3a03 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/csvtohashtable.js @@ -0,0 +1,2 @@ +import CSVToHashTable from './data/csvtohashtable/CsvToHashTable.js'; +export default CSVToHashTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/cursoratbound-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/cursoratbound-plugin.d.ts new file mode 100644 index 000000000..f7f8342c8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/cursoratbound-plugin.d.ts @@ -0,0 +1,9 @@ +import CursorAtBound from './cursoratbound'; + +export default class CursorAtBoundPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: CursorAtBound.IConfig + ): CursorAtBound; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/cursoratbound-plugin.js b/ui/src/phaser3-rex-plugins/plugins/cursoratbound-plugin.js new file mode 100644 index 000000000..de774dc06 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/cursoratbound-plugin.js @@ -0,0 +1,20 @@ +import CursorAtBound from './cursoratbound.js'; + +class CursorAtBoundPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new CursorAtBound(scene, config); + } + +} + +export default CursorAtBoundPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/cursoratbound.d.ts b/ui/src/phaser3-rex-plugins/plugins/cursoratbound.d.ts new file mode 100644 index 000000000..bfbdcd41f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/cursoratbound.d.ts @@ -0,0 +1,2 @@ +import CursorAtBound from './input/cursoratbound/CursorAtBound'; +export default CursorAtBound; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/cursoratbound.js b/ui/src/phaser3-rex-plugins/plugins/cursoratbound.js new file mode 100644 index 000000000..8ebc08c13 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/cursoratbound.js @@ -0,0 +1,2 @@ +import CursorAtBound from './input/cursoratbound/CursorAtBound.js'; +export default CursorAtBound; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/curve/SpiralCurve.d.ts b/ui/src/phaser3-rex-plugins/plugins/curve/SpiralCurve.d.ts new file mode 100644 index 000000000..18d1bc989 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/curve/SpiralCurve.d.ts @@ -0,0 +1,63 @@ +export default SpiralCurve; + +declare namespace SpiralCurve { + interface IConfig { + // Origin point + // Ease origin point + startX?: number, endX?: number, easeX?: string, + startY?: number, endY?: number, easeY?: string, + // Fixed point + x?: number, y?: number, + + // x-radius + startXRadius?: number, endXRadius?: number, easeXRadius?: string, + // y-radius + startYRadiu?: number, endYRadius?: number, easeYRadius?: string, + // start-end radius + startRadius?: number, endRadiux?: number + + // angle + startAngle?: number, endAngle?: number, easeAngle?: string, + + rotation?: number + } +} + +declare class SpiralCurve extends Phaser.Curves.Curve { + constructor( + config?: SpiralCurve.IConfig + ); + + constructor( + x?: number, y?: number, + startRadius?: number, endRadius?: number, + startAngle?: number, endAngle?: number, + rotation?: number + ); + + setStartX(x: number): this; + setStartY(x: number): this; + startX: number; + startY: number; + readonly p0: { x: number, y: number }; + + setEndX(x: number): this; + setEndY(x: number): this; + endX: number; + endY: number; + readonly p1: { x: number, y: number }; + + setStartXRadius(radius: number): this; + setStartYRadius(radius: number): this; + startXRadius: number; + startYRadius: number; + setEndXRadius(radius: number): this; + setEndYRadius(radius: number): this; + endXRadius: number; + endYRadius: number; + + setStartAngle(degrees: number): this; + setEndAngle(degrees: number): this; + startAngle: number; + endAngle: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/curve/SpiralCurve.js b/ui/src/phaser3-rex-plugins/plugins/curve/SpiralCurve.js new file mode 100644 index 000000000..621eab6c8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/curve/SpiralCurve.js @@ -0,0 +1,469 @@ +const Base = Phaser.Curves.Curve; +const GetValue = Phaser.Utils.Objects.GetValue; +const DegToRad = Phaser.Math.DegToRad; +const RadToDeg = Phaser.Math.RadToDeg; +const Vector2 = Phaser.Math.Vector2; +const GetEaseFunction = Phaser.Tweens.Builders.GetEaseFunction; +const Linear = Phaser.Math.Linear; + +class SpiralCurve extends Base { + constructor(x, y, startRadius, endRadius, startAngle, endAngle, rotation) { + var startX, endX, easeX; + var startY, endY, easeY; + var startXRadius, endXRadius, easeXRadius; + var startYRadius, endYRadius, easeYRadius; + var easeAngle; + + if (typeof(x) === 'object') { + var config = x; + if (config.hasOwnProperty('x')) { + startX = config.x; + endX = startX; + } else { + startX = GetValue(config, 'startX', 0); + endX = GetValue(config, 'endX', startX); + } + easeX = GetValue(config, 'easeX', 'Linear'); + + if (config.hasOwnProperty('y')) { + startY = config.y; + endY = startY; + } else { + startY = GetValue(config, 'startY', 0); + endY = GetValue(config, 'endY', startY); + } + easeY = GetValue(config, 'easeY', 'Linear'); + + if (config.hasOwnProperty('startRadius')) { + startXRadius = config.startRadius; + startYRadius = startXRadius; + endXRadius = GetValue(config, 'endRadius', startXRadius); + endYRadius = endXRadius; + } else { + if (config.hasOwnProperty('xRadius')) { + startXRadius = config.xRadius; + endXRadius = startXRadius; + } else { + startXRadius = GetValue(config, 'startXRadius', 0); + endXRadius = GetValue(config, 'endXRadius', startXRadius); + } + if (config.hasOwnProperty('yRadius')) { + startYRadius = config.yRadius; + endYRadius = startYRadius; + } else { + startYRadius = GetValue(config, 'startYRadius', startXRadius); + endYRadius = GetValue(config, 'endYRadius', endXRadius); + } + } + easeXRadius = GetValue(config, 'easeXRadius', 'Linear'); + easeYRadius = GetValue(config, 'easeXRadius', easeXRadius); + + startAngle = GetValue(config, 'startAngle', 0); + endAngle = GetValue(config, 'endAngle', 360); + easeAngle = GetValue(config, 'easeAngle', 'Linear'); + rotation = GetValue(config, 'rotation', 0); + + } else { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (startRadius === undefined) { startRadius = 0; } + if (endRadius === undefined) { endRadius = 0; } + if (startAngle === undefined) { startAngle = 0; } + if (endAngle === undefined) { endAngle = 360; } + if (rotation === undefined) { rotation = 0; } + + startX = x; + endX = x; + easeX = 'Linear'; + startY = y; + endY = y; + easeY = 'Linear'; + startXRadius = startRadius; + endXRadius = endRadius; + easeXRadius = 'Linear'; + startYRadius = startRadius; + endYRadius = endRadius; + easeYRadius = 'Linear'; + easeAngle = 'Linear'; + } + + super('SpiralCurve'); + + this.p0 = new Vector2(startX, startY); + this.p1 = new Vector2(endX, endY); + this._easeX = easeX; + this._easeXFunction = GetEaseFunction(easeX); + this._easeY = easeY; + this._easeYFunction = GetEaseFunction(easeY); + + this._startXRadius = startXRadius; + this._endXRadius = endXRadius; + this._easeXRadius = easeXRadius; + this._easeXRadiusFunction = GetEaseFunction(easeXRadius); + this._startYRadius = startYRadius; + this._endYRadius = endYRadius; + this._easeYRadius = easeYRadius; + this._easeYRadiusFunction = GetEaseFunction(easeYRadius); + this._startAngle = DegToRad(startAngle); + this._endAngle = DegToRad(endAngle); + this._easeAngle = easeAngle; + this._easeAngleFunction = GetEaseFunction(easeAngle); + this._rotation = DegToRad(rotation); + } + + getResolution(divisions) { + return divisions * 2; + } + + getStartPoint(out) { + return this.getPoint(0, out); + } + + getPoint(t, out) { + if (out === undefined) { + out = new Vector2(); + } + + var ox = Linear(this.p0.x, this.p1.x, this._easeXFunction(t)); + var oy = Linear(this.p0.y, this.p1.y, this._easeYFunction(t)); + var angle = Linear(this._startAngle, this._endAngle, this._easeAngleFunction(t)); + var xRadius = Linear(this._startXRadius, this._endXRadius, this._easeXRadiusFunction(t)); + var yRadius = Linear(this._startYRadius, this._endYRadius, this._easeYRadiusFunction(t)); + var x = ox + xRadius * Math.cos(angle); + var y = oy + yRadius * Math.sin(angle); + + if (this._rotation !== 0) { + var cos = Math.cos(this._rotation); + var sin = Math.sin(this._rotation); + + var tx = x - ox; + var ty = y - oy; + + // Rotate the point about the center of the ellipse. + x = tx * cos - ty * sin + ox; + y = tx * sin + ty * cos + oy; + } + + return out.set(x, y); + } + + get x() { + return this.p0.x; + } + + set x(value) { + var dx = value - this.p0.x; + this.p0.x += dx; + this.p1.x += dx; + } + + get y() { + return this.p0.y; + } + + set y(value) { + var dy = value - this.p0.y; + this.p0.y += dy; + this.p1.y += dy; + } + + setStartX(value) { + this.startX = value; + return this; + } + + get startX() { + return this.p0.x; + } + + set startX(value) { + this.p0.x = value; + } + + setEndX(value) { + this.endX = value; + return this; + } + + get endX() { + return this.p1.x; + } + + set endX(value) { + this.p1.x = value; + } + + setEaseX(value) { + this.easeX = value; + return this; + } + + get easeX() { + return this._easeX; + } + + set easeX(value) { + this._easeX = value; + this._easeXFunction = GetEaseFunction(value); + } + + get y() { + return this.p0.y; + } + + set y(value) { + var dy = value - this.p0.y; + this.p0.y += dy; + this.p1.y += dy; + } + + setStartY(value) { + this.startY = value; + return this; + } + + get startY() { + return this.p0.y; + } + + set startY(value) { + this.p0.y = value; + } + + setEndY(value) { + this.endY = value; + return this; + } + + get endY() { + return this.p1.y; + } + + set endY(value) { + this.p1.y = value; + } + + setEaseY(value) { + this.easeY = value; + return this; + } + + get easeY() { + return this._easeY; + } + + set easeY(value) { + this._easeY = value; + this._easeYFunction = GetEaseFunction(value); + } + + setXRadius(value) { + this.xRadius = value; + return this; + } + + get xRadius() { + return Math.max(this._startXRadius, this._endXRadius); + } + + set xRadius(value) { + this._startXRadius = value; + this._endXRadius = value; + } + + setStartXRadius(value) { + this.startXRadius = value; + return this; + } + + get startXRadius() { + return this._startXRadius; + } + + set startXRadius(value) { + this._startXRadius = value; + } + + setEndXRadius(value) { + this.endXRadius = value; + return this; + } + + get endXRadius() { + return this._endXRadius; + } + + set endXRadius(value) { + this._endXRadius = value; + } + + setEaseXRadius(value) { + this.easeXRadius = value; + return this; + } + + get easeXRadius() { + return this._easeXRadius; + } + + set easeXRadius(value) { + this._easeXRadius = value; + this._easeXRadiusFunction = GetEaseFunction(value); + } + + setYRadius(value) { + this.startYRadius = value; + this.endYRadius = value; + return this; + } + + get yRadius() { + return Math.max(this._startYRadius, this._endYRadius); + } + + set yRadius(value) { + this._startYRadius = value; + this._endYRadius = value; + } + + setStartYRadius(value) { + this.startYRadius = value; + return this; + } + + get startYRadius() { + return this._startYRadius; + } + + set startYRadius(value) { + this._startYRadius = value; + } + + setEndYRadius(value) { + this.endYRadius = value; + return this; + } + + get endYRadius() { + return this._endYRadius; + } + + set endYRadius(value) { + this._endYRadius = value; + } + + setEaseYRadius(value) { + this.easeYRadius = value; + return this; + } + + get easeYRadius() { + return this._easeYRadius; + } + + set easeYRadius(value) { + this._easeYRadius = value; + this._easeYRadiusFunction = GetEaseFunction(value); + } + + setWidth(value) { + var ratio = this.xRadius / value; + this._startXRadius *= ratio; + this._endXRadius *= ratio; + return this; + } + + setHeight(value) { + var ratio = this.yRadius / value; + this._startYRadius *= ratio; + this._endYRadius *= ratio; + return this; + } + + setStartAngle(value) { + this.startAngle = value; + return this; + } + + get startAngle() { + return RadToDeg(this._startAngle); + } + + set startAngle(value) { + this._startAngle = DegToRad(value); + } + + setEndAngle(value) { + this.endAngle = value; + return this; + } + + get endAngle() { + return RadToDeg(this._endAngle); + } + + set endAngle(value) { + this._endAngle = DegToRad(value); + } + + setEaseAngle(value) { + this.easeAngle = value; + return this; + } + + get easeAngle() { + return this._easeAngle; + } + + set easeAngle(value) { + this._easeAngle = value; + this._easeAngleFunction = GetEaseFunction(value); + } + + setRotation(value) { + this.rotation = value; + return this; + } + + get angle() { + return RadToDeg(this._rotation); + } + + set angle(value) { + this._rotation = DegToRad(value); + } + + get rotation() { + return this._rotation; + } + + set rotation(value) { + this._rotation = value; + } + + toJSON() { + return { + type: this.type, + startX: this.p0.x, + startY: this.p0.y, + endX: this.p1.x, + endY: this.p1.y, + startXRadius: this._startXRadius, + endXRadius: this._endXRadius, + easeXRadius: this._easeXRadius, + startYRadius: this._startYRadius, + endYRadius: this._endYRadius, + easeYRadius: this._easeYRadius, + startAngle: RadToDeg(this._startAngle), + endAngle: RadToDeg(this._endAngle), + easeAngle: this._easeAngle, + rotation: RadToDeg(this._rotation) + }; + } + + static fromJSON(data) { + return new SpiralCurve(data); + } +} + +export default SpiralCurve; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/customprogress-plugin.js b/ui/src/phaser3-rex-plugins/plugins/customprogress-plugin.js new file mode 100644 index 000000000..063dfa285 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/customprogress-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/shape/customprogress/Factory.js'; +import Creator from './gameobjects/shape/customprogress/Creator.js'; +import CustomProgress from './gameobjects/shape/customprogress/CustomProgress.js'; +import SetValue from './utils/object/SetValue.js'; + +class CustomProgressPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexCustomProgress', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.CustomProgress', CustomProgress); + +export default CustomProgressPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/customprogress.d.ts b/ui/src/phaser3-rex-plugins/plugins/customprogress.d.ts new file mode 100644 index 000000000..1754041d0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/customprogress.d.ts @@ -0,0 +1,2 @@ +import CustomProgress from './gameobjects/shape/customprogress/CustomProgress'; +export default CustomProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/customprogress.js b/ui/src/phaser3-rex-plugins/plugins/customprogress.js new file mode 100644 index 000000000..548012a16 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/customprogress.js @@ -0,0 +1,2 @@ +import CustomProgress from './gameobjects/shape/customprogress/CustomProgress.js'; +export default CustomProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/customshapes-plugin.js b/ui/src/phaser3-rex-plugins/plugins/customshapes-plugin.js new file mode 100644 index 000000000..09a798805 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/customshapes-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/shape/customshapes/Factory.js'; +import Creator from './gameobjects/shape/customshapes/Creator.js'; +import CustomShapes from './gameobjects/shape/customshapes/CustomShapes.js'; +import SetValue from './utils/object/SetValue.js'; + +class CustomShapesPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexCustomShapes', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.CustomShapes', CustomShapes); + +export default CustomShapesPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/customshapes.d.ts b/ui/src/phaser3-rex-plugins/plugins/customshapes.d.ts new file mode 100644 index 000000000..fdcb4897e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/customshapes.d.ts @@ -0,0 +1,2 @@ +import CustomShapes from './gameobjects/shape/customshapes/CustomShapes'; +export default CustomShapes; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/customshapes.js b/ui/src/phaser3-rex-plugins/plugins/customshapes.js new file mode 100644 index 000000000..789996672 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/customshapes.js @@ -0,0 +1,2 @@ +import CustomShapes from './gameobjects/shape/customshapes/CustomShapes.js'; +export default CustomShapes; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/bank/Bank.js b/ui/src/phaser3-rex-plugins/plugins/data/bank/Bank.js new file mode 100644 index 000000000..5335584b9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/bank/Bank.js @@ -0,0 +1,99 @@ +import GetValue from '../../utils/object/GetValue.js'; + +class Bank { + constructor(config) { + this.nextId = GetValue(config, 'start', 1); // start index + this.uidKey = GetValue(config, 'uidKey', '$uid'); + this.autoRemove = GetValue(config, 'remove', true); + this.refs = {}; + this.count = 0; + } + + add(gameObject, uid) { + var refs = this.refs; + var uidKey = this.uidKey; + if (uidKey) { + var uid = gameObject[uidKey]; + if (uid != null) { + return this; + } + } + + if (uid == null) { + do { + uid = this.nextId; + this.nextId++; + } while (refs.hasOwnProperty(uid)) + } + + if (!refs.hasOwnProperty(uid)) { + refs[uid] = gameObject; + this.count++; + if (uidKey) { + gameObject[uidKey] = uid; + } + if (this.autoRemove && gameObject.on) { + gameObject.once('destroy', function () { + this.remove(uid); + }, this) + } + } else { + uid = null; + } + + if (uidKey) { + return this; + } else { + return uid; + } + } + + addMultiple(objects) { + for (var i = 0, cnt = objects.length; i < cnt; i++) { + this.add(objects[i]); + } + return this; + } + + get(uid) { + return this.refs[uid]; + } + + has(uid) { + return this.refs.hasOwnProperty(uid); + } + + remove(uid) { + var refs = this.refs; + if (refs.hasOwnProperty(uid)) { + if (this.uidKey) { + var gameObject = refs[uid]; + gameObject[this.uidKey] = undefined; + } + delete refs[uid]; + this.count--; + } + return this; + } + + forEach(callback, scope) { + var refs = this.refs, + gameObject; + for (var uid in refs) { + gameObject = refs[uid]; + if (scope) { + callback.call(scope, gameObject, uid); + } else { + callback(gameObject, uid); + } + } + } + + clear() { + this.forEach(function (gameObject) { + this.remove(gameObject); + }, this); + } +} + +export default Bank; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/buff/DataManager.d.ts b/ui/src/phaser3-rex-plugins/plugins/data/buff/DataManager.d.ts new file mode 100644 index 000000000..399850115 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/buff/DataManager.d.ts @@ -0,0 +1,33 @@ +export default class DataManager extends Phaser.Data.DataManager { + constructor( + parent: object, + eventEmitter?: Phaser.Events.EventEmitter, + ); + + setBaseValue(key: string, value: number): this; + + removeBaseValue(key: string): this; + + getBaseValue(key: string): number; + + setBuff( + key: string, + buffKey: string, + value: number | string + ): this; + + removeBuff(key: string, buffKey: string,): this; + + getBuffValue(key: string, buffKey: string,): number; + + setMin(key: string, min: number): this; + + setMax(key: string, max: number): this; + + setBounds(key: string, min: number, max: number): this; + + getMinBound(key: string): number; + + getMaxBound(key: string): number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/buff/DataManager.js b/ui/src/phaser3-rex-plugins/plugins/data/buff/DataManager.js new file mode 100644 index 000000000..93a5675d1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/buff/DataManager.js @@ -0,0 +1,32 @@ +import methods from './Methods.js'; +import Extend from './Extend.js'; + +const Base = Phaser.Data.DataManager; +const EventEmitterKlass = Phaser.Events.EventEmitter; + +class DataManager extends Base { + constructor(parent, eventEmitter) { + var useDefaultEventEmitter = (eventEmitter === undefined); + if (useDefaultEventEmitter) { + eventEmitter = new EventEmitterKlass(); + } + + super(parent, eventEmitter); + + if (useDefaultEventEmitter) { + var parentEventEmitter = (parent.events) ? parent.events : parent; + if (parentEventEmitter) { + parentEventEmitter.once('destroy', this.destroy, this); + } + } + + Extend(this); + } +} + +Object.assign( + DataManager.prototype, + methods +); + +export default DataManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/buff/Extend.d.ts b/ui/src/phaser3-rex-plugins/plugins/data/buff/Extend.d.ts new file mode 100644 index 000000000..a41266e82 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/buff/Extend.d.ts @@ -0,0 +1,5 @@ +import DataManager from './DataManager'; + +export default function Extend( + dataManager: Phaser.Data.DataManager +): DataManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/buff/Extend.js b/ui/src/phaser3-rex-plugins/plugins/data/buff/Extend.js new file mode 100644 index 000000000..70bd74c95 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/buff/Extend.js @@ -0,0 +1,15 @@ +import methods from './Methods.js'; + +var Extend = function (dataManager) { + if (dataManager.buffs === undefined) { + dataManager.baseValues = {}; + dataManager.buffs = {}; + dataManager.bounds = {}; + } + if (dataManager.addBuff === undefined) { + Object.assign(dataManager, methods); + } + return dataManager; +} + +export default Extend \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/buff/Methods.js b/ui/src/phaser3-rex-plugins/plugins/data/buff/Methods.js new file mode 100644 index 000000000..eef9a9ee4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/buff/Methods.js @@ -0,0 +1,137 @@ +import Buff from '../../utils/buff/Buff.js'; +import MinMaxBounds from '../../utils/minmaxbounds/MinMaxBounds.js'; + +export default { + setBaseValue(key, value) { + this.baseValues[key] = value; + this.set(key, this.getBuffResult(key)); + return this; + }, + + removeBaseValue(key) { + if (this.baseValues.hasOwnProperty(key)) { + delete this.baseValues[key]; + this.remove(key); + } + return this; + }, + + setBuff(key, buffKey, value) { + if (!this.buffs.hasOwnProperty(key)) { + this.buffs[key] = new Buff(); + } + this.buffs[key].set(buffKey, value); + this.set(key, this.getBuffResult(key)); + return this; + }, + + enableBuff(key, buffKey, enable) { + if (!this.buffs.hasOwnProperty(key)) { + this.buffs[key] = new Buff(); + } + this.buffs[key].setEnable(buffKey, enable); + this.set(key, this.getBuffResult(key)); + return this; + }, + + removeBuff(key, buffKey) { + if (this.buffs.hasOwnProperty(key)) { + if (buffKey === undefined) { + delete this.buffs[key]; + } else { + this.buffs[key].remove(buffKey); + } + } + this.set(key, this.getBuffResult(key)); + return this; + }, + + setMin(key, min) { + if (!this.bounds.hasOwnProperty(key)) { + this.bounds[key] = new MinMaxBounds(); + } + this.bounds[key].setMin(min); + this.set(key, this.getBuffResult(key)); + return this; + }, + + setMax(key, max) { + if (!this.bounds.hasOwnProperty(key)) { + this.bounds[key] = new MinMaxBounds(); + } + this.bounds[key].setMax(max); + this.set(key, this.getBuffResult(key)); + return this; + }, + + setBounds(key, min, max) { + if (!this.bounds.hasOwnProperty(key)) { + this.bounds[key] = new MinMaxBounds(); + } + this.bounds[key].setMin(min).setMax(max); + this.set(key, this.getBuffResult(key)); + return this; + }, + + getBuffResult(key) { + return this.clamp(key, this.buff(key)); + }, + + buff(key, baseValue) { + if (baseValue === undefined) { + baseValue = this.getBaseValue(key); + } + if (!this.buffs.hasOwnProperty(key)) { + return baseValue; + } + return this.buffs[key].buff(baseValue); + }, + + clamp(key, value) { + if (value === undefined) { + value = this.list[key]; + } + if (!this.bounds.hasOwnProperty(key)) { + return value; + } + return this.bounds[key].clamp(value); + }, + + getBaseValue(key) { + if (!this.baseValues.hasOwnProperty(key)) { + this.baseValues[key] = 0; + } + return this.baseValues[key]; + }, + + getBuffs(key, buffKey) { + var buffs = this.buffs[key]; + if (buffKey === undefined) { + return buffs; + } + if (buffs && buffs.hasOwnProperty(buffKey)) { + return buffs[buffKey]; + } + + return undefined; + }, + + getBuffValue(key, buffKey) { + return this.getBuffs(key, buffKey).value + }, + + getBounds(key) { + if (!this.bounds.hasOwnProperty(key)) { + this.bounds[key] = new MinMaxBounds(); + } + return this.bounds[key]; + }, + + getMinBound(key) { + return this.getBounds(key).min; + }, + + getMaxBound(key) { + return this.getBounds(key).max; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/CanvasObjectToBitmap.d.ts b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/CanvasObjectToBitmap.d.ts new file mode 100644 index 000000000..73c2f5d91 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/CanvasObjectToBitmap.d.ts @@ -0,0 +1,21 @@ +import CanvasData from "./canvasdata/CanvasData"; + +export default CanvasObjectToBitmap; + +declare namespace CanvasObjectToBitmap { + interface IConfig { + x?: number, y?: number, + width?: number, height?: number, + } +} + +declare function CanvasObjectToBitmap( + gameObject: Phaser.GameObjects.GameObject, + config?: CanvasObjectToBitmap.IConfig, + out?: CanvasData +): CanvasData; + +declare function CanvasObjectToBitmap( + gameObject: Phaser.GameObjects.GameObject, + out?: CanvasData +): CanvasData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/CanvasObjectToBitmap.js b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/CanvasObjectToBitmap.js new file mode 100644 index 000000000..5f0c7f369 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/CanvasObjectToBitmap.js @@ -0,0 +1,26 @@ +import CanvasData from './canvasdata/CanvasData.js'; +import CanvasToData from './canvasdata/CanvasToData.js'; +import BooleanBuffer from '../../utils/arraybuffers/BooleanBuffer.js'; +import FillAlpha from './fillcallbacks/alpha.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var CanvasObjectToBitmap = function (canvasObject, config, out) { + if (config instanceof CanvasData) { + out = config; + config = undefined; + } + + var x = GetValue(config, 'x', undefined); + var y = GetValue(config, 'y', undefined); + var width = GetValue(config, 'width', undefined); + var height = GetValue(config, 'height', undefined); + + return CanvasToData( + canvasObject.canvas, // canvas + x, y, width, height, // x, y, width, height + BooleanBuffer, FillAlpha, undefined, // BufferClass, fillCallback, fillCallbackScope + out); +}; + +export default CanvasObjectToBitmap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/Methods.js b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/Methods.js new file mode 100644 index 000000000..facc656fc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/Methods.js @@ -0,0 +1,8 @@ +import CanvasObjectToBitmap from './CanvasObjectToBitmap.js'; +import TextureTColorMap from './TextureToColormap.js'; + +export default { + textObjectToBitmap: CanvasObjectToBitmap, + canvasObjectToBitmap: CanvasObjectToBitmap, + textureTColorMap: TextureTColorMap, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/TextureToColorMap.d.ts b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/TextureToColorMap.d.ts new file mode 100644 index 000000000..6d3494aa7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/TextureToColorMap.d.ts @@ -0,0 +1,45 @@ +import CanvasData from "./canvasdata/CanvasData"; + +export default TextureTColorMap; + +declare namespace TextureTColorMap { + interface IConfig { + x?: number, y?: number, + width?: number, height?: number, + } +} + +declare function TextureTColorMap( + gameObject: Phaser.GameObjects.GameObject | Phaser.Textures.Frame, + config?: TextureTColorMap.IConfig, + out?: CanvasData +): CanvasData; + +declare function TextureTColorMap( + gameObject: Phaser.GameObjects.GameObject | Phaser.Textures.Frame, + out?: CanvasData +): CanvasData; + +declare function TextureTColorMap( + key: string, + frameName: string | null, + config?: TextureTColorMap.IConfig, + out?: CanvasData +): CanvasData; + +declare function TextureTColorMap( + key: string, + frameName: string | null, + out?: CanvasData +): CanvasData; + +declare function TextureTColorMap( + key: string, + config?: TextureTColorMap.IConfig, + out?: CanvasData +): CanvasData; + +declare function TextureTColorMap( + key: string, + out?: CanvasData +): CanvasData; diff --git a/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/TextureToColorMap.js b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/TextureToColorMap.js new file mode 100644 index 000000000..b999bde3c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/TextureToColorMap.js @@ -0,0 +1,56 @@ +import CanvasData from './canvasdata/CanvasData.js'; +import CanvasToData from './canvasdata/CanvasToData.js'; +import ColorBuffer from '../../utils/arraybuffers/FourBytesBuffer.js'; +import FillColor from './fillcallbacks/color32.js'; +import IsGameObject from '../../utils/system/IsGameObject.js'; +import DrawFrame from '../../utils/texture/DrawFrameToCanvas.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const CanvasPool = Phaser.Display.Canvas.CanvasPool; + +var TextureTColorMap = function (key, frameName, config, out) { + var frame; + if (typeof (key) === 'string') { + if (typeof (frameName) !== 'string') { + out = config; + config = frameName; + frameName = undefined; + } + frame = this.textureManager.getFrame(key, frameName); + } else { + frame = (IsGameObject(key)) ? key.frame : key; + out = config; + config = frameName; + } + + if (config instanceof CanvasData) { + out = config; + config = undefined; + } + + var hasDefaultCanvas = (this._tmpCanvas !== undefined); + var canvas = (hasDefaultCanvas) ? + this._tmpCanvas : + CanvasPool.create2D(this, undefined, undefined, undefined, true); + + var x = GetValue(config, 'x', undefined); + var y = GetValue(config, 'y', undefined); + var width = GetValue(config, 'width', undefined); + var height = GetValue(config, 'height', undefined); + + out = CanvasToData( + DrawFrame(frame, canvas), // canvas + x, y, width, height, // x, y, width, height + ColorBuffer, FillColor, undefined, // BufferClass, fillCallback, fillCallbackScope + out); + + if (!hasDefaultCanvas) { + CanvasPool.remove(canvas); + } else { + canvas.width = 1; + canvas.height = 1; + } + return out; +}; + +export default TextureTColorMap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/canvasdata/CanvasData.d.ts b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/canvasdata/CanvasData.d.ts new file mode 100644 index 000000000..ef1187bf5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/canvasdata/CanvasData.d.ts @@ -0,0 +1,37 @@ +export default CanvasData; + +declare namespace CanvasData { + type ForEachCallbackType = ( + value: number, + x: number, y: number, + canvasData: CanvasData + ) => void; + +} + +declare class CanvasData { + + readonly width: number; + readonly height: number; + + destroy(): void; + + color32ToColorInt(value: number): number; + + color32ToAlpha(value: number): number; + + forEach( + callback: CanvasData.ForEachCallbackType, + scope?: object + ): this; + + forEachNonZero( + callback: CanvasData.ForEachCallbackType, + scope?: object + ): this; + + get( + x: number, y: number + ): boolean | number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/canvasdata/CanvasData.js b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/canvasdata/CanvasData.js new file mode 100644 index 000000000..e0f767116 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/canvasdata/CanvasData.js @@ -0,0 +1,136 @@ +import Color32Methods from '../../../utils/math/color/Color32Methods.js'; + +class CanvasData { + constructor(BufferClass, width, height) { + if (width === undefined) { + width = 0; + } + if (height === undefined) { + height = width; + } + + this.width = width; + this.height = height; + this.buffer = new BufferClass(width * height); + } + + destroy() { + this.buffer.destroy(); + this.buffer = undefined; + } + + getOffset(x, y) { + return y * this.width + x; + } + + get(x, y) { + var offset + if (arguments.length === 2) { + offset = this.getOffset(x, y); + } else { + offset = x; + } + return this.buffer.get(offset); + } + + set(x, y, value) { + var offset + if (arguments.length === 3) { + offset = this.getOffset(x, y); + } else { + offset = x; + value = y; + } + this.buffer.set(offset, value); + return this; + } + + fill(canvas, x, y, width, height, callback, scope) { + if (typeof (canvas) === 'number') { + var value = canvas; + this.buffer.fill(value); + + } else { + if (x === undefined) { + x = 0; + } + if (y === undefined) { + y = 0; + } + if (width === undefined) { + width = canvas.width - x; + } + if (height === undefined) { + height = canvas.height - y; + } + this.resize(width, height); + var context = canvas.getContext('2d', { willReadFrequently: true }); + var imgData = context.getImageData(x, y, width, height).data; + var pixels = imgData.length, imgDataIndex; + var value; + for (var i = 0, cnt = pixels / 4; i < cnt; i++) { + imgDataIndex = i * 4; + if (scope) { + value = callback.call(scope, imgData, imgDataIndex); + } else { + value = callback(imgData, imgDataIndex); + } + this.set(i, value); + } + } + + return this; + } + + clear() { + this.fill(0); + return this; + } + + resize(width, height) { + if ((this.width === width) && (this.height === height)) { + return this; + } + + this.width = width; + this.height = height; + this.buffer.resize(width * height); + return this; + } + + forEach(callback, scope, skipZero) { + if (skipZero === undefined) { + skipZero = false; + } + var value; + for (var y = 0, h = this.height; y < h; y++) { + for (var x = 0, w = this.width; x < w; x++) { + value = this.get(x, y); + if (skipZero && + ((value === 0) || (value === false)) + ) { + continue; + } + + if (scope) { + callback.call(scope, value, x, y, this); + } else { + callback(value, x, y, this); + } + } + } + return this; + } + + forEachNonZero(callback, scope) { + this.forEach(callback, scope, true); + return this; + } +}; + +Object.assign( + CanvasData.prototype, + Color32Methods +); + +export default CanvasData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/canvasdata/CanvasToData.js b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/canvasdata/CanvasToData.js new file mode 100644 index 000000000..96e5ee5c2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/canvasdata/CanvasToData.js @@ -0,0 +1,23 @@ +import CanvasData from './CanvasData.js'; + +var CanvasToData = function (canvas, x, y, width, height, BufferClass, fillCallback, fillCallbackScope, out) { + if (x === undefined) { + x = 0; + } + if (y === undefined) { + y = 0; + } + if (width === undefined) { + width = canvas.width - x; + } + if (height === undefined) { + height = canvas.height - y; + } + if (out === undefined) { + out = new CanvasData(BufferClass, width, height); + } + + out.fill(canvas, x, y, width, height, fillCallback, fillCallbackScope); + return out; +} +export default CanvasToData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/fillcallbacks/alpha.js b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/fillcallbacks/alpha.js new file mode 100644 index 000000000..5c33b7db5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/fillcallbacks/alpha.js @@ -0,0 +1,5 @@ +var FillCallback = function (imgData, imgDataIndex) { + return (imgData[imgDataIndex + 3] > 0); +} + +export default FillCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/fillcallbacks/color32.js b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/fillcallbacks/color32.js new file mode 100644 index 000000000..1bf5ce0cd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/canvasdata/fillcallbacks/color32.js @@ -0,0 +1,8 @@ +var FillCallback = function (imgData, imgDataIndex) { + return (imgData[imgDataIndex + 3] << 24) | + (imgData[imgDataIndex + 0] << 16) | + (imgData[imgDataIndex + 1] << 8) | + imgData[imgDataIndex + 2]; +}; + +export default FillCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/csvtoarray/CSVToArray.d.ts b/ui/src/phaser3-rex-plugins/plugins/data/csvtoarray/CSVToArray.d.ts new file mode 100644 index 000000000..8bf55443a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/csvtoarray/CSVToArray.d.ts @@ -0,0 +1,13 @@ +export default CSVToArray; + +declare namespace CSVToArray { + interface IConfig { + delimiter?: string, + convert?: boolean + } +} + +declare function CSVToArray( + csvString: string, + config?: CSVToArray.IConfig +): any[][]; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/csvtoarray/CSVToArray.js b/ui/src/phaser3-rex-plugins/plugins/data/csvtoarray/CSVToArray.js new file mode 100644 index 000000000..512af23e8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/csvtoarray/CSVToArray.js @@ -0,0 +1,16 @@ +import CSVParser from 'papaparse/papaparse.min.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var CSVToArray = function (csvString, config) { + var delimiter = GetValue(config, 'delimiter', ','); + var convert = GetValue(config, 'convert', true); + + var arr = CSVParser.parse(csvString, { + delimiter: delimiter, + dynamicTyping: convert + }).data; + return arr; +}; + +export default CSVToArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/csvtohashtable/CsvToHashTable.d.ts b/ui/src/phaser3-rex-plugins/plugins/data/csvtohashtable/CsvToHashTable.d.ts new file mode 100644 index 000000000..100063f58 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/csvtohashtable/CsvToHashTable.d.ts @@ -0,0 +1,125 @@ +export default CsvToHashTable; + +declare namespace CsvToHashTable { + type ConverCallbackType = (value: string, rowKey: string | number, colKey: string | number) => any; + + type AppendDataCallbackType = (table: CsvToHashTable, rowKey: string | number, colKey: string | number) => any + + type SortModeType = 0 | 1 | 2 | 3 | 'ascending' | 'descending' | 'logical ascending' | 'logical descending'; + + type SortCallbackType = (key0: string, key1: string) => number; + + type EachCallbackType = (table: CsvToHashTable, rowKey: string | number, colKey: string | number, value: any) => void; + + interface ILoadConfig { + delimiter?: string, + convert?: boolean | ConverCallbackType, + convertScope?: object + } +} + +declare class CsvToHashTable { + constructor(); + + destroy(): void; + + loadCSV( + csvString: string, + config?: CsvToHashTable.ILoadConfig + ): this; + + convertCol( + colKey: string | number, + convertCallback?: boolean | CsvToHashTable.ConverCallbackType, + convertCallbackScope?: object + ): this; + + convertRow( + rowKey: string | number, + convertCallback?: boolean | CsvToHashTable.ConverCallbackType, + convertCallbackScope?: object + ): this; + + get( + rowKey: string | number, colKey: string | number + ): any; + + set( + rowKey: string | number, colKey: string | number, + value: any + ): this; + + add( + rowKey: string | number, colKey: string | number, + value: number + ): this; + + hasRowKey(rowKey: string | number): boolean; + + hasColKey(colKey: string | number): boolean; + + hasKey(rowKey: string | number, colKey: string | number): boolean; + + isValueInRol(rowKey: string | number, data: any): boolean; + + isValueInCol(colKey: string | number, data: any): boolean; + + clear(): this; + + appendCol( + colKey: string | number, + initValue: any + ): this; + + appendCol( + colKey: string | number, + callback: CsvToHashTable.AppendDataCallbackType, + scope?: object + ): this; + + appendRow( + rowKey: string | number, + initValue: any + ): this; + + appendRow( + rowKey: string | number, + callback: CsvToHashTable.AppendDataCallbackType, + scope?: object + ): this; + + removeCol(colKey: string | number): this; + + removeRol(rowKey: string | number): this; + + sortCol( + colKey: string | number, + mode: CsvToHashTable.SortModeType + ): this; + + sortCol( + callback: CsvToHashTable.SortCallbackType, + scope?: object + ): this; + + sortRow( + rowKey: string | number, + mode: CsvToHashTable.SortModeType + ): this; + + sortRow( + callback: CsvToHashTable.SortCallbackType, + scope?: object + ): this; + + eachCol(rowKey: string | number, + callback: CsvToHashTable.EachCallbackType, + scope?: object + ): this; + + eachRow(colKey: string | number, + callback: CsvToHashTable.EachCallbackType, + scope?: object + ): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/csvtohashtable/CsvToHashTable.js b/ui/src/phaser3-rex-plugins/plugins/data/csvtohashtable/CsvToHashTable.js new file mode 100644 index 000000000..919852f7f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/csvtohashtable/CsvToHashTable.js @@ -0,0 +1,618 @@ +import CSVParser from 'papaparse/papaparse.min.js'; +import TypeConvert from '../../utils/string/TypeConvert.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class CsvToHashTable { + constructor(config) { + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.table = GetValue(o, 'table', {}); // 2d hash table + this.rowKeys = GetValue(o, 'row', []); + this.colKeys = GetValue(o, 'col', []); + this.cursor = GetValue(o, 'cursor', {}); + return this; + } + + toJSON() { + return { + table: this.table, + row: this.rowKeys, + col: this.colKeys, + cursor: this.cursor + }; + } + + shutdown() { + this.table = undefined; + this.rowKeys = undefined; + this.colKeys = undefined; + this.cursor = undefined; + } + + destroy() { + this.shutdown(); + } + + loadCSV(csvString, config) { + var delimiter = GetValue(config, 'delimiter', ','); + var convert = GetValue(config, 'convert', true); + var convertScope = GetValue(config, 'convertScope', undefined); + if (!convert) { + convert = undefined; + convertScope = undefined; + } else if (convert === true) { + convert = TypeConvert; + convertScope = undefined; + } + + var arr = CSVParser.parse(csvString, { + delimiter: delimiter + }).data; + + var inColKeys = arr[0]; + for (var i = 0, cnt = inColKeys.length; i < cnt; i++) { + var colKey = inColKeys[i]; + if (this.colKeys.indexOf(colKey) !== -1) { + continue; + } + this.colKeys.push(colKey); + } + + var inRowKeys = arr.map(function (row) { return row[0] }); + inRowKeys.shift(); // skip 1st row + for (var i = 0, cnt = inRowKeys.length; i < cnt; i++) { + var rowKey = inRowKeys[i]; + if (this.rowKeys.indexOf(rowKey) !== -1) { + continue; + } + this.rowKeys.push(rowKey); + } + + var table = this.table; + var colKey, rowKey, row, value; + + for (var r = 0, rcnt = inRowKeys.length; r < rcnt; r++) { + rowKey = inRowKeys[r]; + if (!table.hasOwnProperty(rowKey)) { + table[rowKey] = {}; + } + row = table[rowKey]; + for (var c = 0, ccnt = inColKeys.length; c < ccnt; c++) { + value = arr[r + 1][c]; + colKey = inColKeys[c]; + + if (convert) { + if (convertScope) { + value = convert.call(convertScope, value, rowKey, colKey, this); + } else { + value = convert(value, rowKey, colKey, this); + } + } + row[colKey] = value; + } + } + + this.setCursor('', ''); + + return this; + } + + clear() { + var table = this.table; + for (var key in table) { + delete table[key]; + } + this.rowKeys.length = 0; + this.colKeys.length = 0; + return this; + }; + + get(rowKey, colKey) { + if (typeof (rowKey) === 'number') { + rowKey = this.rowKeys[rowKey]; + } + if (typeof (colKey) === 'number') { + colKey = this.colKeys[colKey]; + } + + var value = undefined; + var table = this.table; + if (table.hasOwnProperty(rowKey)) { + var row = table[rowKey]; + if (row.hasOwnProperty(colKey)) { + value = row[colKey]; + } + } + + this.setCursor(rowKey, colKey); + return value; + } + + set(rowKey, colKey, value) { + if (typeof (rowKey) === 'number') { + rowKey = this.rowKeys[rowKey]; + } + if (typeof (colKey) === 'number') { + colKey = this.colKeys[colKey]; + } + + var table = this.table; + if (table.hasOwnProperty(rowKey)) { + var row = table[rowKey]; + if (row.hasOwnProperty(colKey)) { + row[colKey] = value; + } + } + + this.setCursor(rowKey, colKey); + return this; + } + + add(rowKey, colKey, value) { + if (typeof (rowKey) === 'number') { + rowKey = this.rowKeys[rowKey]; + } + if (typeof (colKey) === 'number') { + colKey = this.colKeys[colKey]; + } + + var table = this.table; + if (table.hasOwnProperty(rowKey)) { + var row = table[rowKey]; + if (row.hasOwnProperty(colKey)) { + row[colKey] += value; + } + } + + this.setCursor(rowKey, colKey); + return this; + } + + hasRowKey(rowKey) { + if (typeof (rowKey) === 'number') { + return this.rowKeys.length > rowKey; + } + + return (this.rowKeys.indexOf(rowKey) !== -1); + } + + hasColKey(colKey) { + if (typeof (colKey) === 'number') { + return this.colKeys.length > colKey; + } + + return (this.colKeys.indexOf(colKey) !== -1); + } + + hasKey(rowKey, colKey) { + return this.hasRowKey(rowKey) && this.hasColKey(colKey); + } + + isValueInRol(rowKey, value) { + if (!this.hasRowKey(rowKey)) { + return false; + } + if (typeof (rowKey) === 'number') { + rowKey = this.rowKeys[rowKey]; + } + + var row = this.table[rowKey]; + var colKey, colKeys = this.colKeys; + for (var i = 0, len = colKeys.length; i < len; i++) { + colKey = colKeys[i]; + if (row[colKey] === value) { + return true; + } + } + + return false; + } + + isValueInCol(colKey, value) { + if (!this.hasColKey(colKey)) { + return false; + } + if (typeof (colKey) === 'number') { + colKey = this.colKeys[colKey]; + } + + var table = this.table; + var rowKey, rowKeys = this.rowKeys + for (var i = 0, len = rowKeys.length; i < len; i++) { + if (table[rowKey][colKey] === value) { + return true; + } + } + + return false; + } + + appendRow(rowKey, callback, scope) { + if (this.hasRowKey(rowKey)) { + return this; + } + if (typeof (rowKey) === 'number') { + rowKey = this.rowKeys[rowKey]; + } + + + var isCallbackMode = (typeof (callback) === 'function'); + var initValue = (isCallbackMode) ? undefined : callback; + + this.rowKeys.push(rowKey); + var row = {}; + this.table[rowKey] = row; + var colKey, colKeys = this.colKeys, + value; + for (var i = 0, len = colKeys.length; i < len; i++) { + colKey = colKeys[i]; + + if (isCallbackMode) { + if (scope) { + value = callback.call(scope, this, rowKey, colKey); + } else { + value = callback(this, rowKey, colKey) + } + } else { + value = initValue; + } + row[colKey] = value; + } + + return this; + } + + appendCol(colKey, callback, scope) { + if (this.hasColKey(colKey)) { + return this; + } + if (typeof (colKey) === 'number') { + colKey = this.colKeys[colKey]; + } + + var isCallbackMode = (typeof (callback) === 'function'); + var initValue = (isCallbackMode) ? undefined : callback; + + this.colKeys.push(colKey); + var table = this.table; + var rowKey, rowKeys = this.rowKeys, + value; + for (var i = 0, len = rowKeys.length; i < len; i++) { + rowKey = rowKeys[i]; + + if (isCallbackMode) { + if (scope) { + value = callback.call(scope, this, rowKey, colKey); + } else { + value = callback(this, rowKey, colKey); + } + } else { + value = initValue; + } + table[rowKey][colKey] = value; + } + + return this; + } + + removeRol(rowKey) { + var idx; + if (typeof (rowKey) === 'number') { + idx = (this.rowKeys.length > rowKey) ? rowKey : -1; + } else { + idx = this.rowKeys.indexOf(rowKey); + } + + if (idx === -1) { + return this; + } + this.rowKeys.splice(idx, 1); + + delete this.table[rowKey]; + return this; + } + + removeCol(colKey) { + var idx; + if (typeof (colKey) === 'number') { + idx = (this.colKeys.length > colKey) ? colKey : -1; + } else { + idx = this.colKeys.indexOf(colKey); + } + + if (idx === -1) { + return this; + } + this.colKeys.splice(idx, 1); + + var table = this.table; + var rowKeys = this.rowKeys; + for (var i = 0, len = rowKeys.length; i < len; i++) { + delete table[rowKeys[i]][colKey]; + } + return this; + } + + eachRow(colKey, callback, scope) { + if (typeof (colKey) === 'number') { + colKey = this.colKeys[colKey]; + } + + var rowKeys = this.rowKeys, + rowKey, value; + var isValidColKey = this.hasColKey(colKey); + + for (var i = 0, len = rowKeys.length; i < len; i++) { + rowKey = rowKeys[i]; + if (isValidColKey) { + value = this.get(rowKey, colKey); + } + + if (scope) { + callback.call(scope, this, rowKey, colKey, value); + } else { + callback(this, rowKey, colKey, value); + } + } + return this; + } + + eachCol(rowKey, callback, scope) { + if (typeof (rowKey) === 'number') { + rowKey = this.rowKeys[rowKey]; + } + + var colKeys = this.colKeys, + colKey, value; + var isValidRowKey = this.hasRowKey(rowKey); + for (var i = 0, len = colKeys.length; i < len; i++) { + colKey = colKeys[i]; + if (isValidRowKey) { + value = this.get(rowKey, colKey); + } + + if (scope) { + callback.call(scope, this, rowKey, colKey, value); + } else { + callback(scope, this, rowKey, colKey, value); + } + } + return this; + } + + convertCol(colKey, callback, scope) { + if (typeof (colKey) === 'number') { + colKey = this.colKeys[colKey]; + } + + if (callback === undefined) { + callback = TypeConvert; + } + + if (Array.isArray(colKey)) { + for (var i = 0, len = colKey.length; i < len; i++) { + this.convertCol(colKey[i], callback, scope); + } + return this; + } + + if (!this.hasColKey(colKey)) { + return this; + } + + var table = this.table, + row; + var rowKey, rowKeys = this.rowKeys, + value; + for (var r = 0, rcnt = rowKeys.length; r < rcnt; r++) { + rowKey = rowKeys[r]; + row = table[rowKey]; + value = row[colKey]; + if (scope) { + value = callback.call(scope, this, rowKey, colKey, value); + } else { + value = callback(this, rowKey, colKey, value); + } + + row[colKey] = value; + } + return this; + } + + convertRow(rowKey, callback, scope) { + if (typeof (rowKey) === 'number') { + rowKey = this.rowKeys[rowKey]; + } + + if (callback === undefined) { + callback = TypeConvert; + } + + if (Array.isArray(rowKey)) { + for (var i = 0, len = rowKey.length; i < len; i++) { + this.convertRow(rowKey[i], callback, scope); + } + return this; + } + + var row = this.table[rowKey]; + var colKey, colKeys = this.colKeys, + value; + for (var c = 0, ccnt = colKeys.length; c < ccnt; c++) { + colKey = colKeys[r]; + value = row[colKey]; + if (scope) { + value = callback.call(scope, this, rowKey, colKey, value); + } else { + value = callback(this, rowKey, colKey, value); + } + + row[colKey] = value; + } + return this; + + } + + get curColKey() { + return this.cursor.colKey; + } + + get curRowKey() { + return this.cursor.rowKey; + } + + nextColKey(colKey, step) { + if (colKey === undefined) { + colKey = this.cursor.colKey; + } + if (step === undefined) { + step = 1; + } + + var colKeys = this.colKeys; + var idx = colKeys.indexOf(colKey); + if (idx === -1) { + return undefined; + } + return colKeys[idx + step]; + } + + nextRowKey(rowKey, step) { + if (rowKey === undefined) { + rowKey = this.cursor.rowKey; + } + if (step === undefined) { + step = 1; + } + + var rowKeys = this.rowKeys; + var idx = rowKeys.indexOf(rowKey); + if (idx === -1) { + return undefined; + } + return rowKeys[idx + 1]; + } + + previousColKey(colKey, step) { + if (step === undefined) { + step = 1; + } + step = -step; + return this.nextColKey(colKey, step); + } + + previousRowKey(rowKey, step) { + if (step === undefined) { + step = 1; + } + step = -step; + return this.nextRowlKey(rowKey, step); + } + + sortCol(callback, scope) { + if (typeof (callback) === 'function') { + if (scope) { + callback = callback.bind(scope); + } + } else { + var colKey = callback; + if (!this.hasColKey(colKey)) { + return this; + } + var mode = scope; + if (typeof (mode) === 'string') { + mode = SORTMODE[mode]; + } + var table = this; + callback = function (rowKeyA, rowKeyB) { + var valA = table.get(rowKeyA, colKey); + var valB = table.get(rowKeyB, colKey); + var retVal; + if (mode >= 2) { + valA = parseFloat(valA); + valB = parseFloat(valB); + } + switch (mode) { + case 0: + case 2: + retVal = (valA > valB) ? 1 : + (valA < valB) ? -1 : 0; + break; + + case 1: + case 3: + retVal = (valA < valB) ? 1 : + (valA > valB) ? -1 : 0; + break; + } + return retVal; + } + } + + this.rowKeys.sort(callback); + return this; + } + + sortRow(callback, scope) { + if (typeof (callback) === 'function') { + if (scope) { + callback = callback.bind(scope); + } + } else { + var rowKey = callback; + if (!this.hasRowKey(rowKey)) { + return this; + } + var mode = scope; + if (typeof (mode) === 'string') { + mode = SORTMODE[mode]; + } + var table = this; + callback = function (colKeyA, colKeyB) { + var valA = table.get(rowKey, colKeyA); + var valB = table.get(rowKey, colKeyB); + var retVal; + if (mode >= 2) { + valA = parseFloat(valA); + valB = parseFloat(valB); + } + switch (mode) { + case 0: + case 2: + retVal = (valA > valB) ? 1 : + (valA < valB) ? -1 : 0; + break; + + case 1: + case 3: + retVal = (valA < valB) ? 1 : + (valA > valB) ? -1 : 0; + break; + } + return retVal; + } + } + + this.colKeys.sort(callback); + return this; + } + + setCursor(rowKey, colKey) { + var cursor = this.cursor; + cursor.rowKey = rowKey; + cursor.colKey = colKey; + return this; + } + +} + +const SORTMODE = { + 'ascending': 0, + 'descending': 1, + 'logical ascending': 2, + 'logical descending': 3 +} +export default CsvToHashTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/pngappender/AppendData.d.ts b/ui/src/phaser3-rex-plugins/plugins/data/pngappender/AppendData.d.ts new file mode 100644 index 000000000..060cb80a1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/pngappender/AppendData.d.ts @@ -0,0 +1,11 @@ +export default AppendData; + +type BasicDataType = number | string; +type DictDataType = { [key: string]: BasicDataType } | { [key: string]: DictDataType } | { [key: string]: ListDateType }; +type ListDateType = (BasicDataType | ListDateType | DictDataType)[]; +type DataType = BasicDataType | DictDataType | ListDateType; + +declare function AppendData( + pngBuffer: Uint8Array, + data: DataType | Uint8Array, +): Uint8Array; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/pngappender/AppendData.js b/ui/src/phaser3-rex-plugins/plugins/data/pngappender/AppendData.js new file mode 100644 index 000000000..b402128f1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/pngappender/AppendData.js @@ -0,0 +1,41 @@ +import GetChunkEndByteIndex from './GetChunkEndByteIndex.js'; +import Uint8ArrayWriter from '../../utils/arraybuffers/Uint8ArrayWriter.js'; + +var AppendData = function (pngBuffer, data) { + // Get End of last png chunk (IEND) + var pngByteLength = GetChunkEndByteIndex(pngBuffer, 'IEND'); + + var isUint8Array = (typeof (obj) === 'object') && (obj.constructor === Uint8Array); + var dataType = (isUint8Array) ? 1 : 0; + var header0 = dataType; + var header1 = 0; + + var dataUint8Array; + if (isUint8Array) { + dataUint8Array = data; + } else { + if (data != null) { + // JSON -> string -> Uint8Array + data = JSON.stringify(data); + dataUint8Array = (new TextEncoder()).encode(data); + } else { + dataUint8Array = new Uint8Array(0); + } + } + + // Append dataUint8Array after png-chunks + var outputLength = pngByteLength + 8 + dataUint8Array.length; + var writer = (new Uint8ArrayWriter(outputLength)) + // png-buffer + .writeUint8Array(pngBuffer.slice(0, pngByteLength)) + // header0: dataType + .writeUint32(header0) + // header1: 0x0 + .writeUint32(header1) + // myData + .writeUint8Array(dataUint8Array) + + return writer.buf; +} + +export default AppendData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/pngappender/ExtractData.d.ts b/ui/src/phaser3-rex-plugins/plugins/data/pngappender/ExtractData.d.ts new file mode 100644 index 000000000..7c0f9c04b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/pngappender/ExtractData.d.ts @@ -0,0 +1,10 @@ +export default ExtractData; + +type BasicDataType = number | string; +type DictDataType = { [key: string]: BasicDataType } | { [key: string]: DictDataType } | { [key: string]: ListDateType }; +type ListDateType = (BasicDataType | ListDateType | DictDataType)[]; +type DataType = BasicDataType | DictDataType | ListDateType; + +declare function ExtractData( + pngBuffer: Uint8Array, +): DataType | Uint8Array; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/pngappender/ExtractData.js b/ui/src/phaser3-rex-plugins/plugins/data/pngappender/ExtractData.js new file mode 100644 index 000000000..35ec4b284 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/pngappender/ExtractData.js @@ -0,0 +1,33 @@ +import GetChunkEndByteIndex from './GetChunkEndByteIndex.js'; +import Uint8ArrayReader from '../../utils/arraybuffers/Uint8ArrayReader.js'; + +var ExtractData = function (pngBuffer) { + var reader = new Uint8ArrayReader(pngBuffer); + + // Get End of last png chunk (IEND) + var pngByteLength = GetChunkEndByteIndex(reader, 'IEND'); + reader.seek(pngByteLength); + if (reader.outOfArray) { + return null; + } + + // Get header0, header1 + var header0 = reader.readUint32(); + var dataType = header0 & 0xf; + var header1 = reader.readUint32(); + // Get myData + var data = reader.readUint8Array(); + if (dataType === 0) { + if (data.length === 0) { + return null; + } else { + // Uint8Array -> string -> JSON + data = (new TextDecoder()).decode(data); + data = JSON.parse(data); + } + } + + return data; +} + +export default ExtractData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/pngappender/GetChunkEndByteIndex.js b/ui/src/phaser3-rex-plugins/plugins/data/pngappender/GetChunkEndByteIndex.js new file mode 100644 index 000000000..61eb81499 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/pngappender/GetChunkEndByteIndex.js @@ -0,0 +1,33 @@ +import Uint8ArrayReader from '../../utils/arraybuffers/Uint8ArrayReader.js'; + +var GetChunkEndByteIndex = function (pngBuffer, chunkType) { + var reader; + if (pngBuffer instanceof (Uint8ArrayReader)) { + reader = pngBuffer; + } else { + reader = new Uint8ArrayReader(pngBuffer); + } + + reader.seek(8); // Skip png header + while (!reader.outOfArray) { + var dataLength = reader.readUint32(true); + if (chunkType === reader.readString(4)) { + return reader.pointer + dataLength + 4; + } else { + reader.seekForward(dataLength + 4); + } + } + + return -1; +} + +/* +Chunk structure: + +- dataLength: 4 bytes -> uint32 (big-endian) +- chunkType: 4 bytes -> string +- data: dataLength bytes -> unit8Array +- crc: 4 bytes -> uint32 (big-endian) +*/ + +export default GetChunkEndByteIndex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/pool/ObjectPool.js b/ui/src/phaser3-rex-plugins/plugins/data/pool/ObjectPool.js new file mode 100644 index 000000000..a730c78fc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/pool/ObjectPool.js @@ -0,0 +1,21 @@ +import Pool from '../../pool.js'; + +class ObjectPool extends Pool { + shutdown() { + var items = this.items, + item; + for (var i = 0, len = items.length; i < len; i++) { + item = items[i]; + if (item.destroy) { // Assume that object has destroy function + item.destroy(); + } + } + items.length = 0; + } + + destroy() { + this.shutdown(); + } +} + +export default ObjectPool; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/restorabledata/DataManager.d.ts b/ui/src/phaser3-rex-plugins/plugins/data/restorabledata/DataManager.d.ts new file mode 100644 index 000000000..fd1421451 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/restorabledata/DataManager.d.ts @@ -0,0 +1,31 @@ +export default DataManager; + +declare class DataManager extends Phaser.Data.DataManager { + constructor( + parent: object, + eventEmitter?: Phaser.Events.EventEmitter + ); + + constructor( + parent: object, + eventEmitter?: Phaser.Events.EventEmitter, + config?: object + ); + + commit(alias?: string): this; + + restore( + version?: string | number, + restoreFromVersion0?: boolean + ): this; + + set version(value: string | number); + get version(): number; + readonly lastVersion: number; + + readonly versionAlias: string; + readonly versionAliases: string[]; + + toJSON(): object; + resetFromJSON(o?: object): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/restorabledata/DataManager.js b/ui/src/phaser3-rex-plugins/plugins/data/restorabledata/DataManager.js new file mode 100644 index 000000000..8ac4f15cd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/restorabledata/DataManager.js @@ -0,0 +1,219 @@ +import Clear from '../../utils/object/Clear.js'; + +const Base = Phaser.Data.DataManager; +const GetValue = Phaser.Utils.Objects.GetValue; +const EventEmitterKlass = Phaser.Events.EventEmitter; + +class DataManager extends Base { + constructor(parent, eventEmitter, config) { + var useDefaultEventEmitter = (eventEmitter === undefined); + if (useDefaultEventEmitter) { + eventEmitter = new EventEmitterKlass(); + } + + super(parent, eventEmitter); + + if (useDefaultEventEmitter) { + var parentEventEmitter = (parent.events) ? parent.events : parent; + if (parentEventEmitter) { + parentEventEmitter.once('destroy', this.destroy, this); + } + } + + this._recordEnable = true; + this.resetFromJSON(config); + + this.events + .on('changedata', this.onValueChange, this) + .on('setdata', function (parent, key, value) { + this.onValueChange(parent, key, value, null); + }, this) + .on('removedata', function (parent, key, value) { + this.onValueChange(parent, key, null, value); + }, this); + } + + resetFromJSON(o) { + this._version = GetValue(o, 'version', 0); + this._versionAlias = GetValue(o, 'versionAlias', ''); + this._repository = GetValue(o, 'repository', []); + this._versionAliases = GetValue(o, 'versionAliases', {}); + var changeList = GetValue(o, 'changeList', {}); + + var data = GetValue(o, 'data', undefined); + if (data) { + this._recordEnable = false; + this.set(data); + this._recordEnable = true; + } else { + // Restore from version 0 to current version + var currentVersion = (this._versionAlias !== '') ? this._versionAlias : this._version; + this._version = 0; + this.restore(currentVersion); + // Restore change list + this._recordEnable = false; + for (var key in changeList) { + this.setValue(key, changeList[key][0]); + } + this._recordEnable = true; + } + + this._changeList = changeList; + } + + toJSON(includeData) { + if (includeData === undefined) { + includeData = false; + } + var o = { + version: this._version, + versionAlias: this._versionAlias, + changeList: this._changeList, + repository: this._repository, + versionAliases: this._versionAliases, + }; + if (includeData) { + o.data = this.list; + } + return o; + } + + get version() { + return this._version; + } + + set version(value) { + var alias; + if (typeof (value) === 'string') { + alias = value; + value = this._versionAliases[value]; + } + if (typeof (value) !== 'number') { + this._versionAlias = ''; + return; + } + + this._versionAlias = (alias) ? alias : ''; + if (value === 0) { + this._recordEnable = false; + super.reset(); + this._version = 0; + Clear(this._changeList); + this._recordEnable = true; + return; + } + + value = Math.min(value, this._repository.length); + + var changeList, merged = {}; + // Reverse current change + for (var key in this._changeList) { + merged[key] = this._changeList[key][1]; + delete this._changeList[key]; + } + + if (this._version === value) { + // Do nothing + } else if (this._version < value) { + // Forward + for (var i = this._version; i < value; i++) { + changeList = this._repository[i]; + for (var key in changeList) { + merged[key] = changeList[key][0]; + } + } + } else { + // Backward + for (var i = this._version - 1; i >= value; i--) { + changeList = this._repository[i]; + for (var key in changeList) { + merged[key] = changeList[key][1]; + } + } + } + + this._version = value; + var value; + this._recordEnable = false; + for (var key in merged) { + value = merged[key]; + if (value === null) { + this.removeValue(key); + } else { + this.setValue(key, value); + } + } + this._recordEnable = true; + } + + get versionAlias() { + return this._versionAlias; + } + + get lastVersion() { + return this._repository.length; + } + + get versionAliases() { + var aliases = []; + for (var name in this._versionAliases) { + aliases.push(name); + } + return aliases; + } + + commit(alias) { + this._repository.length = this._version; + for (var name in this._versionAliases) { + if (this._versionAliases[name] > this._version) { + delete this._versionAliases[name]; + } + } + + this._repository.push(this._changeList); + this._changeList = {}; + this._version++; + + if (typeof (alias) === 'string') { + this._versionAlias = alias; + this._versionAliases[alias] = this._version; + } + return this; + } + + restore(value, restoreFromVersion0) { + if (value === undefined) { + value = (this._versionAlias !== '') ? this._versionAlias : this._version; + } + if (restoreFromVersion0 === undefined) { + restoreFromVersion0 = false; + } + + if (restoreFromVersion0) { + this.version = 0; + } + this.version = value; + return this; + } + + reset() { + this.restore(0); + this._repository.length = 0; + Clear(this._versionAliases); + return this; + } + + onValueChange(parent, key, value, previousValue) { + if (!this._recordEnable) { + return; + } + + if (this._changeList.hasOwnProperty(key)) { + this._changeList[key][0] = value; + } else { + this._changeList[key] = [value, previousValue]; + // [newData, previousData] + } + } +} +export default DataManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/ArrayMethods.js b/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/ArrayMethods.js new file mode 100644 index 000000000..683d170c8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/ArrayMethods.js @@ -0,0 +1,201 @@ +import SpliceOne from '../../utils/array/SpliceOne.js'; +import RandomBetween from '../../utils/math/Between.js'; +import Shuffle from '../../utils/array/Shuffle.js'; +import Clone from '../../utils/object/Clone.js'; + +export default { + isEmpty() { + return (this.items.length === 0); + }, + + get(index) { + return this.items[index]; + }, + + getFirst() { + return this.items[0]; + }, + + getLast() { + return this.items[this.items.length - 1]; + }, + + getRandom() { + var index = RandomBetween(0, this.items.length - 1); + return this.items[index]; + }, + + add(item, index, moveToNewPosition) { + var currentIndex = this.items.indexOf(item); + if (currentIndex !== -1) { + if (moveToNewPosition && (index !== currentIndex)) { + this.remove(undefined, currentIndex); + this.add(item, index); + } + return this; + } + + if ((index === undefined) || (index >= this.items.length)) { + this.items.push(item); + } else { + this.items.splice(index, 0, item); + } + + this.addDestroyCallback(item); + + return this; + }, + + addFirst(item, moveToNewPosition) { + this.add(item, 0, moveToNewPosition); + return this; + }, + + addLast(item, moveToNewPosition) { + this.add(item, undefined, moveToNewPosition); + return this; + }, + + addMultiple(items, index, moveToNewPosition) { + if (index === undefined) { + for (var i = 0, cnt = items.length; i < cnt; i++) { + this.add(items[i]); + } + } else { + for (var i = 0, cnt = items.length; i < cnt; i++) { + if (this.contains(items[i])) { + continue; + } + this.add(items[i], index, moveToNewPosition); + index++; + } + } + return this; + }, + + remove(item, index) { + if (item) { + index = this.items.indexOf(item); + if (index === -1) { + return this; + } + } else { + item = this.items[index]; + if (!item) { + return this; + } + } + + + if (index === (this.items.length - 1)) { + this.items.length -= 1; + } else { + SpliceOne(this.items, index); + } + + this.removeDestroyCallback(item); + + return this; + }, + + onChildDestroy(child, fromScene) { + this.remove(child); + }, + + removeFirst() { + this.remove(undefined, 0); + return this; + }, + + removeLast() { + this.remove(undefined, (this.item.length - 1)); + return this; + }, + + removeMultiple(items) { + for (var i = items.length; i > 0; i--) { + this.remove(items[i - 1]); + } + return this; + }, + + clear(destroyItems) { + var items; + if (destroyItems) { + items = this.cloneItems(); + } + + this.removeDestroyCallback(this.items); + this.items.length = 0; + + if (destroyItems) { + for (var i = items.length; i > 0; i--) { + items[i].destroy(); + } + } + return this; + }, + + clone(out) { + if (out === this) { + return this; + } else if (out === undefined) { + out = this.newList(); + } + + out.clear(); + Clone(this.items, out.items); + out.addDestroyCallback(out.items) + return out; + }, + + pop(index) { + if (index === undefined) { + index = 0; + } + + var item = this.items[index]; + this.remove(undefined, index); + return item; + }, + + popFirst() { + return this.pop(0); + }, + + popLast() { + return this.pop(this.items.length - 1); + }, + + popRandom() { + var index = RandomBetween(0, this.items.length - 1); + return this.pop(index); + }, + + slice(start, end, out) { + var result = this.items.slice(start, (end + 1)); + + if (out === undefined) { + out = this.newList(); + } + out.clear(); + Clone(result, out.items); + out.addDestroyCallback(out.items); + return out; + }, + + reverse() { + this.items.reverse(); + return this; + }, + + sort(callback) { + this.items.sort(callback); + return this; + }, + + shuffle() { + Shuffle(this.items); + return this; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/ContainMethods.js b/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/ContainMethods.js new file mode 100644 index 000000000..af7033b5d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/ContainMethods.js @@ -0,0 +1,25 @@ +export default { + contains(item) { + return (this.items.indexOf(item) !== -1); + }, + + any(listB) { + var items = (this.isList(listB)) ? listB.items : listB; + for (var i = 0, cnt = items; i < cnt; i++) { + if (this.contains(items[i])) { + return true; + } + } + return false; + }, + + all(listB) { + var items = (this.isList(listB)) ? listB.items : listB; + for (var i = 0, cnt = items; i < cnt; i++) { + if (!this.contains(items[i])) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/DestroyCallbackMethods.js b/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/DestroyCallbackMethods.js new file mode 100644 index 000000000..fb28a54bb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/DestroyCallbackMethods.js @@ -0,0 +1,49 @@ +import IsArray from '../../utils/object/IsArray.js'; + +export default { + setAutoCleanupEnable(enabled) { + if (enabled === undefined) { + enabled = true; + } + this.autoCleanupEnable = enabled; + return this; + }, + + addDestroyCallback(gameObject) { + if ((!gameObject) || (!this.autoCleanupEnable)) { + return this; + } + + if (IsArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + this.addDestroyCallback(gameObjects[i]); + } + return this; + } + + if (gameObject.on) { + gameObject.once('destroy', this.onChildDestroy, this); + } + return this; + }, + + removeDestroyCallback(gameObject) { + if ((!gameObject) || (!this.autoCleanupEnable)) { + return this; + } + + if (IsArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + this.removeDestroyCallback(gameObjects[i]); + } + return this; + } + + if (gameObject.off) { + gameObject.off('destroy', this.onChildDestroy, this); + } + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/SetMethods.js b/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/SetMethods.js new file mode 100644 index 000000000..5459dd7a4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/SetMethods.js @@ -0,0 +1,126 @@ +import Clone from "../../utils/object/Clone"; + +export default { + union(listB, out) { + if (this === listB) { + if (this !== out) { + out = this.clone(out); + } + } else if (this === out) { + this.addMultiple(listB.items); + } else if (listB === out) { + listB.addMultiple(this.items); + } else { + if (this.items.length >= listB.items.length) { + out = this.clone(out); + out.addMultiple(listB.items); + } else { + out = listB.clone(out); + out.addMultiple(this.items); + } + } + return out; + }, + + intersect(listB, out) { + if (this === listB) { + if (this !== out) { + out = this.clone(out); + } + } else if (this === out) { + var itemsA = Clone(this.items); + this.clear(); + + var item; + for (var i = 0, cnt = itemsA.length; i < cnt; i++) { + item = itemsA[i]; + if (listB.contains(item)) { + this.add(item); + } + } + } else if (listB === out) { + var itemsB = Clone(listB.items); + listB.clear(); + + var item; + for (var i = 0, cnt = itemsA.length; i < cnt; i++) { + item = itemsB[i]; + if (this.contains(item)) { + listB.add(item); + } + } + } else { + out = this.newList(); + if (this.items.length >= listB.items.length) { + var itemsB = listB.items, item; + for (var i = 0, cnt = itemsB.length; i < cnt; i++) { + item = itemsB[i]; + if (this.contains(item)) { + out.add(item); + } + } + } else { + var itemsA = this.items, item; + for (var i = 0, cnt = itemsA.length; i < cnt; i++) { + item = itemsA[i]; + if (listB.contains(item)) { + out.add(item); + } + } + } + } + return out; + }, + + difference(listB, out) { + if (this === listB) { + if (this === out) { + this.clear(); + } else { + out = this.newList(); + } + } else if (this === out) { + var itemsA = Clone(this.items); + this.clear(); + + var item; + for (var i = 0, cnt = itemsA.length; i < cnt; i++) { + item = itemsA[i]; + if (!listB.contains(item)) { + this.add(item); + } + } + } else if (listB === out) { + var itemsB = Clone(listB.items); + listB.clear(); + + var item; + for (var i = 0, cnt = itemsA.length; i < cnt; i++) { + item = itemsB[i]; + if (!this.contains(item)) { + listB.add(item); + } + } + } else { + out = this.newList(); + if (this.items.length >= listB.items.length) { + var itemsB = listB.items, item; + for (var i = 0, cnt = itemsB.length; i < cnt; i++) { + item = itemsB[i]; + if (!this.contains(item)) { + out.add(item); + } + } + } else { + var itemsA = this.items, item; + for (var i = 0, cnt = itemsA.length; i < cnt; i++) { + item = itemsA[i]; + if (!listB.contains(item)) { + out.add(item); + } + } + } + } + return out; + }, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/UniqueItemList.d.ts b/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/UniqueItemList.d.ts new file mode 100644 index 000000000..6821cdf2d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/UniqueItemList.d.ts @@ -0,0 +1,116 @@ +export default UniqueItemList; + +declare namespace UniqueItemList { + interface IConfig { + items?: any[], + autoCleanup?: boolean, + } +} + +declare class UniqueItemList { + constructor( + config?: UniqueItemList.IConfig + ); + + constructor( + items?: any[] + ); + + getFirst(): ItemType; + + getLast(): ItemType; + + get(index: number): ItemType; + + getRandom(): ItemType; + + getItems(): ItemType[]; + + cloneItems(): ItemType[]; + + readonly length: number; + + isEmpty(): boolean; + + contains(item: ItemType): boolean; + + any(listB: UniqueItemList): boolean; + + all(listB: UniqueItemList): boolean; + + add( + item: ItemType, + index?: number, + moveToNewPosition?: boolean + ): this; + + addLast(item: ItemType): this; + + addFirst(item: ItemType): this; + + addMultiple(items: ItemType[]): this; + + clone(out?: UniqueItemList): UniqueItemList; + + remove(item: ItemType): this; + + remove( + item: undefined | null | false, + index: number + ): this; + + removeFirst(): this; + + removeLast(): this; + + removeMultiple(items: ItemType[]): this; + + clear(destroyItems?: boolean): this; + + pop(index?: number): ItemType; + + popFirst(): ItemType; + + popLast(): ItemType; + + popRandom(): ItemType; + + slice( + startIndex: number, + endIndex: number, + out?: UniqueItemList + ): UniqueItemList; + + sort( + callback: (itemA: ItemType, itemB: ItemType) => number + ): this; + + reverse(): this; + + shuffle(): this; + + union( + listB: UniqueItemList, + out?: UniqueItemList + ): UniqueItemList; + + intersect( + listB: UniqueItemList, + out?: UniqueItemList + ): UniqueItemList; + + difference( + listB: UniqueItemList, + out?: UniqueItemList + ): UniqueItemList; + + call( + callback: (item: ItemType, index: number) => void, + scope?: object + ): this; + + call( + fnName: string, ...args: any + ): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/UniqueItemList.js b/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/UniqueItemList.js new file mode 100644 index 000000000..6f575ecf9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/data/uniqueitemlist/UniqueItemList.js @@ -0,0 +1,90 @@ +import IsPlainObject from '../../utils/object/IsPlainObject.js'; +import GetValue from '../../utils/object/GetValue.js'; +import DestroyCallbackMethods from './DestroyCallbackMethods.js'; +import ContainMethods from './ContainMethods.js'; +import ArrayMethods from './ArrayMethods.js'; +import SetMethods from './SetMethods.js'; +import Clone from '../../utils/object/Clone.js'; +import ArrayCopy from '../../utils/array/Copy.js'; + +class UniqueItemList { + constructor(items, config) { + if (IsPlainObject(items)) { + config = items; + items = GetValue(config, 'items', undefined); + } + + this.items = []; + this.setAutoCleanupEnable(GetValue(config, 'autoCleanup', true)); + if (items) { + this.addMultiple(items); + } + } + + destroy(destroyItems) { + this.clear(destroyItems); + this.items = undefined; + } + + getItems() { + return this.items; + } + + cloneItems(out) { + return Clone(this.items, out); + } + + isList(item) { + return (item instanceof UniqueItemList); + } + + newList(items) { + var config = { + autoCleanup: this.autoCleanupEnable + } + return new UniqueItemList(items, config); + } + + get length() { + return this.items.length; + } + + call(callback, scope) { + if (this.items.length === 0) { + return this; + } + + if (typeof (callback) === 'string') { + var fnName = callback; + ArrayCopy(ARGS, arguments, 1); + var item; + for (var i = 0, cnt = this.items.length; i < cnt; i++) { + item = this.items[i]; + item[fnName].apply(item, ARGS); + } + ARGS.length = 0; + + } else { + for (var i = 0, cnt = this.items.length; i < cnt; i++) { + if (scope) { + callback.call(scope, this.items[i], i); + } else { + callback(this.items[i], i); + } + } + } + return this; + } +} + +var ARGS = []; // reuse this array + +Object.assign( + UniqueItemList.prototype, + DestroyCallbackMethods, + ContainMethods, + ArrayMethods, + SetMethods +) + +export default UniqueItemList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dissolvepipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/dissolvepipeline-plugin.d.ts new file mode 100644 index 000000000..f2832149b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dissolvepipeline-plugin.d.ts @@ -0,0 +1,29 @@ +// import * as Phaser from 'phaser'; +import DissolvePostFxPipeline from './dissolvepipeline'; + +export default DissolvePipelinePlugin; + +declare namespace DissolvePipelinePlugin { + + interface IConfig extends DissolvePostFxPipeline.IConfig { + name?: string, + } + +} + +declare class DissolvePipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: DissolvePipelinePlugin.IConfig + ): DissolvePostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): DissolvePostFxPipeline | DissolvePostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dissolvepipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/dissolvepipeline-plugin.js new file mode 100644 index 000000000..fb75ff573 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dissolvepipeline-plugin.js @@ -0,0 +1,14 @@ +import DissolvePostFxPipeline from './dissolvepipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class DissolvePipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(DissolvePostFxPipeline, 'rexDissolvePostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.DissolvePostFx', DissolvePostFxPipeline); + +export default DissolvePipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dissolvepipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/dissolvepipeline.d.ts new file mode 100644 index 000000000..c577a4361 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dissolvepipeline.d.ts @@ -0,0 +1,2 @@ +import DissolvePostFxPipeline from './shaders/dissolve/DissolvePostFxPipeline'; +export default DissolvePostFxPipeline; diff --git a/ui/src/phaser3-rex-plugins/plugins/dissolvepipeline.js b/ui/src/phaser3-rex-plugins/plugins/dissolvepipeline.js new file mode 100644 index 000000000..db1625e89 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dissolvepipeline.js @@ -0,0 +1,2 @@ +import DissolvePostFxPipeline from './shaders/dissolve/DissolvePostFxPipeline.js'; +export default DissolvePostFxPipeline; diff --git a/ui/src/phaser3-rex-plugins/plugins/drag-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/drag-plugin.d.ts new file mode 100644 index 000000000..60b18cfb6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/drag-plugin.d.ts @@ -0,0 +1,9 @@ +import Drag from './drag'; + +export default class DragPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Drag.IConfig + ): Drag; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/drag-plugin.js b/ui/src/phaser3-rex-plugins/plugins/drag-plugin.js new file mode 100644 index 000000000..8a387d259 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/drag-plugin.js @@ -0,0 +1,20 @@ +import Drag from './drag.js'; + +class DragPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Drag(gameObject, config); + } + +} + +export default DragPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/drag.d.ts b/ui/src/phaser3-rex-plugins/plugins/drag.d.ts new file mode 100644 index 000000000..ba2f083ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/drag.d.ts @@ -0,0 +1,2 @@ +import Drag from './input/drag/Drag'; +export default Drag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/drag.js b/ui/src/phaser3-rex-plugins/plugins/drag.js new file mode 100644 index 000000000..5597795e2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/drag.js @@ -0,0 +1,2 @@ +import Drag from './input/drag/Drag.js'; +export default Drag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dragrotate-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/dragrotate-plugin.d.ts new file mode 100644 index 000000000..68538c3d2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dragrotate-plugin.d.ts @@ -0,0 +1,9 @@ +import DragRotate from './dragrotate'; + +export default class DragRotatePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: DragRotate.IConfig + ): DragRotate; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dragrotate-plugin.js b/ui/src/phaser3-rex-plugins/plugins/dragrotate-plugin.js new file mode 100644 index 000000000..beb645827 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dragrotate-plugin.js @@ -0,0 +1,20 @@ +import DragRotate from './dragrotate.js'; + +class DragRotatePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new DragRotate(scene, config); + } + +} + +export default DragRotatePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dragrotate.d.ts b/ui/src/phaser3-rex-plugins/plugins/dragrotate.d.ts new file mode 100644 index 000000000..b0bb841a6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dragrotate.d.ts @@ -0,0 +1,2 @@ +import DragRotate from './input/dragrotate/DragRotate'; +export default DragRotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dragrotate.js b/ui/src/phaser3-rex-plugins/plugins/dragrotate.js new file mode 100644 index 000000000..3aab549ac --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dragrotate.js @@ -0,0 +1,2 @@ +import DragRotate from './input/dragrotate/DragRotate.js'; +export default DragRotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dragspeed-plugin.js b/ui/src/phaser3-rex-plugins/plugins/dragspeed-plugin.js new file mode 100644 index 000000000..4278f58f6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dragspeed-plugin.js @@ -0,0 +1,20 @@ +import DragSpeed from './dragspeed.js'; + +class DragSpeedPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new DragSpeed(gameObject, config); + } + +} + +export default DragSpeedPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dragspeed.js b/ui/src/phaser3-rex-plugins/plugins/dragspeed.js new file mode 100644 index 000000000..2d4b7247b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dragspeed.js @@ -0,0 +1,2 @@ +import DragSpeed from './input/dragspeed/DragSpeed.js'; +export default DragSpeed; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dropdown-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/dropdown-plugin.d.ts new file mode 100644 index 000000000..70733d768 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dropdown-plugin.d.ts @@ -0,0 +1,9 @@ +import DropDown from './dropdown'; + +export default class DropDownPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: DropDown.IConfig + ): DropDown; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dropdown-plugin.js b/ui/src/phaser3-rex-plugins/plugins/dropdown-plugin.js new file mode 100644 index 000000000..58eda7d70 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dropdown-plugin.js @@ -0,0 +1,18 @@ +import DropDown from './behaviors/dropdown/DropDown.js'; + +class DropDownPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new DropDown(gameObject, config); + } +} + +export default DropDownPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dropdown.d.ts b/ui/src/phaser3-rex-plugins/plugins/dropdown.d.ts new file mode 100644 index 000000000..3462812ba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dropdown.d.ts @@ -0,0 +1,2 @@ +import DropDown from './behaviors/dropdown/Dropdown'; +export default DropDown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dropdown.js b/ui/src/phaser3-rex-plugins/plugins/dropdown.js new file mode 100644 index 000000000..1f7e3968b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dropdown.js @@ -0,0 +1,2 @@ +import DropDown from './behaviors/dropdown/DropDown.js'; +export default DropDown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline-plugin.d.ts new file mode 100644 index 000000000..20395ff41 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline-plugin.d.ts @@ -0,0 +1,30 @@ +// import * as Phaser from 'phaser'; +import DropShadowPostFxPipeline from './dropshadowpipeline'; + + +export default DropShadowPipelinePlugin; + +declare namespace DropShadowPipelinePlugin { + + interface IConfig extends DropShadowPostFxPipeline.IConfig { + name?: string, + } + +} + +declare class DropShadowPipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: DropShadowPipelinePlugin.IConfig + ): DropShadowPostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): DropShadowPostFxPipeline | DropShadowPostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline-plugin.js new file mode 100644 index 000000000..f1f9832dd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline-plugin.js @@ -0,0 +1,14 @@ +import DropShadowPostFxPipeline from './dropshadowpipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class DropShadowPipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(DropShadowPostFxPipeline, 'rexDropShadowPostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.DropShadowPostFx', DropShadowPostFxPipeline); + +export default DropShadowPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline.d.ts new file mode 100644 index 000000000..1f404a793 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline.d.ts @@ -0,0 +1,2 @@ +import DropShadowPostFxPipeline from './shaders/dropshadow/DropShadowPostFxPipeline'; +export default DropShadowPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline.js b/ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline.js new file mode 100644 index 000000000..021faaec4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dropshadowpipeline.js @@ -0,0 +1,2 @@ +import DropShadowPostFxPipeline from './shaders/dropshadow/DropShadowPostFxPipeline.js'; +export default DropShadowPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dynamictext-plugin.js b/ui/src/phaser3-rex-plugins/plugins/dynamictext-plugin.js new file mode 100644 index 000000000..4bf7d012d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dynamictext-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/dynamictext/dynamictext/Factory'; +import Creator from './gameobjects/dynamictext/dynamictext/Creator.js'; +import DynamicText from './gameobjects/dynamictext/dynamictext/DynamicText.js'; +import SetValue from './utils/object/SetValue.js'; + +class DynamicTextPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexDynamicText', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.DynamicText', DynamicText); + +export default DynamicTextPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dynamictext.d.ts b/ui/src/phaser3-rex-plugins/plugins/dynamictext.d.ts new file mode 100644 index 000000000..5220639d2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dynamictext.d.ts @@ -0,0 +1,2 @@ +import DynamicText from './gameobjects/dynamictext/dynamictext/DynamicText'; +export default DynamicText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/dynamictext.js b/ui/src/phaser3-rex-plugins/plugins/dynamictext.js new file mode 100644 index 000000000..6f78571ab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/dynamictext.js @@ -0,0 +1,2 @@ +import DynamicText from './gameobjects/dynamictext/dynamictext/DynamicText.js'; +export default DynamicText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/easedata-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/easedata-plugin.d.ts new file mode 100644 index 000000000..aa910fa1c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/easedata-plugin.d.ts @@ -0,0 +1,8 @@ +import { EaseData } from './easedata.js'; + +export default class ScalePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: EaseData.IConfig + ): EaseData; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/easedata-plugin.js b/ui/src/phaser3-rex-plugins/plugins/easedata-plugin.js new file mode 100644 index 000000000..3e6af8c66 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/easedata-plugin.js @@ -0,0 +1,19 @@ +import { EaseData } from './easedata.js'; + +class EaseDataPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new EaseData(gameObject, config); + } +} + +export default EaseDataPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/easedata.d.ts b/ui/src/phaser3-rex-plugins/plugins/easedata.d.ts new file mode 100644 index 000000000..93db552af --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/easedata.d.ts @@ -0,0 +1,5 @@ +import EaseData from './behaviors/easedata/EaseData'; + +export { + EaseData +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/easedata.js b/ui/src/phaser3-rex-plugins/plugins/easedata.js new file mode 100644 index 000000000..4c2ce2829 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/easedata.js @@ -0,0 +1,5 @@ +import EaseData from './behaviors/easedata/EaseData.js'; + +export { + EaseData +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/easemove-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/easemove-plugin.d.ts new file mode 100644 index 000000000..49bfb0e81 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/easemove-plugin.d.ts @@ -0,0 +1,13 @@ +import { EaseMove, EaseMoveTo, EaseMoveToDestroy, EaseMoveFrom, EaseMoveFromDestroy } from './easemove'; + +export default class ScalePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: EaseMove.IConfig + ): EaseMove; + + moveTo: typeof EaseMoveTo; + moveFrom: typeof EaseMoveFrom; + moveToDestroy: typeof EaseMoveToDestroy; + moveFromDestroy: typeof EaseMoveFromDestroy; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/easemove-plugin.js b/ui/src/phaser3-rex-plugins/plugins/easemove-plugin.js new file mode 100644 index 000000000..0124a891f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/easemove-plugin.js @@ -0,0 +1,31 @@ +import { EaseMove, EaseMoveTo, EaseMoveToDestroy, EaseMoveFrom, EaseMoveFromDestroy } from './easemove.js'; + +class EaseMovePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new EaseMove(gameObject, config); + } +} + +// mixin +var methods = { + moveTo: EaseMoveTo, + moveFrom: EaseMoveFrom, + moveToDestroy: EaseMoveToDestroy, + moveFromDestroy: EaseMoveFromDestroy +} +Object.assign( + EaseMovePlugin.prototype, + methods +); + +export default EaseMovePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/easemove.d.ts b/ui/src/phaser3-rex-plugins/plugins/easemove.d.ts new file mode 100644 index 000000000..009a4e058 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/easemove.d.ts @@ -0,0 +1,27 @@ +import EaseMove from './behaviors/easemove/EaseMove'; +import EaseMoveTo from './behaviors/easemove/EaseMoveTo'; +import EaseMoveFrom from './behaviors/easemove/EaseMoveFrom'; + +declare function EaseMoveToDestroy( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + endX: number | string | undefined, + endY: number | string | undefined, + ease?: string, + easeMove?: EaseMove +): EaseMove; + +declare function EaseMoveFromDestroy( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + startX: number | string | undefined, + startY: number | string | undefined, + ease?: string, + easeMove?: EaseMove +): EaseMove; + +export { + EaseMove, + EaseMoveTo, EaseMoveToDestroy, + EaseMoveFrom, EaseMoveFromDestroy +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/easemove.js b/ui/src/phaser3-rex-plugins/plugins/easemove.js new file mode 100644 index 000000000..c7947d730 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/easemove.js @@ -0,0 +1,17 @@ +import EaseMove from './behaviors/easemove/EaseMove.js'; +import EaseMoveTo from './behaviors/easemove/EaseMoveTo.js'; +import EaseMoveFrom from './behaviors/easemove/EaseMoveFrom.js'; + +var EaseMoveToDestroy = function (gameObject, duration, endX, endY, ease, easeMove) { + return EaseMoveTo(gameObject, duration, endX, endY, ease, true, easeMove); +} + +var EaseMoveFromDestroy = function (gameObject, duration, startX, startY, ease, easeMove) { + return EaseMoveFrom(gameObject, duration, startX, startY, ease, true, easeMove); +} + +export { + EaseMove, + EaseMoveTo, EaseMoveToDestroy, + EaseMoveFrom, EaseMoveFromDestroy +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/effectlayer-plugin.js b/ui/src/phaser3-rex-plugins/plugins/effectlayer-plugin.js new file mode 100644 index 000000000..eb376b6ad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/effectlayer-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/shader/effectlayer/effectlayer/Factory.js'; +import Creator from './gameobjects/shader/effectlayer/effectlayer/Creator.js'; +import EffectLayer from './gameobjects/shader/effectlayer/effectlayer/EffectLayer.js'; +import SetValue from './utils/object/SetValue.js'; + +class EffectLayerPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexEffectLayer', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.EffectLayer', EffectLayer); + +export default EffectLayerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/effectlayer.js b/ui/src/phaser3-rex-plugins/plugins/effectlayer.js new file mode 100644 index 000000000..2f1f48890 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/effectlayer.js @@ -0,0 +1,2 @@ +import EffectLayer from './gameobjects/shader/effectlayer/EffectLayer.js'; +export default EffectLayer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/eightdirection-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/eightdirection-plugin.d.ts new file mode 100644 index 000000000..e5b4e0547 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/eightdirection-plugin.d.ts @@ -0,0 +1,9 @@ +import EightDirection from './eightdirection'; + +export default class EightDirectionPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: EightDirection.IConfig + ): EightDirection; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/eightdirection-plugin.js b/ui/src/phaser3-rex-plugins/plugins/eightdirection-plugin.js new file mode 100644 index 000000000..cc4f28b56 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/eightdirection-plugin.js @@ -0,0 +1,20 @@ +import EightDirection from './eightdirection.js'; + +class EightDirectionPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new EightDirection(gameObject, config); + } + +} + +export default EightDirectionPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/eightdirection.d.ts b/ui/src/phaser3-rex-plugins/plugins/eightdirection.d.ts new file mode 100644 index 000000000..7be5603a6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/eightdirection.d.ts @@ -0,0 +1,2 @@ +import EightDirection from './behaviors/eightdirection/EightDirection'; +export default EightDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/eightdirection.js b/ui/src/phaser3-rex-plugins/plugins/eightdirection.js new file mode 100644 index 000000000..223994c39 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/eightdirection.js @@ -0,0 +1,2 @@ +import EightDirection from './behaviors/eightdirection/EightDirection.js'; +export default EightDirection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/eventpromise-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/eventpromise-plugin.d.ts new file mode 100644 index 000000000..78dc33077 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/eventpromise-plugin.d.ts @@ -0,0 +1,7 @@ +import { WaitEvent, WaitComplete, Delay } from './eventpromise' + +export default class EventPromisePlugin extends Phaser.Plugins.BasePlugin { + waitEvent: typeof WaitEvent; + waitComplete: typeof WaitComplete; + delay: typeof Delay; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/eventpromise-plugin.js b/ui/src/phaser3-rex-plugins/plugins/eventpromise-plugin.js new file mode 100644 index 000000000..e4c14f9af --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/eventpromise-plugin.js @@ -0,0 +1,22 @@ +import { WaitEvent, WaitComplete, Delay } from './eventpromise.js' + +class EventPromisePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } +} + +var methods = { + waitEvent: WaitEvent, + waitComplete: WaitComplete, + delay: Delay, +} + +// mixin +Object.assign( + EventPromisePlugin.prototype, + methods +); + +export default EventPromisePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/eventpromise.d.ts b/ui/src/phaser3-rex-plugins/plugins/eventpromise.d.ts new file mode 100644 index 000000000..5eb1de952 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/eventpromise.d.ts @@ -0,0 +1,3 @@ +import { WaitEvent, WaitComplete } from './utils/promise/WaitEvent'; +import Delay from './utils/promise/Delay'; +export { WaitEvent, WaitComplete, Delay }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/eventpromise.js b/ui/src/phaser3-rex-plugins/plugins/eventpromise.js new file mode 100644 index 000000000..318c7eb1a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/eventpromise.js @@ -0,0 +1,4 @@ +import { WaitEvent, WaitComplete } from './utils/promise/WaitEvent.js'; +import Delay from './utils/promise/Delay.js'; + +export { WaitEvent, WaitComplete, Delay }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/expressionparser-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/expressionparser-plugin.d.ts new file mode 100644 index 000000000..fa34c71ef --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/expressionparser-plugin.d.ts @@ -0,0 +1,11 @@ +import ExpressionParser from './expressionparser'; +import Compile from './math/expressionparser/utils/Compile'; +import CreateProxyContext from './math/expressionparser/utils/CreateProxyContext'; + +export default class ExpressionParserPlugin extends Phaser.Plugins.BasePlugin { + add(): ExpressionParser; + + compile: typeof Compile; + + createProxyContext: typeof CreateProxyContext; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/expressionparser-plugin.js b/ui/src/phaser3-rex-plugins/plugins/expressionparser-plugin.js new file mode 100644 index 000000000..4ca437d61 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/expressionparser-plugin.js @@ -0,0 +1,32 @@ +import ExpressionParser from './expressionparser.js'; +import Compile from './math/expressionparser/utils/Complile.js'; +import CreateProxyContext from './utils/proxy/createproxycontext/CreateProxyContext.js'; +import SetValue from './utils/object/SetValue.js'; + +class ExpressionParserPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add() { + return new ExpressionParser(); + } + + compile(expression) { + return Compile(expression); + } + + createProxyContext(config, baseContext) { + return CreateProxyContext(config, baseContext); + } +} + +SetValue(window, 'RexPlugins.ExpressionParser', ExpressionParser); + +export default ExpressionParserPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/expressionparser.d.ts b/ui/src/phaser3-rex-plugins/plugins/expressionparser.d.ts new file mode 100644 index 000000000..d7b04168a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/expressionparser.d.ts @@ -0,0 +1,2 @@ +import ExpressionParser from './math/expressionparser/ExpressionParser'; +export default ExpressionParser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/expressionparser.js b/ui/src/phaser3-rex-plugins/plugins/expressionparser.js new file mode 100644 index 000000000..806d32fdc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/expressionparser.js @@ -0,0 +1,2 @@ +import ExpressionParser from './math/expressionparser/ExpressionParser.js'; +export default ExpressionParser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fade-in.d.ts b/ui/src/phaser3-rex-plugins/plugins/fade-in.d.ts new file mode 100644 index 000000000..ea7e7f8ad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fade-in.d.ts @@ -0,0 +1,8 @@ +import Fade from './fade'; + +export default function FadeIn( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + alpha?: number | { start: number, end: number }, + fade?: Fade +): Fade; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fade-in.js b/ui/src/phaser3-rex-plugins/plugins/fade-in.js new file mode 100644 index 000000000..145c97e68 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fade-in.js @@ -0,0 +1,37 @@ +import Fade from './fade.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +var FadeIn = function (gameObject, duration, alpha, fade) { + var startAlpha, endAlpha; + if (IsPlainObject(alpha)) { + startAlpha = alpha.start; + endAlpha = alpha.end; + } else { + endAlpha = alpha; + } + if (startAlpha === undefined) { + startAlpha = 0 + } + if (endAlpha === undefined) { + endAlpha = 1; + } + + var config = { + mode: 0, + start: startAlpha, + end: endAlpha, + duration: duration, + } + + if (fade === undefined) { + fade = new Fade(gameObject, config); + } else { + fade.resetFromJSON(config); + } + fade.restart(); + + return fade; +}; + +export default FadeIn; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fade-out-destroy.d.ts b/ui/src/phaser3-rex-plugins/plugins/fade-out-destroy.d.ts new file mode 100644 index 000000000..92182f629 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fade-out-destroy.d.ts @@ -0,0 +1,14 @@ +import Fade from './fade'; + +export default function FadeOutDestroy( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + fade?: Fade +): Fade; + +export default function FadeOutDestroy( + gameObject: Phaser.GameObjects.GameObject, + duration: number, + destroyMode?: boolean, + fade?: Fade +): Fade; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fade-out-destroy.js b/ui/src/phaser3-rex-plugins/plugins/fade-out-destroy.js new file mode 100644 index 000000000..f51b422a7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fade-out-destroy.js @@ -0,0 +1,29 @@ +import Fade from './fade.js'; + +var FadeOutDestroy = function (gameObject, duration, destroyMode, fade) { + if (destroyMode instanceof Fade) { + fade = destroyMode; + destroyMode = undefined; + } + + if (destroyMode === undefined) { + destroyMode = true; + } + + var config = { + mode: (destroyMode) ? 1 : 0, + end: 0, + duration: duration, + } + + if (fade === undefined) { + fade = new Fade(gameObject, config); + } else { + fade.resetFromJSON(config); + } + fade.restart(); + + return fade; +}; + +export default FadeOutDestroy; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fade-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/fade-plugin.d.ts new file mode 100644 index 000000000..cc6ebe026 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fade-plugin.d.ts @@ -0,0 +1,11 @@ +import Fade from './fade'; +import FadeOutDestroy from './fade-out-destroy'; + +export default class FadePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Fade.IConfig + ): Fade; + + fadeOutDestroy: typeof FadeOutDestroy; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fade-plugin.js b/ui/src/phaser3-rex-plugins/plugins/fade-plugin.js new file mode 100644 index 000000000..072777c3d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fade-plugin.js @@ -0,0 +1,29 @@ +import Fade from './fade.js'; +import FadeOutDestroy from './fade-out-destroy.js'; + +class FadePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Fade(gameObject, config); + } +} + +// mixin +var methods = { + fadeOutDestroy: FadeOutDestroy +}; +Object.assign( + FadePlugin.prototype, + methods +); + +export default FadePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fade.d.ts b/ui/src/phaser3-rex-plugins/plugins/fade.d.ts new file mode 100644 index 000000000..156e7198a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fade.d.ts @@ -0,0 +1,2 @@ +import Fade from './behaviors/fade/Fade'; +export default Fade; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fade.js b/ui/src/phaser3-rex-plugins/plugins/fade.js new file mode 100644 index 000000000..394c1737a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fade.js @@ -0,0 +1,2 @@ +import Fade from './behaviors/fade/Fade.js'; +export default Fade; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/filechooser-plugin.js b/ui/src/phaser3-rex-plugins/plugins/filechooser-plugin.js new file mode 100644 index 000000000..7d1a1380d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/filechooser-plugin.js @@ -0,0 +1,29 @@ +import OpenFileChooser from './behaviors/filechooser/Open.js'; +import Factory from './gameobjects/dom/filechooser/Factory.js'; +import Creator from './gameobjects/dom/filechooser/Creator.js'; +import FileChooser from './gameobjects/dom/filechooser/FileChooser.js'; +import SetValue from './utils/object/SetValue.js'; + +class FileChooserPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexFileChooser', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + // Note: Not working in iOS9+ + open(config) { + return OpenFileChooser(this.game, config); + } +} + +SetValue(window, 'RexPlugins.GameObjects.FileChooser', FileChooser); + +export default FileChooserPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/filechooser.d.ts b/ui/src/phaser3-rex-plugins/plugins/filechooser.d.ts new file mode 100644 index 000000000..f41f89e16 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/filechooser.d.ts @@ -0,0 +1,3 @@ +import OpenFileChooser from './behaviors/filechooser/Open'; +import FileChooser from './gameobjects/dom/filechooser/FileChooser'; +export { OpenFileChooser, FileChooser }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/filechooser.js b/ui/src/phaser3-rex-plugins/plugins/filechooser.js new file mode 100644 index 000000000..744ba71a2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/filechooser.js @@ -0,0 +1,3 @@ +import OpenFileChooser from './behaviors/filechooser/Open.js'; +import FileChooser from './gameobjects/dom/filechooser/FileChooser.js'; +export { OpenFileChooser, FileChooser }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/filedropzone-plugin.js b/ui/src/phaser3-rex-plugins/plugins/filedropzone-plugin.js new file mode 100644 index 000000000..3244eafb5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/filedropzone-plugin.js @@ -0,0 +1,28 @@ +import Factory from './gameobjects/dom/filedropzone/Factory.js'; +import Creator from './gameobjects/dom/filedropzone/Creator.js'; +import FileDropZone from './gameobjects/dom/filedropzone/FileDropZone.js'; +import SetValue from './utils/object/SetValue.js'; + +class FileDropZonePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexFileDropZone', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + // Note: Not working in iOS9+ + open(config) { + return OpenFileChooser(this.game, config); + } +} + +SetValue(window, 'RexPlugins.GameObjects.FileDropZone', FileDropZone); + +export default FileDropZonePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/filedropzone.d.ts b/ui/src/phaser3-rex-plugins/plugins/filedropzone.d.ts new file mode 100644 index 000000000..6f630e322 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/filedropzone.d.ts @@ -0,0 +1,2 @@ +import FileDropZone from './gameobjects/dom/filedropzone/FileDropZone'; +export default FileDropZone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/filedropzone.js b/ui/src/phaser3-rex-plugins/plugins/filedropzone.js new file mode 100644 index 000000000..a1bbd7d17 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/filedropzone.js @@ -0,0 +1,2 @@ +import FileDropZone from './gameobjects/dom/filedropzone/FileDropZone.js'; +export default FileDropZone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase-components.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase-components.d.ts new file mode 100644 index 000000000..51a5e0c69 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase-components.d.ts @@ -0,0 +1,27 @@ +import Preload from './firebase/preload/Preload.js'; + +import Broadcast from './firebase/database/broadcast/Broadcast.js'; +import OnlineUserList from './firebase/database/onlineuserlist/OnlineUserList.js'; +import Room from './firebase/database/room/Room.js'; +import SingleRoom from './firebase/database/singleroom/SingleRoom.js'; +import ItemTable from './firebase/database/itemtable/ItemTable.js'; + +import PageLoader from './firebase/firestore/pageloader/PageLoader.js'; +import Files from './firebase/firestore/files/Files.js'; +import IdAlias from './firebase/firestore/idalias/IdAlias.js'; +import LeaderBoard from './firebase/firestore/leaderboard/LeaderBoard.js'; +import Messages from './firebase/firestore/messages/Messages.js'; + +export { + Preload, + Broadcast, + OnlineUserList, + Room, + SingleRoom, + ItemTable, + PageLoader, + Files, + IdAlias, + LeaderBoard, + Messages +}; diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase-components.js b/ui/src/phaser3-rex-plugins/plugins/firebase-components.js new file mode 100644 index 000000000..a1c8b3ae2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase-components.js @@ -0,0 +1,27 @@ +import Preload from './firebase/preload/Preload'; + +import Broadcast from './firebase/database/broadcast/Broadcast'; +import OnlineUserList from './firebase/database/onlineuserlist/OnlineUserList'; +import Room from './firebase/database/room/Room'; +import SingleRoom from './firebase/database/singleroom/SingleRoom'; +import ItemTable from './firebase/database/itemtable/ItemTable'; + +import PageLoader from './firebase/firestore/pageloader/PageLoader'; +import Files from './firebase/firestore/files/Files'; +import IdAlias from './firebase/firestore/idalias/IdAlias'; +import LeaderBoard from './firebase/firestore/leaderboard/LeaderBoard'; +import Messages from './firebase/firestore/messages/Messages'; + +export { + Preload, + Broadcast, + OnlineUserList, + Room, + SingleRoom, + ItemTable, + PageLoader, + Files, + IdAlias, + LeaderBoard, + Messages +}; diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase-plugin.js b/ui/src/phaser3-rex-plugins/plugins/firebase-plugin.js new file mode 100644 index 000000000..a25b94840 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase-plugin.js @@ -0,0 +1,40 @@ +import LoaderCallback from './firebase/preload/LoaderCallback.js'; +import ObjectFactory from './firebase/ObjectFactory.js'; + +import BroadcastFactory from './firebase/database/broadcast/Factory.js'; +import OnlineUserListFactory from './firebase/database/onlineuserlist/Factory.js'; +import RoomFactory from './firebase/database/room/Factory.js'; +import SingleRoomFactory from './firebase/database/singleroom/Factory.js'; +import ItemTableFactory from './firebase/database/itemtable/Factory.js'; + +import PageLoaderFactory from './firebase/firestore/pageloader/Factory.js'; +import FilesFactory from './firebase/firestore/files/Factory.js'; +import IdAliasFactory from './firebase/firestore/idalias/Factory.js'; +import LeaderBoardFactory from './firebase/firestore/leaderboard/Factory.js'; +import MessagesFactory from './firebase/firestore/messages/Factory.js'; + +class FirebasePlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + + this.add = new ObjectFactory(); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + initializeApp(config) { + this.add.initializeApp(config); + return this; + } + + preload(scene, urlConfig, firebaseConfig) { + LoaderCallback.call(scene.sys.load, urlConfig, firebaseConfig); + return this; + } +} + + +export default FirebasePlugin; diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase.js b/ui/src/phaser3-rex-plugins/plugins/firebase.js new file mode 100644 index 000000000..925f0a1e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase.js @@ -0,0 +1,32 @@ +import Preload from './firebase/preload/Preload.js'; +import ObjectFactory from './firebase/ObjectFactory.js' + +import BroadcastFactory from './firebase/database/broadcast/Factory.js'; +import OnlineUserListFactory from './firebase/database/onlineuserlist/Factory.js'; +import RoomFactory from './firebase/database/room/Factory.js'; +import SingleRoomFactory from './firebase/database/singleroom/Factory.js'; +import ItemTableFactory from './firebase/database/itemtable/Factory.js'; + +import PageLoaderFactory from './firebase/firestore/pageloader/Factory.js'; +import FilesFactory from './firebase/firestore/files/Factory.js'; +import IdAliasFactory from './firebase/firestore/idalias/Factory.js'; +import LeaderBoardFactory from './firebase/firestore/leaderboard/Factory.js'; +import MessagesFactory from './firebase/firestore/messages/Factory.js'; + +class FirebasePlugin { + constructor() { + this.add = new ObjectFactory(); + } + + initializeApp(config) { + this.add.initializeApp(config); + return this; + } + + preload(urlConfig, firebaseConfig) { + return Preload(urlConfig, firebaseConfig); + } +} + + +export default FirebasePlugin; diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/ObjectFactory.js b/ui/src/phaser3-rex-plugins/plugins/firebase/ObjectFactory.js new file mode 100644 index 000000000..63bc67288 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/ObjectFactory.js @@ -0,0 +1,15 @@ +class ObjectFactory { + constructor() { + } + + initializeApp(config) { + firebase.initializeApp(config); + return this; + } + + static register(type, callback) { + ObjectFactory.prototype[type] = callback; + } +}; + +export default ObjectFactory; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Broadcast.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Broadcast.d.ts new file mode 100644 index 000000000..5182b6d4e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Broadcast.d.ts @@ -0,0 +1,54 @@ +import EventEmitter from "../../../utils/eventemitter/EventEmitter"; + +export default Broadcast; + +declare namespace Broadcast { + interface IConfig { + root?: string, + senderID?: string, + senderName?: string, + receiverID?: string, + history?: number | boolean, + + eventEmitter?: EventEmitter | false, + } + + type MessageType = string | + { [name: string]: number | string | boolean }; + + interface IReceiveData { + senderID: string, senderName?: string, + message: MessageType + } +} + +declare class Broadcast extends EventEmitter { + constructor( + config?: Broadcast.IConfig + ); + + setSender( + userID: string, userName?: string + ): this; + + setSender( + config: { userID: string, userName?: string } + ): this; + + userID: string; + userName: string; + readonly userInfo: { userID?: string, userName?: string }; + + setReceiver(receiverID: string): this; + receiverID: string; + + send( + message: Broadcast.MessageType + ): Promise; + + startReceiving(): this; + stopReceiving(): this; + + getHistory(): Broadcast.IReceiveData[]; + clearHistory(): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Broadcast.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Broadcast.js new file mode 100644 index 000000000..ec2a276ce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Broadcast.js @@ -0,0 +1,123 @@ +import EventEmitterMethods from '../../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import IsPlainObject from '../../../utils/object/IsPlainObject.js'; +import Send from './Send.js'; +import ReceiveMethods from './ReceiveMethods.js'; +import History from './History.js'; + +class Broadcast { + constructor(config) { + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + this.eventNameMap = GetValue(config, 'eventNames', DefaultEventNames); + + this.database = firebase.database(); + this.setRootPath(GetValue(config, 'root', '')); + + // Sender + this.skipFirst = true; + this.stamp = false; + this.userInfo = { userID: '', userName: undefined }; + this.setSender(GetValue(config, 'senderID', ''), GetValue(config, 'senderName', '')); + this.setReceiver(GetValue(config, 'receiverID', '')); + + // Receiver + this.isReceiving = false; + + // History messages + var historyMaxLength = GetValue(config, 'history', 0); + if (historyMaxLength === true) { + historyMaxLength = -1; + } else if (historyMaxLength === false) { + historyMaxLength = 0; + } + this.history = new History({ + maxLength: historyMaxLength + }); + + } + + shutdown() { + this + .stopReceiving() + .destroyEventEmitter(); + } + + destroy() { + this.shutdown(); + } + + get userID() { + return this.userInfo.userID; + } + + set userID(value) { + this.userInfo.userID = value; + } + + get userName() { + return this.userInfo.userName; + } + + set userName(value) { + this.userInfo.userName = value; + } + + setRootPath(rootPath) { + this.rootPath = rootPath; + this.sendToRef = undefined; + this.receiverRef = undefined; + return this; + } + + setSender(userID, userName) { + if (IsPlainObject(userID)) { + this.userInfo = userID; + } else { + this.userID = userID; + this.userName = userName; + } + return this; + } + + setReceiver(receiverID) { + this.receiverID = receiverID; + return this; + } + + changeUserName(userID, userName) { + if (userID === this.userID) { + this.userName = userName; + } + this.history.changeUserName(userID, userName); + return this; + } + + getHistory() { + return this.history.records; + } + + clearHistory() { + this.history.clear(); + return this; + } +} + +var methods = { + send: Send +} +Object.assign( + Broadcast.prototype, + EventEmitterMethods, + ReceiveMethods, + methods +); + +const DefaultEventNames = { + receive: 'receive' +} + + +export default Broadcast; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Factory.d.ts new file mode 100644 index 000000000..79dda23c6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Factory.d.ts @@ -0,0 +1,5 @@ +import Broadcast from './Broadcast'; + +export default function ( + config: Broadcast.IConfig +): Broadcast; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Factory.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Factory.js new file mode 100644 index 000000000..c6506ab81 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Factory.js @@ -0,0 +1,11 @@ +import Broadcast from './Broadcast.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('broadcast', function (config) { + return new Broadcast(config); +}); + +SetValue(window, 'RexPlugins.Fire.Broadcast', Broadcast); + +export default Broadcast; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/History.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/History.js new file mode 100644 index 000000000..2b59ddb05 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/History.js @@ -0,0 +1,40 @@ +import GetValue from '../../../utils/object/GetValue.js'; + +class History { + constructor(config) { + this.maxLength = GetValue(config, 'maxLength', -1); // -1: Infinity + this.records = []; + } + + add(record) { + if (this.maxLength === 0) { + return this; + } + + this.records.push(record); + if ((this.maxLength > 0) && (this.records.length > this.maxLength)) { + this.records.shift(); + } + return this; + } + + clear() { + this.records.length = 0; + return this; + } + + changeUserName(userID, userName) { + if (this.maxLength === 0) { + return this; + } + + this.records.forEach(function (record) { + if (record.senderID === userID) { + record.senderName = userName; + } + }) + return this; + } +} + +export default History; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/ReceiveMethods.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/ReceiveMethods.js new file mode 100644 index 000000000..188f46bf6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/ReceiveMethods.js @@ -0,0 +1,45 @@ +var methods = { + startReceiving() { + if (this.isReceiving && (this.receiverRef.key === this.receiverID)) { + return this; + } + + this.stopReceiving(); + + this.isReceiving = true; + this.skipFirst = true; // Skip previous message + this.receiverRef = this.database.ref(this.rootPath).child(this.receiverID); + this.receiverRef.on('value', OnReceive, this); + this.receiverRef.onDisconnect().remove(); + return this; + }, + + stopReceiving() { + if (!this.isReceiving) { + return this; + } + + this.isReceiving = false; + this.receiverRef.off('value', OnReceive, this); + this.receiverRef.remove(); + this.receiverRef.onDisconnect().cancel(); + return this; + } +} + +var OnReceive = function (snapshot) { + if (this.skipFirst) { + this.skipFirst = false; + return; + } + var d = snapshot.val(); + if (d == null) { + return; + } + + delete d.stamp; + this.history.add(d); + this.emit(this.eventNameMap.receive, d); +} + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Send.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Send.js new file mode 100644 index 000000000..142301baa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/Send.js @@ -0,0 +1,24 @@ +var Send = function (message) { + if ((!this.sendToRef) || (this.sendToRef.key !== this.receiverID)) { + this.sendToRef = this.database.ref(this.rootPath).child(this.receiverID); + } + + // Clear message + if (message === undefined) { + return this.sendToRef.remove(); // Promise + } + + var d = { + message: message, + senderID: this.userID, + stamp: this.stamp, + }; + if (this.userName !== undefined) { + d.senderName = this.userName; + } + this.skipFirst = false; + this.stamp = !this.stamp; + return this.sendToRef.set(d); +} + +export default Send; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/schema.md b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/schema.md new file mode 100644 index 000000000..9f24f76bd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/broadcast/schema.md @@ -0,0 +1,5 @@ +- + - `message` - Message + - `senderID` - Unique ID of sender + - `senderName` - Name of sender + - `stamp` - Toggle between true and false \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/Factory.d.ts new file mode 100644 index 000000000..5ec6745bd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/Factory.d.ts @@ -0,0 +1,5 @@ +import ItemTable from './ItemTable'; + +export default function ( + config: ItemTable.IConfig +): ItemTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/Factory.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/Factory.js new file mode 100644 index 000000000..1f01c2689 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/Factory.js @@ -0,0 +1,11 @@ +import ItemTable from './ItemTable.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('itemTable', function (config) { + return new ItemTable(config); +}); + +SetValue(window, 'RexPlugins.Fire.ItemTable', ItemTable); + +export default ItemTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/ItemTable.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/ItemTable.d.ts new file mode 100644 index 000000000..491272e10 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/ItemTable.d.ts @@ -0,0 +1,149 @@ +import EventEmitter from "../../../utils/eventemitter/EventEmitter"; + +export default ItemTable; + +declare namespace ItemTable { + + type TableType = 1 | 2 | 3 | '1d' | '2d' | '3d'; + + interface IConfig { + root?: string, + type?: TableType, + + eventEmitter?: EventEmitter | false, + } + + type ValueType = number | string | boolean | + { [name: string]: ValueType }; + + type TransactionCallbackType = (prevValue: ValueType) => ValueType; + +} + +declare class ItemTable extends EventEmitter { + constructor( + config?: ItemTable.IConfig + ); + + setData( + key0: string, + value: ItemTable.ValueType + ): Promise; + + setData( + key0: string, key1: string, + value: ItemTable.ValueType + ): Promise; + + setData( + key0: string, key1: string, key2: string, + value: ItemTable.ValueType + ): Promise; + + incValue( + key0: string, + value: number + ): Promise; + + incValue( + key0: string, key1: string, + value: number + ): Promise; + + incValue( + key0: string, key1: string, key2: string, + value: number + ): Promise; + + removeData( + key0: string + ): Promise; + + removeData( + key0: string, key1: string + ): Promise; + + removeData( + key0: string, key1: string, key2: string + ): Promise; + + updateData( + data: { [path: string]: ItemTable.ValueType } + ): Promise; + + transaction( + key0: string, + callback: ItemTable.TransactionCallbackType + ): Promise; + + transaction( + key0: string, key1: string, + callback: ItemTable.TransactionCallbackType + ): Promise; + + transaction( + key0: string, key1: string, key2: string, + callback: ItemTable.TransactionCallbackType + ): Promise; + + removeDataOnDisconnect( + key0: string, + ): Promise; + + removeDataOnDisconnect( + key0: string, key1: string, + ): Promise; + + removeDataOnDisconnect( + key0: string, key1: string, key2: string, + ): Promise; + + setDataOnDisconnect( + key0: string, + value: ItemTable.ValueType + ): Promise; + + setDataOnDisconnect( + key0: string, key1: string, + value: ItemTable.ValueType + ): Promise; + + setDataOnDisconnect( + key0: string, key1: string, key2: string, + value: ItemTable.ValueType + ): Promise; + + startUpdate(): this; + stopUpdate(): this; + + getData( + ): ItemTable.ValueType; + + getData( + key0: string, + ): ItemTable.ValueType; + + getData( + key0: string, key1: string, + ): ItemTable.ValueType; + + getData( + key0: string, key1: string, key2: string, + ): ItemTable.ValueType; + + cloneData( + ): ItemTable.ValueType; + + cloneData( + key0: string, + ): ItemTable.ValueType; + + cloneData( + key0: string, key1: string, + ): ItemTable.ValueType; + + cloneData( + key0: string, key1: string, key2: string, + ): ItemTable.ValueType; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/ItemTable.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/ItemTable.js new file mode 100644 index 000000000..34f44d883 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/ItemTable.js @@ -0,0 +1,144 @@ +import EventEmitterMethods from '../../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import Table from '../../../utils/struct/Tree.js'; +import ColumnUpdater from './read/ColumnUpdater.js'; +import RowUpdater from './read/RowUpdater.js'; +import Pagepdater from './read/PageUpdater.js'; +import Init from './read/Init.js'; +import SetData from './write/SetData.js'; +import RemoveData from './write/RemoveData.js'; +import IncValue from './write/IncValue.js'; +import Transaction from './write/Transaction.js'; +import UpdateData from './write/UpdateData.js'; +import RemoveDataOnDisconnect from './write/RemoveDataOnDisconnect.js'; +import SetDataOnDisconnect from './write/SetDataOnDisconnect.js'; + +class ItemTable { + constructor(config) { + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + this.eventNameMap = GetValue(config, 'eventNames', DefaultEventNames); + + this.database = firebase.database(); + this.table = new Table(); + this.setTableType(GetValue(config, 'type', 3)); + this.setRootPath(GetValue(config, 'root', '')); + this.initialFlag = false; + } + + shutdown() { + this.updater.destroy(); + this + .destroyEventEmitter() + .stopUpdate(); + } + + destroy() { + this.shutdown(); + } + + setRootPath(rootPath) { + this.rootPath = rootPath; + this.updater.setRootPath(rootPath); + return this; + } + + setTableType(type) { + if (typeof (type) === 'string') { + type = TABLE_TYPE[type]; + } + this.tableType = type; + var UpdaterClass = UpdaterClasses[type]; + this.updater = new UpdaterClass({ + type: type, + eventEmitter: this.getEventEmitter(), + eventNames: this.eventNameMap, + table: this.table + }) + + + return this; + } + + getRootRef() { + return this.database.ref(this.rootPath) + } + + getRef(key0, key1, key2) { + var ref = this.getRootRef(); + ref = (key0) ? ref.child(key0) : ref; + ref = (key1) ? ref.child(key1) : ref; + ref = (key2) ? ref.child(key2) : ref; + return ref; + } + + startUpdate() { + Init.call(this); + this.updater + .startUpdate(); + return this; + } + + stopUpdate() { + this.updater.stopUpdate(); + return this; + } + + clear() { + this.updater.clear(); + return this; + } + + getData() { + return this.table.getValue(arguments); + } + + cloneData() { + return this.table.cloneValue(arguments); + } +} + +var UpdaterClasses = { + 1: ColumnUpdater, + 2: RowUpdater, + 3: Pagepdater +}; + +var methods = { + setData: SetData, + removeData: RemoveData, + incValue: IncValue, + transaction: Transaction, + updateData: UpdateData, + removeDataOnDisconnect: RemoveDataOnDisconnect, + setDataOnDisconnect: SetDataOnDisconnect +} +Object.assign( + ItemTable.prototype, + EventEmitterMethods, + methods +); + +const TABLE_TYPE = { + '1d': 1, + '2d': 2, + '3d': 3 +} + +const DefaultEventNames = { + init: 'init', + update: 'update', + addkey0: 'addkey0', + removekey0: 'removekey0', + changekey0: 'changekey0', + addkey1: 'addkey1', + removekey1: 'removekey1', + changekey1: 'changekey1', + addkey2: 'addkey2', + removekey2: 'removekey2', + changekey2: 'changekey2' +} + +export default ItemTable \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/BaseUpdater.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/BaseUpdater.js new file mode 100644 index 000000000..921ebeefe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/BaseUpdater.js @@ -0,0 +1,154 @@ +import EventEmitterMethods from '../../../../utils/eventemitter/EventEmitterMethods.js'; + +class BaseUpdater { + constructor(config) { + // Event emitter + this.setEventEmitter(config.eventEmitter, config.EventEmitterClass); + + this.parent = config.parent; + this.key = config.key; + if (this.parent) { + this.fullKeyPath = ExtendKeyPath(this.parent.fullKeyPath, this.key); + } else { + this.fullKeyPath = ''; + } + this.type = config.type; + this.eventNameMap = config.eventNames; + this.table = config.table; + + this.database = firebase.database(); + this.setRootPath(); + this.children = {}; + } + + shutdown() { + this + .stopUpdate() + .clear() + .destroyEventEmitter(); + } + + destroy() { + this.shutdown(); + } + + setRootPath(rootPath) { + if (rootPath === undefined) { + var parentRootPath = (this.parent) ? this.parent.rootPath : ''; + rootPath = `${parentRootPath}/${this.key}`; + } + this.rootPath = rootPath; + + var child; + for (var key in this.children) { + child = this.children[key]; + if (child instanceof BaseUpdater) { + child.setRootPath(); + } + } + return this; + } + + get rootRef() { + return this.database.ref(this.rootPath); + } + + load() { + var self = this; + return this.rootRef.once('value') + .then(function (snapshot) { + // Won't add any child + var value = snapshot.val() || {}; + self.table.setValue(value) + return Promise.resolve(value) + }) + } + + setData(key, value) { + if (key === undefined) { + this.clear(); // Clear + } else if (value === undefined) { + var data = key; // JSON data + for (key in this.children) { // Not in new data + if (!data.hasOwnProperty(key)) { + this.removeChild(key); + } + } + for (key in data) { + this.setChildData(key, data[key]); + } + } else { + this.setChildData(key, value); // Pass data to column-updater + } + return this; + } + + clear() { + this.table.removeKey(this.fullKeyPath); + for (var key in this.children) { + this.removeChild(key); + } + return this; + } + + // Overwrite + get childClass() { + return undefined; + } + + // Overwrite + setChildData(key, data) { + var keyPath = ExtendKeyPath(this.fullKeyPath, key); + this.table.setValue(keyPath, data); + + if (!this.children.hasOwnProperty(key)) { + if (this.childClass) { + var child = new this.childClass({ + parent: this, + key: key, + type: this.type, + eventEmitter: this.getEventEmitter(), + eventNames: this.eventNameMap, + table: this.table + }); + child.startUpdate(); + this.children[key] = child; + } + } else { + this.children[key].setData(data); + } + return this; + } + + // Overwrite + removeChild(key) { + if (this.children.hasOwnProperty(key)) { + this.children[key].destroy(); + delete this.children[key]; + } + return this; + } + + // Overwrite + startUpdate() { } + + // Overwrite + stopUpdate() { } +} + +var ExtendKeyPath = function (baseKeyPath, newKey) { + if ((baseKeyPath == null) || (baseKeyPath === '')) { + return newKey; + } else if ((newKey == null) || (newKey === '')) { + return baseKeyPath; + } else { + return `${baseKeyPath}.${newKey}`; + } +} + +Object.assign( + BaseUpdater.prototype, + EventEmitterMethods +); + +export default BaseUpdater; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/ColumnUpdater.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/ColumnUpdater.js new file mode 100644 index 000000000..1e7dd06b2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/ColumnUpdater.js @@ -0,0 +1,80 @@ +import BaseUpdater from './BaseUpdater.js'; + +class ColumnUpdater extends BaseUpdater { + startUpdate() { + this.rootRef.on('child_added', this.addCol, this); + this.rootRef.on('child_removed', this.removeCol, this); + this.rootRef.on('child_changed', this.changeColValue, this); + return this; + } + + stopUpdate() { + this.rootRef.off('child_added', this.addCol, this); + this.rootRef.off('child_removed', this.removeCol, this); + this.rootRef.off('child_changed', this.changeColValue, this); + return this; + } + + addCol(snapshot) { + var key = snapshot.key, + value = snapshot.val(); + this.setData(key, value); + + switch (this.type) { + case 1: + this.emit(this.eventNameMap.addkey0, key, value); + break; + case 2: + this.emit(this.eventNameMap.addkey1, this.key, key, value); + break; + default: // 3 + this.emit(this.eventNameMap.addkey2, this.pageKey, this.key, key, value); + break; + } + this.emit(this.eventNameMap.update, this.table.data); + } + + removeCol(snapshot) { + var key = snapshot.key; + this.removeChild(key); + + switch (this.type) { + case 1: + this.emit(this.eventNameMap.removekey0, key); + break; + case 2: + this.emit(this.eventNameMap.removekey1, this.key, key); + break; + default: // 3 + this.emit(this.eventNameMap.removekey2, this.pageKey, this.key, key); + break; + } + this.emit(this.eventNameMap.update, this.table.data); + } + + changeColValue(snapshot) { + var key = snapshot.key, + value = snapshot.val(); + this.setData(key, value); + + switch (this.type) { + case 1: + this.emit(this.eventNameMap.changekey0, key, value); + break; + case 2: + this.emit(this.eventNameMap.changekey1, this.key, key, value); + break; + default: // 3 + this.emit(this.eventNameMap.changekey2, this.pageKey, this.key, key, value); + break; + } + this.emit(this.eventNameMap.update, this.table.data); + } + + get pageKey() { + return this.parent.key; + } + +} + +export default ColumnUpdater; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/Init.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/Init.js new file mode 100644 index 000000000..b96a27b31 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/Init.js @@ -0,0 +1,14 @@ +var Init = function () { + var self = this; + this.initialFlag = false; + return this.updater + .clear() + .load() + .then(function (value) { + self.initialFlag = true; + self.emit(self.eventNames.init, value); + return Promise.resolve(value); + }) +} + +export default Init; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/PageUpdater.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/PageUpdater.js new file mode 100644 index 000000000..4990ca458 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/PageUpdater.js @@ -0,0 +1,41 @@ +import BaseUpdater from './BaseUpdater.js'; +import RowUpdater from './RowUpdater.js'; + +class PageUpdater extends BaseUpdater { + constructor(config) { + super(config); + } + + startUpdate() { + this.rootRef.on('child_added', this.addPage, this); + this.rootRef.on('child_removed', this.removePage, this); + return this; + } + + stopUpdate() { + this.rootRef.off('child_added', this.addPage, this); + this.rootRef.off('child_removed', this.removePage, this); + return this; + } + + addPage(snapshot) { + var key = snapshot.key, + value = snapshot.val(); + this.setData(key, value); + + this.emit(this.eventNameMap.addkey0, key, value); + } + + removePage(snapshot) { + var key = snapshot.key; + this.removeChild(key); + + this.emit(this.eventNameMap.removekey0, key); + } + + get childClass() { + return RowUpdater; + } +} + +export default PageUpdater; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/RowUpdater.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/RowUpdater.js new file mode 100644 index 000000000..ab0d1385c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/read/RowUpdater.js @@ -0,0 +1,55 @@ +import BaseUpdater from './BaseUpdater.js'; +import ColumnUpdater from './ColumnUpdater.js'; + +class RowUpdater extends BaseUpdater { + startUpdate() { + this.rootRef.on('child_added', this.addRow, this); + this.rootRef.on('child_removed', this.removeRow, this); + return this; + } + + stopUpdate() { + this.rootRef.off('child_added', this.addRow, this); + this.rootRef.off('child_removed', this.removeRow, this); + return this; + } + + addRow(snapshot) { + var key = snapshot.key, + value = snapshot.val(); + this.setData(key, value); + + switch (this.type) { + case 2: + this.emit(this.eventNameMap.addkey0, this.key, key, value); + break; + default: // 3 + this.emit(this.eventNameMap.addkey1, this.key, key, value); + break; + } + } + + removeRow(snapshot) { + var key = snapshot.key; + this.removeChild(key); + + switch (this.type) { + case 2: + this.emit(this.eventNameMap.removekey0, key); + break; + default: // 3 + this.emit(this.eventNameMap.removekey1, this.key, key); + break; + } + } + + get childClass() { + return ColumnUpdater; + } + + get pageKey() { + return this.parent.key; + } +} + +export default RowUpdater; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/schema.md b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/schema.md new file mode 100644 index 000000000..50dec1210 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/schema.md @@ -0,0 +1,14 @@ +## 1D table + +- : Number, string, or JSON + +## 2D table + +- + - : Number, string, or JSON + +## 3D table + +- + - + - : Number, string, or JSON diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/IncValue.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/IncValue.js new file mode 100644 index 000000000..41e60c834 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/IncValue.js @@ -0,0 +1,27 @@ +var IncValue = function () { + var key0, key1, key2, value; + + switch (arguments.length) { + case 4: + [key0, key1, key2, value] = arguments; + break; + case 3: + [key0, key1, value] = arguments; + break; + case 2: + [key0, value] = arguments; + break; + default: + value = arguments[0]; + break; + } + + return this.getRef(key0, key1, key2).transaction(function (preValue) { + if (preValue === null) { + preValue = 0; + } + return (preValue + value); + }); +} + +export default IncValue; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/RemoveData.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/RemoveData.js new file mode 100644 index 000000000..8955b7a5d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/RemoveData.js @@ -0,0 +1,19 @@ +var RemoveData = function () { + var key0, key1, key2; + + switch (arguments.length) { + case 3: + [key0, key1, key2] = arguments; + break; + case 2: + [key0, key1] = arguments; + break; + default: + key0 = arguments[0]; + break; + } + + return this.getRef(key0, key1, key2).remove(); +} + +export default RemoveData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/RemoveDataOnDisconnect.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/RemoveDataOnDisconnect.js new file mode 100644 index 000000000..2744ec2d7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/RemoveDataOnDisconnect.js @@ -0,0 +1,19 @@ +var RemoveDataOnDisconnect = function () { + var key0, key1, key2; + + switch (arguments.length) { + case 3: + [key0, key1, key2] = arguments; + break; + case 2: + [key0, key1] = arguments; + break; + case 1: + key0 = arguments[0]; + break; + } + + return this.getRef(key0, key1, key2).onDisconnect().remove(); +} + +export default RemoveDataOnDisconnect; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/SetData.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/SetData.js new file mode 100644 index 000000000..5daca28c2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/SetData.js @@ -0,0 +1,22 @@ +var SetData = function () { + var key0, key1, key2, value; + + switch (arguments.length) { + case 4: + [key0, key1, key2, value] = arguments; + break; + case 3: + [key0, key1, value] = arguments; + break; + case 2: + [key0, value] = arguments; + break; + default: + value = arguments[0]; + break; + } + + return this.getRef(key0, key1, key2).set(value); +} + +export default SetData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/SetDataOnDisconnect.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/SetDataOnDisconnect.js new file mode 100644 index 000000000..809b3f3ad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/SetDataOnDisconnect.js @@ -0,0 +1,22 @@ +var SetDataOnDisconnect = function () { + var key0, key1, key2, value; + + switch (arguments.length) { + case 4: + [key0, key1, key2, value] = arguments; + break; + case 3: + [key0, key1, value] = arguments; + break; + case 2: + [key0, value] = arguments; + break; + default: + value = arguments[0]; + break; + } + + return this.getRef(key0, key1, key2).onDisconnect().set(value); +} + +export default SetDataOnDisconnect; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/Transaction.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/Transaction.js new file mode 100644 index 000000000..fa2d1d5df --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/Transaction.js @@ -0,0 +1,23 @@ +var Transaction = function () { + var key0, key1, key2, callback; + + switch (arguments.length) { + case 4: + [key0, key1, key2, callback] = arguments; + break; + case 3: + [key0, key1, callback] = arguments; + break; + case 2: + [key0, callback] = arguments; + break; + default: + callback = arguments[0]; + break; + } + + // callback: function(preValue) { return newValue; } + return this.getRef(key0, key1, key2).transaction(callback); +} + +export default Transaction; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/UpdateData.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/UpdateData.js new file mode 100644 index 000000000..07c9148dc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/itemtable/write/UpdateData.js @@ -0,0 +1,5 @@ +var UpdateData = function (data) { + return this.getRef().update(data); +} + +export default UpdateData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/ChangeUserName.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/ChangeUserName.js new file mode 100644 index 000000000..f348d3c9f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/ChangeUserName.js @@ -0,0 +1,24 @@ +var ChangeUserName = function (userName) { + var self = this; + return new Promise(function (resolve, reject) { + var userRef = self.getUserRef(); + if (userRef) { // Find userRef + resolve(userRef) + } else { // Query userRef + var query = self.rootRef.orderByChild('userID').equalTo(self.userID); + query.once('child_added') + .then(function (snapshot) { + resolve(snapshot.ref) + }) + } + }) + .then(function (userRef) { // Set userName + return userRef.child('userName').set(userName) + }) + .then(function () { + self.userName = userName; + return Promise.resolve(); + }) +} + +export default ChangeUserName; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Factory.d.ts new file mode 100644 index 000000000..e7bdafd98 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Factory.d.ts @@ -0,0 +1,5 @@ +import OnlineUserList from './OnlineUserList'; + +export default function ( + config: OnlineUserList.IConfig +): OnlineUserList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Factory.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Factory.js new file mode 100644 index 000000000..9599445d0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Factory.js @@ -0,0 +1,11 @@ +import OnlineUserList from './OnlineUserList.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('onlineUserList', function (config) { + return new OnlineUserList(config); +}); + +SetValue(window, 'RexPlugins.Fire.OnlineUserList', OnlineUserList); + +export default OnlineUserList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Join.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Join.js new file mode 100644 index 000000000..391a9447e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Join.js @@ -0,0 +1,69 @@ +import Delay from '../../../utils/promise/Delay.js'; + +var Join = function (userID, userName) { + if (userID === undefined) { + userID = this.userID; + userName = this.userName; + } + + if (this.contains(userID)) { + return Promise.resolve(); // Promise + } + + // Prepare data + var d = { + userID: userID, + userName: userName + }; + var maxUsers = this.maxUsers; + var rootRef = this.database.ref(this.rootPath); + var userRef = rootRef.push(); + + return userRef.onDisconnect().remove() + .then(function () { + return userRef.set(d) + }) + .then(function () { + return Delay(0); + }) + .then(function () { + // No user count limitation + if (maxUsers === 0) { + self.isInList = true; + return Promise.resolve(); + } + + // Has user count limitation + return rootRef.limitToFirst(maxUsers).once('value') + .then(function (snapshot) { + if (Contains(snapshot, userID)) { + self.isInList = true; + return Promise.resolve(); + } + + self.isInList = false; + // UserID is not in firstN list + return userRef.remove() + .then(function () { + return userRef.onDisconnect().cancel() + }) + .then(function () { + return Promise.reject() + }) + }); + }) +}; + +var Contains = function (snapshot, userID) { + var result = false; + snapshot.forEach(function (childSnapshot) { + var user = childSnapshot.val(); + if (user.userID === userID) { + result = true; + return true; + } + }); + return result; +} + +export default Join; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Leave.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Leave.js new file mode 100644 index 000000000..1e684426f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/Leave.js @@ -0,0 +1,14 @@ +var Leave = function (userID) { + if (userID === undefined) { + userID = this.userID; + } + + if (!this.contains(userID)) { + return Promise.resolve(); // Promise + } + var itemID = this.userID2ItemID[userID]; + var userRef = this.database.ref(this.rootPath).child(itemID); + return userRef.remove(); // Promise +} + +export default Leave; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/OnlineUserList.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/OnlineUserList.d.ts new file mode 100644 index 000000000..858ce426c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/OnlineUserList.d.ts @@ -0,0 +1,58 @@ +import EventEmitter from "../../../utils/eventemitter/EventEmitter"; + +export default OnlineUserList; + +declare namespace OnlineUserList { + + interface IConfig { + root?: string, + maxUsers?: number, + + userID?: string, userName?: string, + + eventEmitter?: EventEmitter | false, + } +} + +declare class OnlineUserList extends EventEmitter { + constructor( + config?: OnlineUserList.IConfig + ); + + setUser( + userID: string, userName?: string + ): this; + + setUser( + config: { userID: string, userName?: string } + ): this; + + userID: string; + userName: string; + readonly userInfo: { userID?: string, userName?: string }; + + join( + ): Promise; + + leave( + userID?: string, + ): Promise; + + changeUserName( + userName: string + ): Promise; + + getUsers( + ): { userID?: string, userName?: string, }[]; + + isFirstUser( + userID?: string + ): boolean; + + isFull(): boolean; + + readonly maxUsers: number; + + readonly isInList: boolean; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/OnlineUserList.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/OnlineUserList.js new file mode 100644 index 000000000..af3e2ecce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/OnlineUserList.js @@ -0,0 +1,203 @@ +import EventEmitterMethods from '../../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import IsPlainObject from '../../../utils/object/IsPlainObject.js'; +import ItemList from '../utils/itemlist/ItemList.js'; +import Join from './Join.js'; +import Leave from './Leave.js'; +import ChangeUserName from './ChangeUserName.js'; + +class OnlineUserList { + constructor(config) { + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + + this.database = firebase.database(); + this.setRootPath(GetValue(config, 'root', '')); + + this.userInfo = { userID: '', userName: '' }; + this.setUser(GetValue(config, 'userID', ''), GetValue(config, 'userName', '')); + this.setMaxUsers(GetValue(config, 'maxUsers', 0)); + this.userList = new ItemList({ + eventEmitter: this.getEventEmitter(), + itemIDKey: 'joinAt', + eventNames: { + add: GetValue(config, 'eventNames.join', 'join'), + remove: GetValue(config, 'eventNames.leave', 'leave'), + update: GetValue(config, 'eventNames.update', 'update'), + change: GetValue(config, 'eventNames.change', 'change'), + init: GetValue(config, 'eventNames.init', 'init'), + changename: GetValue(config, 'eventNames.changename', 'changename') + } + }); + + this.isInList = false; + this.userID2ItemID = {}; + this.userList + .on(this.userList.eventNames.add, function (user) { + this.userID2ItemID[user.userID] = user.joinAt; + if (user.userID === this.userInfo.userID) { + this.emit(this.userList.eventNames.init, this.getUsers()); + } + }, this) + .on(this.userList.eventNames.remove, function (user) { + delete this.userID2ItemID[user.userID]; + + if (user.userID === this.userID) { + this.isInList = false; + } + }, this) + .on(this.userList.eventNames.change, function (currUserInfo, prevUserInfo) { + var userID = currUserInfo.userID, + userName = currUserInfo.userName, + prevUserName = prevUserInfo.userName; + if (userName !== prevUserName) { + this.emit(this.userList.eventNames.changename, userID, userName, prevUserName); + } + }, this) + } + + shutdown() { + this + .stopUpdate() + .destroyEventEmitter() + .leave(); + + this.userList.shutdown(); + } + + destroy() { + this.shutdown(); + } + + get userID() { + return this.userInfo.userID; + } + + set userID(value) { + this.userInfo.userID = value; + } + + get userName() { + return this.userInfo.userName; + } + + set userName(value) { + this.userInfo.userName = value; + } + + setRootPath(rootPath) { + this.rootPath = rootPath; + return this; + } + + get rootRef() { + return this.database.ref(this.rootPath); + } + + setUser(userID, userName) { + if (IsPlainObject(userID)) { + this.userInfo = userID; + } else { + this.userID = userID; + this.userName = userName; + } + return this; + } + + setMaxUsers(maxUsers) { + this.maxUsers = maxUsers; + return this; + } + + clear() { + this.userList.clear(); + return this; + } + + forEach(callback, scope) { + this.userList.forEach(callback, scope); + return this; + } + + isFull() { + if (this.maxUsers === 0) { + return false; + } + return (this.userList.getItems().length >= this.maxUsers); + } + + isFirstUser(userID) { + if (userID === undefined) { + userID = this.userID; + } + var user = this.usersList.getItems()[0]; + return (user && (user.userID === userID)); + } + + getUser(userID) { + if (userID === undefined) { + userID = this.userID; + } + if (!this.contains(userID)) { + return null; + } + var itemID = this.userID2ItemID[userID]; + return this.userList.getItemFromItemID(itemID); + } + + getUsers() { + return this.userList.getItems(); + } + + get rootRef() { + return this.database.ref(this.rootPath); + } + + getUserRef(userID) { + if (userID === undefined) { + userID = this.userID; + } + if (!this.contains(userID)) { + return null; + } + var itemID = this.userID2ItemID[userID]; + return this.rootRef.child(itemID); + } + + contains(userID) { + if (userID === undefined) { + userID = this.userID; + } + return this.userID2ItemID.hasOwnProperty(userID); + } + + startUpdate() { + var query = this.database.ref(this.rootPath); + if (this.maxUsers > 0) { + query = query.limitToFirst(this.maxUsers); + } + this.userList.startUpdate(query); + return this; + } + + stopUpdate() { + this.userList.stopUpdate(); + return this; + } +} + +var methods = { + join: Join, + leave: Leave, + changeUserName: ChangeUserName +} + +Object.assign( + OnlineUserList.prototype, + EventEmitterMethods, + methods +); + +export default OnlineUserList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/schema.md b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/schema.md new file mode 100644 index 000000000..955dcf481 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/onlineuserlist/schema.md @@ -0,0 +1,3 @@ +- + - `userID` - Unique ID of user + - `userName` - Name of the user \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeFilterData.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeFilterData.js new file mode 100644 index 000000000..6857cb7eb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeFilterData.js @@ -0,0 +1,20 @@ +var ChangeFilterData = function (roomID, filterData) { + if (arguments.length === 1) { + filterData = roomID; + roomID = undefined; + } + if (roomID === undefined) { + roomID = this.roomID; + } + + var self = this; + return this.hasRoom(roomID) + .then(function (hasRoom) { + if (!hasRoom) { + return Promise.resolve(); + } + return self.getRoomFilterRef(roomID).child('data').update(filterData) + }) +} + +export default ChangeFilterData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeRoomName.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeRoomName.js new file mode 100644 index 000000000..eed19b891 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeRoomName.js @@ -0,0 +1,23 @@ +var ChangeRoomName = function (roomID, roomName) { + if (arguments.length === 1) { + roomName = roomID; + roomID = undefined; + } + if (roomID === undefined) { + roomID = this.roomID; + } + + var self = this; + return this.hasRoom(roomID) + .then(function (hasRoom) { + if (!hasRoom) { + return Promise.resolve(); + } + var d = {}; + d[`room-filters/${roomID}/name`] = roomName; + d[`room-data/${roomID}/name`] = roomName; + return self.getRootRef().update(d) + }) +} + +export default ChangeRoomName; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeRoomState.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeRoomState.js new file mode 100644 index 000000000..8e7deafc4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeRoomState.js @@ -0,0 +1,39 @@ +import { GetFilterString } from './utils/RoomFilterMethods.js'; + +var ChangeRoomState = function (roomID, roomState) { + if (arguments.length === 1) { + roomState = roomID; + roomID = undefined; + } + if (roomID === undefined) { + roomID = this.roomID; + } + + var self = this; + return this.hasRoom(roomID) + .then(function (hasRoom) { + if (!hasRoom) { + return Promise.resolve(); + } + + var filter = GetFilterString(roomState, self.roomType); + var d = {}; + d[`room-filters/${roomID}/filter`] = filter; + d[`room-data/${roomID}/filter`] = filter; + return self.getRootRef().update(d) + }) +} + +var OpenRoom = function (roomID) { + return this.setRoomState(roomID, 'open'); +} + +var CloseRoom = function (roomID) { + return this.setRoomState(roomID, 'closed'); +} + +export { + ChangeRoomState, + OpenRoom, + CloseRoom +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeUserName.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeUserName.js new file mode 100644 index 000000000..c1bc5ccb6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/ChangeUserName.js @@ -0,0 +1,5 @@ +var ChangeUserName = function (userName) { + return this.userList.changeUserName(userName); +} + +export default ChangeUserName; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/CreateRandomRoom.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/CreateRandomRoom.js new file mode 100644 index 000000000..fb7ab732c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/CreateRandomRoom.js @@ -0,0 +1,29 @@ +import GetValue from '../../../utils/object/GetValue.js'; +import GetRandomWord from '../../../utils/string/GetRandomWord.js'; + +var CreateRandomRoom = function (config) { + if (config === undefined) { + config = {}; + } + + var digits = GetValue(config, 'digits', 10); + var candidates = GetValue(config, 'candidates', '0123456789'); + var retry = GetValue(config, 'retry', 1000); + + return TryCreateRandomRoom.call(this, digits, candidates, retry, config); +} + +var TryCreateRandomRoom = function (digits, candidates, retry, config) { + config.roomID = GetRandomWord(digits, digits, candidates); + if (retry <= 0) { + return Promise.reject(config); + } + retry--; + var self = this; + return this.createRoom(config) + .catch(function () { + return TryCreateRandomRoom.call(self, digits, candidates, retry, config); + }) +} + +export default CreateRandomRoom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/CreateRoom.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/CreateRoom.js new file mode 100644 index 000000000..3e35e2d8b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/CreateRoom.js @@ -0,0 +1,115 @@ +import MergeRight from '../../../utils/object/MergeRight.js'; +import { GetFilterString } from './utils/RoomFilterMethods.js'; +import OnJoinRoom from './utils/OnJoinRoom.js'; + +var TryCreateRoom = function (config) { + if (config === undefined) { + config = {}; + } + if (config.roomID == null) { + config.roomID = this.getRoomRef().push().key; + } + + var self = this; + return RegisterRoom.call(self, config.roomID) + .then(function () { // Create room + return CreateRoom.call(self, config); + }); +} + +var RegisterRoom = function (roomID) { + return this.getRoomAliveRef(roomID) + .transaction(function (value) { + if (value === null) { // Room is not existed, register success + return true; + } + else { // Room is existed, register fail + return; // Abort the transaction + } + }); +} + +var CreateRoom = function (config) { + config = MergeRight(DefaultConfig, config); + var roomID = config.roomID; + var roomName = config.roomName; + var roomType = config.roomType; + var doorState = config.door; + var join = config.join; + var filterData = config.filterData; + + var roomRef = this.getRoomRef(roomID); + var roomFilterRef = this.getRoomFilterRef(roomID); + var roomMetadataRef = this.getRoomDataRef(roomID); + + // Remove room when creater is offline + this.isRemoveRoomWhenLeft = !config.presisted; + if (this.isRemoveRoomWhenLeft) { + roomRef.onDisconnect().remove(); + roomFilterRef.onDisconnect().remove(); + roomMetadataRef.onDisconnect().remove(); + } + + var filter = GetFilterString(doorState, roomType); + + var d = {}; + + // Room-filter + var roomFilterData = { + filter: filter, + name: roomName + }; + if (filterData) { + roomFilterData.data = filterData; + } + d[`room-filters/${roomID}`] = roomFilterData; + + // Room-metadata + var roomMetadata = { + name: roomName, + filter: filter, + maxUsers: config.maxUsers, + moderators: {} + }; + roomMetadata.moderators[this.userID] = this.userName; + d[`room-data/${roomID}`] = roomMetadata; + + + var self = this; + return new Promise(function (resolve, reject) { + if (join) { + var promise = self.userList + .setRootPath(self.getUserListPath(roomID)) + .setMaxUsers(0) // Don't test max user count + .join(); // Promise + self.userList + .setMaxUsers(config.maxUsers); + return promise.then(resolve, reject); + } else { + return resolve(); + } + }) + .then(function () { + return self.getRootRef().update(d) + }) + .then(function () { + self.isRoomCreator = true; + if (join) { + OnJoinRoom.call(self, config); + } + return Promise.resolve(config); + }) +} + +var DefaultConfig = { + roomID: '', + roomName: '', + roomType: '', + maxUsers: 0, + presisted: false, + door: 'open', + join: true, + filterData: undefined +} + +export default TryCreateRoom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/Factory.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/Factory.js new file mode 100644 index 000000000..4b9f9726a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/Factory.js @@ -0,0 +1,11 @@ +import Room from './Room.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('room', function (config) { + return new Room(config); +}); + +SetValue(window, 'RexPlugins.Fire.Room', Room); + +export default Room; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/GetRefMethods.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/GetRefMethods.js new file mode 100644 index 000000000..0dfef0304 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/GetRefMethods.js @@ -0,0 +1,87 @@ +import { GetFilterString } from './utils/RoomFilterMethods.js'; + +var Methods = { + getRootRef(childKey) { + var ref = this.database.ref(this.rootPath); + if (childKey) { + ref = ref.child(childKey); + } + return ref; + }, + + getRoomRef(roomID, childKey) { + var ref = this.getRootRef('rooms'); + if (roomID !== undefined) { + ref = ref.child(roomID); + if (childKey !== undefined) { + ref = ref.child(childKey); + } + } + return ref; + }, + + getRoomAliveRef(roomID) { + return this.getRoomRef(roomID, 'alive'); + }, + + getUserListRef(roomID) { + return this.getRoomRef(roomID, 'users'); + }, + + getRoomFilterRef(roomID) { + var ref = this.getRootRef('room-filters'); + if (roomID !== undefined) { + ref = ref.child(roomID); + } + return ref; + }, + + getRoomDataRef(roomID) { + var ref = this.getRootRef('room-data'); + if (roomID !== undefined) { + ref = ref.child(roomID); + } + return ref; + }, + + // TODO: ?? + getUserDataRef(userID) { + var ref = this.getRootRef('user-data'); + if (userID !== undefined) { + ref = ref.child(userID); + } + return ref; + }, + + getRoomDataPath(roomID, childKey) { + var path = `${this.rootPath}/rooms/${roomID}`; + if (childKey) { + path += `/${childKey}`; + } + return path; + }, + + getUserListPath(roomID) { + return this.getRoomDataPath(roomID, 'users'); + }, + + getItemTablePath(roomID, key) { + return `${this.getRoomDataPath(roomID, 'tables')}/${key}`; + }, + + getRoomListQuery(roomType, roomState) { + if (roomState === undefined) { + roomState = 'open'; + } + var query = this.getRoomFilterRef(); + query = query.orderByChild('filter'); + if (roomType === undefined) { + query = query.startAt(roomState).endAt(`${roomState}~`); + } else { + query = query.equalTo(GetFilterString(roomState, roomType)); + } + return query; + } +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/GetRoomList.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/GetRoomList.js new file mode 100644 index 000000000..c44da7e3c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/GetRoomList.js @@ -0,0 +1,12 @@ +var GetRoomList = function (roomType, roomState) { + var self = this; + return new Promise(function (resolve, reject) { + self.roomList + .once('roomlist.update', function (rooms) { + resolve(rooms) + }) + .startUpdate(self.getRoomListQuery(roomType, roomState)); + }) +} + +export default GetRoomList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/GetUserList.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/GetUserList.js new file mode 100644 index 000000000..388b8c588 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/GetUserList.js @@ -0,0 +1,23 @@ +import ItemList from '../utils/itemlist/ItemList.js'; + +var GetUserList = function (roomID) { + if (roomID === undefined) { + return this.userList.getUsers(); + } + + var self = this; + return new Promise(function (resolve, reject) { + var userList = new ItemList({ + itemIDKey: 'joinAt', + mode: 'once' + }) + + userList + .once('update', function (users) { + resolve(users) + }) + .startUpdate(self.getUserListRef(roomID)); + }) +} + +export default GetUserList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/HasRoom.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/HasRoom.js new file mode 100644 index 000000000..131feea09 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/HasRoom.js @@ -0,0 +1,13 @@ +var HasRoom = function (roomID) { + if (roomID === this.roomID) { + return Promise.resolve(true); + } + + return this.getRoomDataRef(roomID).once('value') + .then(function (snapshot) { + var hasRoom = (snapshot.val() !== null); + return Promise.resolve(hasRoom); + }) +} + +export default HasRoom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/IsRoomOpened.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/IsRoomOpened.js new file mode 100644 index 000000000..8c4ec27bf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/IsRoomOpened.js @@ -0,0 +1,33 @@ +import { GetRoomState } from './utils/RoomFilterMethods.js'; + +var IsRoomOpened = function (metadata) { + if (metadata == null) { + return false; + } + + var state = GetRoomState(metadata.filter); + if (state === 'closed') { + return false; + } + + var userID = this.userID; + var IsModerator = metadata.moderators.hasOwnProperty(userID); + if (IsModerator) { + return true; + } + + switch (metadata.permission) { + case 'black-list': + var blackList = metadata['black-list']; + return !(blackList && blackList.hasOwnProperty(userID)); + + case 'white-list': + var whiteList = metadata['white-list']; + return whiteList && whiteList.hasOwnProperty(userID); + + default: // 'anyone' + return true; + } +} + +export default IsRoomOpened; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/JoinRandomRoom.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/JoinRandomRoom.js new file mode 100644 index 000000000..99d3be3b7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/JoinRandomRoom.js @@ -0,0 +1,32 @@ +import GetValue from '../../../utils/object/GetValue.js'; +import Shuffle from '../../../utils/array/Shuffle.js'; + +var JoinRandomRoom = function (config) { + if (config === undefined) { + config = {}; + } + + var roomType = GetValue(config, 'roomType', ''); + var roomState = GetValue(config, 'door', 'open'); + var self = this; + return this.getRoomList(roomType, roomState) + .then(function (rooms) { + Shuffle(rooms); + return JoinNextRoom.call(self, config, rooms, 0); + }) +} + +var JoinNextRoom = function (config, rooms, index) { + if (index === rooms.length) { + return Promise.reject(); + } + config.roomID = rooms[index].roomID; + index++; + var self = this; + return this.joinRoom(config) + .catch(function () { + return JoinNextRoom.call(self, config, rooms, index); + }) +} + +export default JoinRandomRoom; diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/JoinRoom.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/JoinRoom.js new file mode 100644 index 000000000..0b3097b72 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/JoinRoom.js @@ -0,0 +1,59 @@ +import { GetRoomType } from './utils/RoomFilterMethods.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import OnJoinRoom from './utils/OnJoinRoom.js'; + +var TryJoinRoom = function (config) { + var leftThenJoin = GetValue(config, 'leftThenJoin', true); + + var self = this; + if (leftThenJoin) { + return this.leaveRoom() + .then(function () { + return JoinRoom.call(self, config); + }) + } else { + return JoinRoom.call(self, config); + } +} + +var JoinRoom = function (config) { + var roomID = GetValue(config, 'roomID', undefined); + if (roomID === undefined) { + return Promise.reject(); + } + + this.isRemoveRoomWhenLeft = false; + var self = this; + return IsRoomOpened.call(self, config) + .then(function (metadata) { + return self.userList + .setRootPath(self.getUserListPath(config.roomID)) + .setMaxUsers(metadata.maxUsers) + .join(); + }) + .then(function () { + OnJoinRoom.call(self, config); + return Promise.resolve(config); + }) +} + +var IsRoomOpened = function (config) { + var self = this; + return this.getRoomDataRef(config.roomID).once('value') + .then(function (snapshot) { + var metadata = snapshot.val(); + if (metadata === null) { // Can't find room + return Promise.reject(); + } + + config.roomName = metadata.name; + config.roomType = GetRoomType(metadata.filter); + if (!self.isRoomOpened(metadata)) { + return Promise.reject(); + } else { + return Promise.resolve(metadata); + } + }); +} + +export default TryJoinRoom; diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/KickUser.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/KickUser.js new file mode 100644 index 000000000..b2ee04bb4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/KickUser.js @@ -0,0 +1,12 @@ +var KickUser = function (userID) { + if (!this.userList.contains(userID)) { + return Promise.resolve(); + } else if (userID === this.userID) { + return this.leaveRoom(); + } else { + // TODO: Who can kick user? + return this.userList.leave(userID); + } +} + +export default KickUser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/LeaveRoom.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/LeaveRoom.js new file mode 100644 index 000000000..b9a869dc8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/LeaveRoom.js @@ -0,0 +1,21 @@ +var LeaveRoom = function () { + if (!this.isInRoom()) { + return Promise.resolve(); + } + + // 'userlist.leave' event -> 'room.leave' event -> then + this.leftRoomFlag = true; + if (this.isRemoveRoomWhenLeft) { + // Remove room, include user list + return this.removeRoom() + } else { + var prevRoomInfo = this.getRoomInfo(); + // Leave user list only + return this.userList.leave() + .then(function () { + return Promise.resolve(prevRoomInfo) + }) + } +} + +export default LeaveRoom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/Methods.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/Methods.js new file mode 100644 index 000000000..d87b622e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/Methods.js @@ -0,0 +1,43 @@ +import CreateRoom from './CreateRoom.js'; +import CreateRandomRoom from './CreateRandomRoom.js'; +import JoinRoom from './JoinRoom.js'; +import JoinRandomRoom from './JoinRandomRoom.js'; +import LeaveRoom from './LeaveRoom.js'; +import RemoveRoom from './RemoveRoom.js'; +import KickUser from './KickUser.js'; +import IsRoomOpened from './IsRoomOpened.js'; +import { ChangeRoomState, OpenRoom, CloseRoom } from './ChangeRoomState.js'; +import ChangeFilterData from './ChangeFilterData.js'; +import ChangeUserName from './ChangeUserName.js'; +import ChangeRoomName from './ChangeRoomName.js'; +import GetUserList from './GetUserList.js'; +import GetRoomList from './GetRoomList.js'; +import HasRoom from './HasRoom.js'; +import GetRefMethods from './GetRefMethods.js'; + +var Methods = { + createRoom: CreateRoom, + createRandomRoom: CreateRandomRoom, + joinRoom: JoinRoom, + joinRandomRoom: JoinRandomRoom, + leaveRoom: LeaveRoom, + removeRoom: RemoveRoom, + kickUser: KickUser, + isRoomOpened: IsRoomOpened, + changeRoomState: ChangeRoomState, + changeFilterData: ChangeFilterData, + changeUserName: ChangeUserName, + changeRoomName: ChangeRoomName, + openRoom: OpenRoom, + closeRoom: CloseRoom, + getUserList: GetUserList, + getRoomList: GetRoomList, + hasRoom: HasRoom +} + +Object.assign( + Methods, + GetRefMethods +); + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/RemoveRoom.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/RemoveRoom.js new file mode 100644 index 000000000..6a173eb46 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/RemoveRoom.js @@ -0,0 +1,21 @@ +var RemoveRoom = function (roomID) { + if (roomID === undefined) { + roomID = this.roomID; + } + if (roomID === undefined) { + return Promise.resolve(); + } + + var d = {}; + d[`room-filter/${roomID}`] = null; + d[`room-data/${roomID}`] = null; + d[`rooms/${roomID}`] = null; + + var prevRoomInfo = this.getRoomInfo(); + return this.getRootRef().update(d) + .then(function () { + return Promise.resolve(prevRoomInfo); + }) +} + +export default RemoveRoom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/Room.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/Room.js new file mode 100644 index 000000000..159e75adc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/Room.js @@ -0,0 +1,130 @@ +import EventEmitterMethods from '../../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import CreateUserList from './utils/CreateUserList.js'; +import CreateRoomList from './utils/CreateRoomList.js'; +import CreateBroadcast from './utils/CreateBroadcast.js'; +import CreateTables from './utils/CreateTables.js'; +import IsPlainObject from '../../../utils/object/IsPlainObject.js'; +import Methods from './Methods.js'; + +class Room { + constructor(config) { + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + + this.database = firebase.database() + this.rootPath = GetValue(config, 'root', ''); + + // User properties + this.userInfo = { userID: '', userName: '' }; + this.setUser(GetValue(config, 'userID', ''), GetValue(config, 'userName', '')); + // Room properties + this.isRoomCreator = false; + this.roomID = undefined; + this.roomName = undefined; + this.roomType = undefined; + this.doorState = undefined; + this.leftRoomFlag = false; + this.isRemoveRoomWhenLeft = undefined; + // User list + this.userList = CreateUserList.call(this, config); + // Room list + this.roomList = CreateRoomList.call(this, config); + // Broadcast + this.broadcast = CreateBroadcast.call(this, config); + // Item tables + this.tables = CreateTables.call(this, config); + } + + shutdown() { + var self = this; + this + .destroyEventEmitter() + .leaveRoom() + .then(function () { + self.userList.destroy(); + self.userList = undefined; + + self.roomList.destroy(); + self.roomList = undefined; + + self.broadcast.destroy(); + self.broadcast = undefined; + }) + } + + destroy() { + this.shutdown(); + } + + get userID() { + return this.userInfo.userID; + } + + set userID(value) { + this.userInfo.userID = value; + } + + get userName() { + return this.userInfo.userName; + } + + set userName(value) { + this.userInfo.userName = value; + } + + getRoomInfo(roomID, roomName) { + if (roomID === undefined) { + roomID = this.roomID; + } + if (roomName === undefined) { + roomName = this.roomName; + } + return { roomID: roomID, roomName: roomName }; + } + + setUser(userID, userName) { + if (IsPlainObject(userID)) { + this.userInfo = userID; + } else { + this.userID = userID; + this.userName = userName; + } + return this; + } + + isInRoom(roomID) { + return (roomID === undefined) ? (this.roomID !== undefined) : (this.roomID === roomID); + } + + isFull() { + return this.userList.isFull(); + } + + isFirstUser(userID) { + return this.userList.isFirstUser(userID); + } + + getUsers() { + return this.userList.getUsers(); + } + + get maxUsers() { + return this.userList.maxUsers; + } + + getTable(key) { + return this.tables[key]; + } +} + + +Object.assign( + Room.prototype, + EventEmitterMethods, + Methods +); + +export default Room; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/schema.md b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/schema.md new file mode 100644 index 000000000..56064f7bb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/schema.md @@ -0,0 +1,65 @@ +``` +# Filter to monitor opened rooms +room-filters/ + + filter - open/closed + "|" + public/private/... + name - The display name of the room + data - null, or JSON data + + +# Header of room, write by owner of room. Each room has unique roomID +room-data/ + + name - The display name of the room + + # monitor filter to catch room open/closed event + filter - open/closed + "|" + public/private/... + + # moderators of this room + moderators/ + - Unique ID of user + + # join permission + permission - null("anyone")/("black-list")/("white-list") + black-list/ + - Unique ID of user + white-list/ + - Unique ID of user + # ignore room if user can not join + + maxUsers - The maximum number of users that can join this room. + # limit the amount of users + + table/ + - + - + - : value + +# Body of room data. Each room has unique roomID +rooms/ + + alive - true or null + + # users in this room. + users/ + + userID - Unique ID of user + userName - The name of the user + + broadcast/ + - `message` - Message + - `senderID` - Unique ID of sender + - `senderName` - Name of sender + - `stamp` - Toggle between true and false + + +# Write by each user, user could join to many rooms +user-data\ + + user/ + ID - Unique ID of user + name - The display name of the user + room/ + ID - The id of the room + name - The display name of the room +``` \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateBroadcast.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateBroadcast.js new file mode 100644 index 000000000..cbdfd6e59 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateBroadcast.js @@ -0,0 +1,37 @@ +import Broadcast from '../../broadcast/Broadcast.js'; +import GetValue from '../../../../utils/object/GetValue.js'; + +var CreateBroadcast = function (config) { + var broadcastConfig = GetValue(config, 'broadcast', true); + if (!broadcastConfig) { + return null; + } + + var broadcast = new Broadcast({ + eventEmitter: this.getEventEmitter(), + eventNames: { + receive: 'broadcast.receive' + }, + + receiverID: 'boradcast', + senderID: this.userInfo, + history: GetValue(broadcastConfig, 'history', false) + }); + + this + .on('room.join', function (roomConfig) { + broadcast + .setRootPath(this.getRoomDataPath(roomConfig.roomID)) + .startReceiving() + }, this) + .on('room.leave', function () { + broadcast.stopReceiving() + }, this) + .on('userlist.changename', function (userID, userName) { + broadcast.changeUserName(userID, userName); + }, this) + + return broadcast; +} + +export default CreateBroadcast; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateRoomList.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateRoomList.js new file mode 100644 index 000000000..46f1aac5b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateRoomList.js @@ -0,0 +1,20 @@ +import ItemList from '../../utils/itemlist/ItemList.js'; + +var CreateRoomList = function (config) { + var roomList = new ItemList({ + eventEmitter: this.getEventEmitter(), + root: this.getRoomFilterRef(), + itemIDKey: 'roomID', + eventNames: { + update: 'roomlist.update', + add: 'roomlist.add', + remove: 'roomlist.remove', + change: 'roomlist.change' + }, + mode: 'once' + }) + + return roomList; +} + +export default CreateRoomList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateTables.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateTables.js new file mode 100644 index 000000000..2c497bc11 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateTables.js @@ -0,0 +1,55 @@ +import ItemTable from '../../itemtable/ItemTable.js'; +import GetValue from '../../../../utils/object/GetValue.js'; + +var CreateTables = function (config) { + var tablesConfig = GetValue(config, 'tables', undefined); + if (tablesConfig === undefined) { + return {}; + } + + var tableConfig; + var tables = {}; + for (var i = 0, cnt = tablesConfig.length; i < cnt; i++) { + tableConfig = tablesConfig[i]; + tables[tableConfig.key] = CreateTable.call(this, tableConfig); + } + + return tables; +} +var CreateTable = function (config) { + var key = config.key; + var table = new ItemTable({ + eventEmitter: this.getEventEmitter(), + root: this.getItemTablePath(this.roomID, key), + + type: GetValue(config, 'type', 1), + eventNames: { + init: `tables.${key}.init`, + update: `tables.${key}.update`, + addkey0: `tables.${key}.addkey0`, + removekey0: `tables.${key}.removekey0`, + changekey0: `tables.${key}.changekey0`, + addkey1: `tables.${key}.addkey1`, + removekey1: `tables.${key}.removekey1`, + changekey1: `tables.${key}.changekey1`, + addkey2: `tables.${key}.addkey2`, + removekey2: `tables.${key}.removekey2`, + changekey2: `tables.${key}.changekey2` + } + }); + + this + .on('room.join', function () { + table + .startUpdate() + }) + .on('room.leave', function () { + table + .clear() + .stopUpdate() + }) + + return table; +} + +export default CreateTables; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateUserList.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateUserList.js new file mode 100644 index 000000000..e87d24b87 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/CreateUserList.js @@ -0,0 +1,51 @@ +import OnlineUserList from '../../onlineuserlist/OnlineUserList.js'; + +var CreateUserList = function (config) { + var userList = new OnlineUserList({ + eventEmitter: this.getEventEmitter(), + eventNames: { + join: 'userlist.join', // Any user join + leave: 'userlist.leave', // Any user leave + update: 'userlist.update', // Update user list + change: 'userlist.change', // Any user(name) change + init: 'userlist.init', + changename: 'userlist.changename' + }, + + userID: this.userInfo + }); + userList + .on('userlist.leave', function (user) { + if (user.userID === this.userID) { + OnLeftRoom.call(this); // Current user is left or kicked + } + }, this) + + this + .on('room.join', function () { + userList + .startUpdate() + }) + .on('room.leave', function () { + userList + .stopUpdate() + .clear() + }) + + return userList; +} + +var OnLeftRoom = function () { + this.emit('room.leave'); + + // Clear room info later + var self = this; + setTimeout(function () { + self.roomID = undefined; + self.roomName = undefined; + self.doorState = undefined; + self.leftRoomFlag = false; + }, 0); +} + +export default CreateUserList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/OnJoinRoom.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/OnJoinRoom.js new file mode 100644 index 000000000..b59a6b550 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/OnJoinRoom.js @@ -0,0 +1,9 @@ +var OnJoinRoom = function (config) { + this.roomID = config.roomID; + this.roomName = config.roomName; + this.roomType = config.roomType; + + this.emit('room.join', config); +} + +export default OnJoinRoom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/RoomFilterMethods.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/RoomFilterMethods.js new file mode 100644 index 000000000..7a2ba7f01 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/room/utils/RoomFilterMethods.js @@ -0,0 +1,20 @@ +var GetRoomState = function (filterString) { + return filterString.split('|')[0]; +} + +var GetRoomType = function (filterString) { + return filterString.split('|')[1]; +} + +var GetFilterString = function (roomState, roomType) { + if (roomType === undefined) { + roomType = ''; + } + return `${roomState}|${roomType}`; +} + +export { + GetRoomState, + GetRoomType, + GetFilterString +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/ChangeUserName.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/ChangeUserName.js new file mode 100644 index 000000000..c1bc5ccb6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/ChangeUserName.js @@ -0,0 +1,5 @@ +var ChangeUserName = function (userName) { + return this.userList.changeUserName(userName); +} + +export default ChangeUserName; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/Factory.d.ts new file mode 100644 index 000000000..7b2b6d223 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/Factory.d.ts @@ -0,0 +1,5 @@ +import SingleRoom from './SingleRoom'; + +export default function ( + config: SingleRoom.IConfig +): SingleRoom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/Factory.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/Factory.js new file mode 100644 index 000000000..9c1b1a3c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/Factory.js @@ -0,0 +1,11 @@ +import SingleRoom from './SingleRoom.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('singleRoom', function (config) { + return new SingleRoom(config); +}); + +SetValue(window, 'RexPlugins.Fire.SingleRoom', SingleRoom); + +export default SingleRoom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/GetRefMethods.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/GetRefMethods.js new file mode 100644 index 000000000..9c4739380 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/GetRefMethods.js @@ -0,0 +1,31 @@ +var Methods = { + getRoomRef(childKey) { + var ref = this.database.ref(this.rootPath); + if (childKey) { + ref = ref.child(childKey); + } + return ref; + }, + + getUserListRef() { + return this.getRoomRef('users'); + }, + + getRoomDataPath(childKey) { + var path = this.rootPath; + if (childKey) { + path += `/${childKey}`; + } + return path; + }, + + getUserListPath() { + return this.getRoomDataPath('users'); + }, + + getItemTablePath(key) { + return `${this.getRoomDataPath('tables')}/${key}`; + } +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/GetUserList.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/GetUserList.js new file mode 100644 index 000000000..5b87c72c2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/GetUserList.js @@ -0,0 +1,5 @@ +var GetUserList = function () { + return this.userList.getUsers(); +} + +export default GetUserList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/JoinRoom.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/JoinRoom.js new file mode 100644 index 000000000..23a8d5d18 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/JoinRoom.js @@ -0,0 +1,10 @@ +var JoinRoom = function () { + var self = this; + return this.userList.join() + .then(function () { + self.emit('room.join'); + return Promise.resolve(); + }) +} + +export default JoinRoom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/KickUser.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/KickUser.js new file mode 100644 index 000000000..b2ee04bb4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/KickUser.js @@ -0,0 +1,12 @@ +var KickUser = function (userID) { + if (!this.userList.contains(userID)) { + return Promise.resolve(); + } else if (userID === this.userID) { + return this.leaveRoom(); + } else { + // TODO: Who can kick user? + return this.userList.leave(userID); + } +} + +export default KickUser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/LeaveRoom.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/LeaveRoom.js new file mode 100644 index 000000000..cd3627a3f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/LeaveRoom.js @@ -0,0 +1,11 @@ +var LeaveRoom = function () { + if (!this.isInRoom()) { + return Promise.resolve(); + } + + // 'userlist.leave' event -> 'room.leave' event -> then + this.leftRoomFlag = true; + return this.userList.leave(); +} + +export default LeaveRoom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/Methods.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/Methods.js new file mode 100644 index 000000000..c6838d192 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/Methods.js @@ -0,0 +1,21 @@ +import JoinRoom from './JoinRoom.js'; +import LeaveRoom from './LeaveRoom.js'; +import KickUser from './KickUser.js'; +import ChangeUserName from './ChangeUserName.js'; +import GetUserList from './GetUserList.js'; +import GetRefMethods from './GetRefMethods.js'; + +var Methods = { + joinRoom: JoinRoom, + leaveRoom: LeaveRoom, + kickUser: KickUser, + changeUserName: ChangeUserName, + getUserList: GetUserList +} + +Object.assign( + Methods, + GetRefMethods +); + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/SingleRoom.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/SingleRoom.d.ts new file mode 100644 index 000000000..717c9a036 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/SingleRoom.d.ts @@ -0,0 +1,76 @@ +import EventEmitter from "../../../utils/eventemitter/EventEmitter"; +import Broadcast from "../broadcast/Broadcast"; +import ItemTable from "../itemtable/ItemTable"; + +export default SingleRoom; + +declare namespace SingleRoom { + interface ITableConfig { + key: string, + type?: ItemTable.TableType + } + + interface IConfig { + root?: string, + maxUsers?: number, + broadcast: boolean | + { + history?: number | boolean, + }, + tables?: undefined | ITableConfig[], + + userID?: string, userName?: string, + + eventEmitter?: EventEmitter | false, + } +} + +declare class SingleRoom extends EventEmitter { + constructor(config?: SingleRoom.IConfig); + + setUser( + userID: string, userName?: string + ): this; + + setUser( + config: { userID: string, userName?: string } + ): this; + + userID: string; + userName: string; + readonly userInfo: { userID?: string, userName?: string }; + + joinRoom( + ): Promise; + + leaveRoom( + ): Promise; + + kickUser( + userID: string + ): Promise; + + getUsers( + ): { userID?: string, userName?: string, }[]; + + isFirstUser( + userID?: string + ): boolean; + + isFull(): boolean; + + readonly maxUsers: number; + + isInRoom(): boolean; + + readonly broadcast: Broadcast; + + changeUserName( + userName: string + ): Promise; + + getTable( + key: string + ): ItemTable; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/SingleRoom.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/SingleRoom.js new file mode 100644 index 000000000..1f09cfcfe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/SingleRoom.js @@ -0,0 +1,95 @@ +import EventEmitterMethods from '../../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import CreateUserList from './utils/CreateUserList.js'; +import CreateBroadcast from './utils/CreateBroadcast.js'; +import CreateTables from './utils/CreateTables.js'; +import IsPlainObject from '../../../utils/object/IsPlainObject.js'; +import Methods from './Methods.js'; + +class SingleRoom { + constructor(config) { + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + + this.database = firebase.database() + this.rootPath = GetValue(config, 'root', ''); + + // User properties + this.userInfo = { userID: '', userName: '' }; + this.setUser(GetValue(config, 'userID', ''), GetValue(config, 'userName', '')); + // Room properties + this.leftRoomFlag = false; + // User list + this.userList = CreateUserList.call(this, config); + // Broadcast + this.broadcast = CreateBroadcast.call(this, config); + // Item tables + this.tables = CreateTables.call(this, config); + } + + shutdown() { + } + + destroy() { + this.shutdown(); + } + + get userID() { + return this.userInfo.userID; + } + + set userID(value) { + this.userInfo.userID = value; + } + + get userName() { + return this.userInfo.userName; + } + + set userName(value) { + this.userInfo.userName = value; + } + + setUser(userID, userName) { + if (IsPlainObject(userID)) { + this.userInfo = userID; + } else { + this.userID = userID; + this.userName = userName; + } + return this; + } + + isInRoom() { + return this.userList.isInList; + } + + isFull() { + return this.userList.isFull(); + } + + isFirstUser(userID) { + return this.userList.isFirstUser(userID); + } + + getUsers() { + return this.userList.getUsers(); + } + + get maxUsers() { + return this.userList.maxUsers; + } + + getTable(key) { + return this.tables[key]; + } +} + +Object.assign( + SingleRoom.prototype, + EventEmitterMethods, + Methods +); +export default SingleRoom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/schema.md b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/schema.md new file mode 100644 index 000000000..0b8cfc432 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/schema.md @@ -0,0 +1,21 @@ +``` +# Body of room data. Each room has unique roomID +rooms/ + + # users in this room. + users/ + + userID - Unique ID of user + userName - The name of the user + + broadcast/ + - `message` - Message + - `senderID` - Unique ID of sender + - `senderName` - Name of sender + - `stamp` - Toggle between true and false + + table/ + - + - + - : value +``` \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/utils/CreateBroadcast.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/utils/CreateBroadcast.js new file mode 100644 index 000000000..1894bbe45 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/utils/CreateBroadcast.js @@ -0,0 +1,36 @@ +import Broadcast from '../../broadcast/Broadcast.js'; +import GetValue from '../../../../utils/object/GetValue.js'; + +var CreateBroadcast = function (config) { + var broadcastConfig = GetValue(config, 'broadcast', true); + if (!broadcastConfig) { + return null; + } + + var broadcast = new Broadcast({ + eventEmitter: this.getEventEmitter(), + eventNames: { + receive: 'broadcast.receive' + }, + + root: this.rootPath, + receiverID: 'broadcast', + senderID: this.userInfo, + history: GetValue(broadcastConfig, 'history', false) + }); + + this + .on('room.join', function () { + broadcast.startReceiving() + }) + .on('room.leave', function () { + broadcast.stopReceiving() + }) + .on('userlist.changename', function (userID, userName) { + broadcast.changeUserName(userID, userName); + }, this) + + return broadcast; +} + +export default CreateBroadcast; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/utils/CreateTables.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/utils/CreateTables.js new file mode 100644 index 000000000..150a412ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/utils/CreateTables.js @@ -0,0 +1,55 @@ +import ItemTable from '../../itemtable/ItemTable.js'; +import GetValue from '../../../../utils/object/GetValue.js'; + +var CreateTables = function (config) { + var tablesConfig = GetValue(config, 'tables', undefined); + if (tablesConfig === undefined) { + return {}; + } + + var tableConfig; + var tables = {}; + for (var i = 0, cnt = tablesConfig.length; i < cnt; i++) { + tableConfig = tablesConfig[i]; + tables[tableConfig.key] = CreateTable.call(this, tableConfig); + } + + return tables; +} +var CreateTable = function (config) { + var key = config.key; + var table = new ItemTable({ + eventEmitter: this.getEventEmitter(), + root: this.getItemTablePath(key), + + type: GetValue(config, 'type', 1), + eventNames: { + init: `tables.${key}.init`, + update: `tables.${key}.update`, + addkey0: `tables.${key}.addkey0`, + removekey0: `tables.${key}.removekey0`, + changekey0: `tables.${key}.changekey0`, + addkey1: `tables.${key}.addkey1`, + removekey1: `tables.${key}.removekey1`, + changekey1: `tables.${key}.changekey1`, + addkey2: `tables.${key}.addkey2`, + removekey2: `tables.${key}.removekey2`, + changekey2: `tables.${key}.changekey2` + } + }); + + this + .on('room.join', function () { + table + .startUpdate() + }) + .on('room.leave', function () { + table + .clear() + .stopUpdate() + }) + + return table; +} + +export default CreateTables; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/utils/CreateUserList.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/utils/CreateUserList.js new file mode 100644 index 000000000..d4d094a00 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/singleroom/utils/CreateUserList.js @@ -0,0 +1,52 @@ +import OnlineUserList from '../../onlineuserlist/OnlineUserList.js'; +import GetValue from '../../../../utils/object/GetValue.js'; + +var CreateUserList = function (config) { + var userList = new OnlineUserList({ + eventEmitter: this.getEventEmitter(), + eventNames: { + join: 'userlist.join', // Any user join + leave: 'userlist.leave', // Any user leave + update: 'userlist.update', // Update user list + change: 'userlist.change', // Any user(name) change + init: 'userlist.init', + changename: 'userlist.changename' + }, + + root: this.getUserListPath(), + userID: this.userInfo, + maxUsers: GetValue(config, 'maxUsers', 0) + }); + + userList + .on('userlist.leave', function (user) { + if (user.userID === this.userID) { + OnLeftRoom.call(this); // Current user is left or kicked + } + }, this) + + this + .on('room.join', function () { + userList + .startUpdate() + }) + .on('room.leave', function () { + userList + .stopUpdate() + .clear() + }) + + return userList; +} + +var OnLeftRoom = function () { + this.emit('room.leave'); + + // Clear room info later + var self = this; + setTimeout(function () { + self.leftRoomFlag = false; + }, 0); +} + +export default CreateUserList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/ItemList.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/ItemList.js new file mode 100644 index 000000000..eace8ed8a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/ItemList.js @@ -0,0 +1,121 @@ +import EventEmitterMethods from '../../../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../../../utils/object/GetValue.js'; +import ItemMethods from './ItemMethods.js'; +import UpdateOnce from './updaters/UpdateOnce.js'; +import UpdateChild from './updaters/UpdateChild.js'; +import UpdateAll from './updaters/UpdateAll.js'; + +class ItemList { + constructor(config) { + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + this.eventNameMap = GetValue(config, 'eventNames', DefaultEventNames); + + this.isUpdating = false; + this.items = []; + this.itemID2Index = {}; + this.setItemIDKey(GetValue(config, 'itemIDKey', '__itemID__')); + this.setMode(GetValue(config, 'mode', 1)); + this.setGetitemCallback(GetValue(config, 'getItemCallback', DefaultGetItemCallback), GetValue(config, 'getItemCallbackScope', this)); + this.setQuery(GetValue(config, 'query', undefined)); + } + + shutdown() { + this + .stopUpdate() + .clear(); + } + + destroy() { + this.shutdown(); + } + + setItemIDKey(key) { + this.keyItemID = key; + return this; + } + + setMode(mode) { + if (typeof (mode) === 'string') { + mode = MODE[mode]; + } + + this.mode = mode; + this.updater = Updaters[mode]; + return this; + } + + setGetitemCallback(callback, scope) { + this.getItemCallback = callback; + this.getItemCallbackScope = scope; + return this; + } + + setQuery(query) { + this.query = query; + return this; + } + + startUpdate(query) { + if (query) { + this.setQuery(query); + } else if (this.query) { + query = this.query; + } else { // !query && !this.query + return this; + } + + this + .stopUpdate() + .clear(); + + this.isUpdating = true; + this.updater.start.call(this, query); + return this; + } + + stopUpdate() { + if ((!this.query) || (!this.isUpdating)) { + return this; + } + + this.isUpdating = false; + this.updater.stop.call(this); + return this; + } +} + +var DefaultGetItemCallback = function (snapshot) { + var item = snapshot.val(); + item[this.keyItemID] = snapshot.key; + return item; +} + +Object.assign( + ItemList.prototype, + EventEmitterMethods, + ItemMethods +); + +const DefaultEventNames = { + update: 'update', + add: 'add', + remove: 'remove', + change: 'change' +} + +const Updaters = { + 0: UpdateOnce, + 1: UpdateChild, + 2: UpdateAll +}; + +const MODE = { + once: 0, + child: 1, + all: 2 +} + +export default ItemList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/ItemMethods.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/ItemMethods.js new file mode 100644 index 000000000..3fc5f5727 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/ItemMethods.js @@ -0,0 +1,52 @@ +import Clear from '../../../../utils/object/Clear.js'; + +var Methods = { + clear() { + this.items.length = 0; + Clear(this.itemID2Index); + return this; + }, + + getItems() { + return this.items; + }, + + hasItem(itemID) { + return this.itemID2Index.hasOwnProperty(itemID); + }, + + getItemIndexFromItemID(itemID) { + if (itemID == null) { + return null; + } + return this.itemID2Index[itemID]; + }, + + getItemFromItemID(itemID) { + if (itemID == null) { + return null; + } + var index = this.getItemIndexFromItemID(itemID); + if (index == null) { + return null; + } + + return this.items[index]; + }, + + forEach(callback, scope) { + this.items.forEach(callback, scope); + return this; + }, + + updateItemID2Index() { + Clear(this.itemID2Index); + var itemID; + for (var i = 0, cnt = this.items.length; i < cnt; i++) { + itemID = this.items[i][this.keyItemID]; + this.itemID2Index[itemID] = i; + } + return this; + } +} +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/Callbacks.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/Callbacks.js new file mode 100644 index 000000000..f3da3432e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/Callbacks.js @@ -0,0 +1,78 @@ +import SpliceOne from '../../../../../utils/array/SpliceOne.js'; + +var AddChildCallback = function (snapshot, prevName) { + var item = AddItem.call(this, snapshot, prevName); + this.updateItemID2Index(); + + this.emit(this.eventNameMap.add, item); + this.emit(this.eventNameMap.update, this.items); +} + +var ChangeChildCallback = function (snapshot, prevName) { + var prevItem = RemoveItem.call(this, snapshot); + this.updateItemID2Index(); + var newItem = AddItem.call(this, snapshot, prevName); + this.updateItemID2Index(); + + this.emit(this.eventNameMap.change, newItem, prevItem); + this.emit(this.eventNameMap.update, this.items); +} + +var RemoveChildCallback = function (snapshot) { + var item = RemoveItem.call(this, snapshot); + this.updateItemID2Index(); + + this.emit(this.eventNameMap.remove, item); + this.emit(this.eventNameMap.update, this.items); +} + +var GetAllChildrenCallback = function (snapshot) { + this.clear(); + snapshot.forEach((function (childSnapshot) { + AddItem.call(this, childSnapshot, null, true); + }).bind(this)); + this.updateItemID2Index(); + + this.emit(this.eventNameMap.update, this.items); +} + +var AddItem = function(snapshot, prevName, pushMode) { + var item; + var callback = this.getItemCallback; + var scope = this.getItemCallbackScope; + if (scope) { + item = callback.call(scope, snapshot); + } else { + item = callback(snapshot); + } + + if (pushMode) { + this.items.push(item); + return item; + } + + if (prevName == null) { + this.items.unshift(item); + } else { + var i = this.itemID2Index[prevName]; + if (i === this.items.length - 1) { + this.items.push(item); + } else { + this.items.splice(i + 1, 0, item); + } + } + return item; +} + +var RemoveItem = function (snapshot) { + var index = this.itemID2Index[snapshot.key]; + var item = SpliceOne(this.items, index); + return item +} + +export { + AddChildCallback, + ChangeChildCallback, + RemoveChildCallback, + GetAllChildrenCallback +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/UpdateAll.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/UpdateAll.js new file mode 100644 index 000000000..99f181f81 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/UpdateAll.js @@ -0,0 +1,14 @@ +import { GetAllChildrenCallback } from './Callbacks.js'; + +var Updater = { + start(query) { + query.on('value', GetAllChildrenCallback, this); + return this; + }, + stop() { + this.query.off('value', GetAllChildrenCallback, this); + return this; + } +} + +export default Updater; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/UpdateChild.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/UpdateChild.js new file mode 100644 index 000000000..a32b765d9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/UpdateChild.js @@ -0,0 +1,19 @@ +import { AddChildCallback, RemoveChildCallback, ChangeChildCallback } from './Callbacks.js'; + +var Updater = { + start(query) { + query.on('child_added', AddChildCallback, this); + query.on('child_removed', RemoveChildCallback, this); + query.on('child_moved', ChangeChildCallback, this); + query.on('child_changed', ChangeChildCallback, this); + return this; + }, + stop() { + this.query.off('child_added', AddChildCallback, this); + this.query.off('child_removed', RemoveChildCallback, this); + this.query.off('child_moved', ChangeChildCallback, this); + this.query.off('child_changed', ChangeChildCallback, this); + return this; + }, +} +export default Updater; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/UpdateOnce.js b/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/UpdateOnce.js new file mode 100644 index 000000000..3d9c39754 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/database/utils/itemlist/updaters/UpdateOnce.js @@ -0,0 +1,15 @@ +import { GetAllChildrenCallback } from './Callbacks.js'; + +var Updater = { + start(query) { + this.isUpdating = false; + query.once('value', GetAllChildrenCallback, this); + return this; + }, + stop() { + // Do nothing + return this; + } +} + +export default Updater; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Clear.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Clear.js new file mode 100644 index 000000000..bcda7308e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Clear.js @@ -0,0 +1,32 @@ +var Clear = function () { + var userID = this.userID; + var self = this; + return this.getFileQuery(userID, undefined, 'header') + .get() + .then(function (querySnapshot) { + var batch = self.database.batch(); + var header; + querySnapshot.forEach(function (doc) { + header = DocToHeader(doc); + batch.delete(self.rootRef.doc(header.headerDocID)); + if (header.contentDocID) { + batch.delete(self.rootRef.doc(header.contentDocID)); + } + }); + return batch.commit(); + }) + .then(function () { + self.clearCache(); + return Promise.resolve({ + userID: userID + }); + }) + .catch(function (error) { + return Promise.reject({ + error: error, + userID: userID + }); + }); +} + +export default Clear; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Delete.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Delete.js new file mode 100644 index 000000000..cf6542be4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Delete.js @@ -0,0 +1,39 @@ +var Delete = function (fileID) { + var userID = this.userID; + var self = this; + return LoadHeader.call(this, fileID) // Try load header + .then(function (prevHeader) { + if (!prevHeader) { // File dose not exist + return Promise.resolve({ + userID: userID, + fileID: fileID + }); + } + + var batch = self.database.batch(); + batch.delete(self.rootRef.doc(prevHeader.headerDocID)); + if (prevHeader.contentDocID) { + batch.delete(self.rootRef.doc(prevHeader.contentDocID)); + } + return batch.commit(); + }) + .then(function () { + if (self.cacheHeaders.hasOwnProperty(fileID)) { + delete self.cacheHeaders[fileID]; + } + + return Promise.resolve({ + userID: userID, + fileID: fileID + }); + }) + .catch(function (error) { + return Promise.reject({ + error: error, + userID: userID, + fileID: fileID + }); + }); +} + +export default Delete; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/DocToHeader.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/DocToHeader.js new file mode 100644 index 000000000..6a2fe5737 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/DocToHeader.js @@ -0,0 +1,7 @@ +var DocToHeader = function (doc) { + var header = doc.data(); + header.headerDocID = doc.id; + return header; +} + +export default DocToHeader; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Factory.d.ts new file mode 100644 index 000000000..2d7961a3f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Factory.d.ts @@ -0,0 +1,5 @@ +import Files from './Files'; + +export default function ( + config: Files.IConfig +): Files; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Factory.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Factory.js new file mode 100644 index 000000000..d1b108a20 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Factory.js @@ -0,0 +1,11 @@ +import Files from './Files.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('files', function (config) { + return new Files(config); +}); + +SetValue(window, 'RexPlugins.Fire.Files', Files); + +export default Files; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Files.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Files.d.ts new file mode 100644 index 000000000..381759277 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Files.d.ts @@ -0,0 +1,62 @@ +export default Files; + +declare namespace Files { + interface IConfig { + root?: string + } + + interface IBaseData { + userID?: string; + fileID?: string; + type?: 'header' | 'content'; + + // Other properties + [name: string]: unknown; + } + + interface IHeader extends IBaseData { } + + interface IContent extends IBaseData { } +} + +declare class Files { + constructor( + config: Files.IConfig + ); + + setOwner(userID: string): this; + + setOwner( + config: { userID: string } + ): this; + + userID: string; + readonly userInfo: { userID?: string, userName?: string }; + + save( + fileID: string, + header?: Files.IHeader, + content?: Files.IContent, + updateMode?: boolean + ): Promise< + { userID: string, fileID: string } + >; + + loadHeaders( + ): Promise< + { + userID: string, + headers: { [fileID: string]: Files.IHeader } + } + >; + + load( + fileID: string + ): Promise< + { + userID: string, fileID: string, + header: Files.IHeader, + content: Files.IContent + } + >; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Files.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Files.js new file mode 100644 index 000000000..a2eea1d0f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Files.js @@ -0,0 +1,85 @@ +import GetValue from '../../../utils/object/GetValue.js'; +import IsPlainObject from '../../../utils/object/IsPlainObject.js'; +import Save from './Save.js'; +import Load from './Load.js'; +import LoadHeaders from './LoadHeaders.js'; +import Delete from './Delete.js'; +import Clear from './Clear.js'; +import ClearDict from '../../../utils/object/Clear.js'; + +class Files { + constructor(config) { + this.database = firebase.firestore(); + this.setRootPath(GetValue(config, 'root', '')); + + this.cacheHeaders = {}; + + // Owner + this.userInfo = { userID: '' }; + this.setOwner(GetValue(config, 'userID', '')); + + } + + shutdown() { + } + + destroy() { + this.shutdown(); + } + + get userID() { + return this.userInfo.userID; + } + + set userID(value) { + this.userInfo.userID = value; + } + + setRootPath(rootPath) { + this.rootPath = rootPath; + this.rootRef = this.database.collection(rootPath); + return this; + } + + setOwner(userID) { + var prevUserID = this.userID; + if (IsPlainObject(userID)) { + this.userInfo = userID; + } else { + this.userID = userID; + } + if (prevUserID !== this.userID) { + this.clearCache(); + } + return this; + } + + clearCache() { + ClearDict(this.cacheHeaders); + return this; + } + + getFileQuery(userID, fileID, type) { + var query = this.rootRef; + query = (userID) ? query.where('userID', '==', userID) : query; + query = (fileID) ? query.where('fileID', '==', fileID) : query; + query = (type) ? query.where('type', '==', type) : query; + return query; + } + +} + +var methods = { + save: Save, + load: Load, + loadHeaders: LoadHeaders, + delete: Delete, + clear: Clear, +} + +Object.assign( + Files.prototype, + methods +); + +export default Files; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Load.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Load.js new file mode 100644 index 000000000..9a686a516 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Load.js @@ -0,0 +1,36 @@ +import DocToHeader from './DocToHeader.js'; + +var Load = function (fileID) { + var userID = this.userID; + + var self = this; + return this.getFileQuery(userID, fileID).get() + .then(function (querySnapshot) { + var header, content; + querySnapshot.forEach(function (doc) { + switch (docData.type) { + case 'header': + header = DocToHeader(doc); + break; + case 'content': + content = doc.data(); + break; + } + }); + return Promise.resolve({ + userID: userID, + fileID: fileID, + header: header, + content: content + }); + }) + .catch(function () { + return Promise.reject({ + error: error, + userID: userID, + fileID: fileID + }); + }); +} + +export default Load; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/LoadHeader.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/LoadHeader.js new file mode 100644 index 000000000..2a512148c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/LoadHeader.js @@ -0,0 +1,25 @@ +import DocToHeader from './DocToHeader.js'; + +// Internal used +var LoadHeader = function (fileID) { + var userID = this.userID; + let header = this.cacheHeaders[fileID]; + if (header && (header.userID === userID)) { + return Promise.resolve(header); + } + + // Can't find in cache headers, load from firestore + var self = this; + return this.getFileQuery(userID, fileID, 'header').limit(1).get() + .then(function (querySnapshot) { + let header = undefined; + if (querySnapshot.size > 0) { + var doc = querySnapshot.docs[0]; + header = DocToHeader(doc); + self.cacheHeaders[fileID] = header; // Cache it + } + return Promise.resolve(header); + }); +} + +export default LoadHeader; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/LoadHeaders.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/LoadHeaders.js new file mode 100644 index 000000000..26602d375 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/LoadHeaders.js @@ -0,0 +1,28 @@ +import DocToHeader from './DocToHeader.js'; +import ClearDict from '../../../utils/object/Clear.js'; + +var LoadHeaders = function () { + var userID = this.userID; + var self = this; + return this.getFileQuery(userID, undefined, 'header').get() + .then(function (querySnapshot) { + var header; + ClearDict(self.cacheHeaders); + querySnapshot.forEach(function (doc) { + header = DocToHeader(doc); + self.cacheHeaders[header.fileID] = header; + }); + return Promise.resolve({ + userID: userID, + headers: self.cacheHeaders + }); + }) + .catch(function () { + return Promise.reject({ + error: error, + userID: userID + }); + }); +} + +export default LoadHeaders; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Save.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Save.js new file mode 100644 index 000000000..4d2b26c20 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/Save.js @@ -0,0 +1,79 @@ +import LoadHeader from './LoadHeader.js'; + +var Save = function (fileID, header, content, updateMode) { + if (typeof (content) === 'boolean') { + updateMode = content; + content = undefined; + } + if (updateMode === undefined) { + updateMode = false; + } + + var userID = this.userID; + if (header === undefined) { + header = {}; + } + header.userID = userID; + header.fileID = fileID; + header.type = 'header'; + + if (content) { + content.userID = userID; + content.fileID = fileID; + content.type = 'content'; + } + var writeCommand = (updateMode) ? 'update' : 'set'; + + var self = this; + return LoadHeader.call(this, fileID) // Try load header + .then(function (prevHeader) { + var headerDocRef, contentDocRef; + if (prevHeader) { // Overwrite file + headerDocRef = self.rootRef.doc(prevHeader.headerDocID); + if (content) { + if (prevHeader.contentDocID) { + contentDocRef = self.rootRef.doc(prevHeader.contentDocID); + } else { + contentDocRef = self.rootRef.doc(); + } + } + } else { // Add new file + headerDocRef = self.rootRef.doc(); + if (content) { + contentDocRef = self.rootRef.doc(); + } + } + + // Don't save headerDocID to server + if (header.hasOwnProperty('headerDocID')) { + delete header.headerDocID; + } + // Save contentDocID + if (contentDocRef) { + header.contentDocID = contentDocRef.id; + } + + + var batch = self.database.batch(); + batch[writeCommand](headerDocRef, header); + if (content) { + batch[writeCommand](contentDocRef, content); + } + return batch.commit(); + }) + .then(function () { + return Promise.resolve({ + userID: userID, + fileID: fileID + }); + }) + .catch(function (error) { + return Promise.reject({ + error: error, + userID: userID, + fileID: fileID + }); + }); +} + +export default Save; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/schema.md b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/schema.md new file mode 100644 index 000000000..93c9f354d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/files/schema.md @@ -0,0 +1,18 @@ +## Header (Required) + +- + - `userID` - Unique ID of owner + - `fileID` - Unique file ID of each owner + - `type` - 'header' + - `contentDocID` - Document ID of content + - ... + +## Content + +Optional + +- + - `userID` - Unique ID of owner + - `fileID` - Unique file ID of each owner + - `type` - 'content' + - ... \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Add.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Add.js new file mode 100644 index 000000000..670c7df26 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Add.js @@ -0,0 +1,19 @@ +import AddAliasTransaction from './AddAliasTransaction.js'; + +var Add = function (id, alias) { + var self = this; + return this.getAlias(id) + .then(function (result) { + if (result.alias) { + if (result.alias === alias) { + return Promise.resolve({ id: id, alias: alias }); + } else { + return Promise.reject({ id: id, alias: alias }); + } + } else { + return AddAliasTransaction.call(self, id, alias); + } + }); +} + +export default Add; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/AddAliasTransaction.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/AddAliasTransaction.js new file mode 100644 index 000000000..7b57d5fd0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/AddAliasTransaction.js @@ -0,0 +1,15 @@ +var Add = function (id, alias) { + var self = this; + return this.database.runTransaction(function (transaction) { + var aliasRef = self.getAliasRef(alias); + return transaction.get(aliasRef).then(function (doc) { + if (!doc.exists) { + transaction.set(aliasRef, { id: id }); + return Promise.resolve({ id: id, alias: alias }); + } else { + return Promise.reject({ id: id, alias: alias }); + } + }); + }); +} +export default Add; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/AddRandom.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/AddRandom.js new file mode 100644 index 000000000..bc1b73aee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/AddRandom.js @@ -0,0 +1,26 @@ +import GetValue from '../../../utils/object/GetValue.js'; +import GetRandomWord from '../../../utils/string/GetRandomWord.js'; +import RetryAddRandomAliasTransaction from './RetryAddRandomAliasTransaction.js'; + +var AddRandom = function (id, config) { + var digits = GetValue(config, 'digits', 10); + var candidates = GetValue(config, 'candidates', '0123456789'); + var retry = GetValue(config, 'retry', 1000); + + var self = this; + return this.getAlias(id) + .then(function (result) { + if (result.alias) { + var alias = GetRandomWord(digits, digits, candidates); + if (result.alias === alias) { + return Promise.resolve({ id: id, alias: alias }); + } else { + return Promise.reject({ id: id, alias: alias }); + } + } else { + return RetryAddRandomAliasTransaction.call(self, id, digits, candidates, retry); + } + }); +} + +export default AddRandom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Factory.d.ts new file mode 100644 index 000000000..79b5553a3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Factory.d.ts @@ -0,0 +1,5 @@ +import IdAlias from './IdAlias'; + +export default function ( + config: IdAlias.IConfig +): IdAlias; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Factory.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Factory.js new file mode 100644 index 000000000..cac7c575c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Factory.js @@ -0,0 +1,11 @@ +import IdAlias from './IdAlias.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../utils//object/SetValue.js'; + +ObjectFactory.register('idAlias', function (config) { + return new IdAlias(config); +}); + +SetValue(window, 'RexPlugins.Fire.IdAlias', IdAlias); + +export default IdAlias; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/GetAlias.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/GetAlias.js new file mode 100644 index 000000000..6283f21fb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/GetAlias.js @@ -0,0 +1,16 @@ +var GetAlias = function (id) { + return this.rootRef.where('id', '==', id).limit(1).get() + .then(function (querySnapshot) { + var alias; + if (querySnapshot.size > 0) { + var doc = querySnapshot.docs[0]; + alias = doc.id; + } + return Promise.resolve({ + id: id, + alias: alias + }); + }); +} + +export default GetAlias; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/GetId.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/GetId.js new file mode 100644 index 000000000..88df087d8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/GetId.js @@ -0,0 +1,15 @@ +var GetId = function (alias) { + return this.getAliasRef(alias).get() + .then(function (doc) { + var id; + if (doc.exists) { + id = doc.data().id; + } + return Promise.resolve({ + id: id, + alias: alias + }); + }); +} + +export default GetId; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/GetRandomAlias.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/GetRandomAlias.js new file mode 100644 index 000000000..037d134ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/GetRandomAlias.js @@ -0,0 +1,20 @@ +import GetValue from '../../../utils//object/GetValue.js'; +import RetryAddRandomAliasTransaction from './RetryAddRandomAliasTransaction.js'; + +var GetRandomAlias = function (id, config) { + var digits = GetValue(config, 'digits', 10); + var candidates = GetValue(config, 'candidates', '0123456789'); + var retry = GetValue(config, 'retry', 1000); + + var self = this; + return this.getAlias(id) + .then(function (result) { + if (result.alias) { + return Promise.resolve(result); + } else { + return RetryAddRandomAliasTransaction.call(self, id, digits, candidates, retry); + } + }) +}; + +export default GetRandomAlias; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/IdAlias.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/IdAlias.d.ts new file mode 100644 index 000000000..cb65f3936 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/IdAlias.d.ts @@ -0,0 +1,42 @@ +export default IdAlias; + +declare namespace IdAlias { + interface IConfig { + root?: string + } + + interface IGetRandomAliasConfig { + digits?: number, + candidates?: string, + retry?: number + } + + type ResultType = { + id: string | undefined, + alias: string | undefined + } +} + +declare class IdAlias { + constructor( + config: IdAlias.IConfig + ); + + getRandomAlias( + id: string, + config?: IdAlias.IGetRandomAliasConfig + ): Promise; + + add( + id: string, alias: string + ): Promise; + + getId( + alias: string + ): Promise; + + getAlias( + id: string + ): Promise; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/IdAlias.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/IdAlias.js new file mode 100644 index 000000000..8ad278234 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/IdAlias.js @@ -0,0 +1,47 @@ +import GetValue from '../../../utils/object/GetValue.js'; +import Add from './Add.js'; +import AddRandom from './AddRandom.js'; +import GetId from './GetId.js'; +import GetAlias from './GetAlias.js'; +import GetRandomAlias from './GetRandomAlias.js'; +import Remove from './Remove.js'; + +class IdAlias { + constructor(config) { + this.database = firebase.firestore(); + this.setRootPath(GetValue(config, 'root', '')); + } + + shutdown() { + } + + destroy() { + this.shutdown(); + } + + setRootPath(rootPath) { + this.rootPath = rootPath; + this.rootRef = this.database.collection(rootPath); + return this; + } + + getAliasRef(alias) { + return this.rootRef.doc(alias); + } +} + +var methods = { + add: Add, + addRandom: AddRandom, + getId: GetId, + getAlias: GetAlias, + getRandomAlias: GetRandomAlias, + remove: Remove +} + +Object.assign( + IdAlias.prototype, + methods +); + +export default IdAlias; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Remove.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Remove.js new file mode 100644 index 000000000..669f5343f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/Remove.js @@ -0,0 +1,9 @@ +var Remove = function (id) { + var self = this; + return this.getAlias(id) + .then(function (alias) { + return self.getAliasRef(alias).delete(); + }) +} + +export default Remove; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/RetryAddRandomAliasTransaction.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/RetryAddRandomAliasTransaction.js new file mode 100644 index 000000000..8f32eb8c2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/RetryAddRandomAliasTransaction.js @@ -0,0 +1,19 @@ +import GetRandomWord from '../../../utils/string/GetRandomWord.js'; +import AddAliasTransaction from './AddAliasTransaction.js'; + +var TryAdd = function (id, digits, candidates, retry) { + var alias = GetRandomWord(digits, digits, candidates); + if (retry <= 0) { + return Promise.reject({ id: id, alias: alias }); + } + retry--; + var self = this; + return AddAliasTransaction.call(self, id, alias) + .catch(function () { + setTimeout(function () { + return TryAdd.call(self, id, digits, candidates, retry); + }, 0); + }); +} + +export default TryAdd; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/schema.md b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/schema.md new file mode 100644 index 000000000..addb1cc3d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/idalias/schema.md @@ -0,0 +1,2 @@ +- + - `id` - ID \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Const.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Const.js new file mode 100644 index 000000000..e0d91928b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Const.js @@ -0,0 +1,29 @@ +var TimeTagKeys = { + d: 'tagD', + w: 'tagW', + m: 'tagM', + y: 'tagY', + a: 'tagA' +} + +var ScoreKeys = { + d: 'scoreD', + w: 'scoreW', + m: 'scoreM', + y: 'scoreY', + a: 'scoreA' +} + +var FullTimeName = { + d: 'day', + w: 'week', + m: 'month', + y: 'year', + a: 'all' +} + +export { + TimeTagKeys, + ScoreKeys, + FullTimeName +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/DeleteMethods.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/DeleteMethods.js new file mode 100644 index 000000000..61947522f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/DeleteMethods.js @@ -0,0 +1,25 @@ +import Delete from '../utils/query/Delete.js'; + +var Methods = { + deleteUser(userID) { + if (userID === undefined) { + userID = this.userID; + } + + var query = this.getRecordQuery(undefined, undefined, userID, undefined); + return Delete(query); + }, + + deleteBoard(boardID, tag) { + if (boardID === undefined) { + boardID = this.boardID; + } + if (tag === undefined) { + tag = this.tag; + } + + var query = this.getRecordQuery(boardID, tag, undefined, undefined); + return Delete(query); + } +} +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Factory.d.ts new file mode 100644 index 000000000..4c14fa0c2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Factory.d.ts @@ -0,0 +1,5 @@ +import LeaderBoard from './LeaderBoard'; + +export default function ( + config: LeaderBoard.IConfig +): LeaderBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Factory.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Factory.js new file mode 100644 index 000000000..b84a49f20 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Factory.js @@ -0,0 +1,11 @@ +import LeaderBoard from './LeaderBoard.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('leaderBoard', function (config) { + return new LeaderBoard(config); +}); + +SetValue(window, 'RexPlugins.Fire.LeaderBoard', LeaderBoard); + +export default LeaderBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetQueryMethods.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetQueryMethods.js new file mode 100644 index 000000000..3bd7de04e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetQueryMethods.js @@ -0,0 +1,45 @@ +import { TimeTagKeys, ScoreKeys } from './Const.js'; +import GetTime from './GetTime.js'; + +var Methods = { + getRecordQuery(boardID, customTag, userID, timeTagKey) { + var query = this.rootRef; + query = (boardID !== undefined) ? query.where('boardID', '==', boardID) : query; + query = (customTag !== undefined) ? query.where('tag', '==', customTag) : query; + query = (userID !== undefined) ? query.where('userID', '==', userID) : query; + + if (timeTagKey !== undefined) { + query = query.where(timeTagKey[0], '==', timeTagKey[1]); + } + return query; + }, + + getMyRecordQuery(userID) { + if (userID === undefined) { + userID = this.userID; + } + return this.getRecordQuery(this.boardID, this.tag, userID, undefined).limit(1); + }, + + getPageQuery() { + var timeTagKey, scoreKey; + if (this.timeFilters !== false) { + var t = this.timeFilterType[0]; + timeTagKey = [TimeTagKeys[t], GetTime()[t]]; + scoreKey = ScoreKeys[t]; + } else { // No time filters + timeTagKey = undefined; + scoreKey = 'score'; + } + + var baseQuery = this.getRecordQuery(this.boardID, this.tag, undefined, timeTagKey); + var nextPageQuery = baseQuery.orderBy(scoreKey, 'desc'); + var prevPageQuery = baseQuery.orderBy(scoreKey); + return { + next: nextPageQuery, + previous: prevPageQuery + } + } +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetRank.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetRank.js new file mode 100644 index 000000000..d1da423ab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetRank.js @@ -0,0 +1,20 @@ +import FindFirst from '../utils/query/FindFirst.js'; + +var GetRank = function (userID) { + if (userID === undefined) { + userID = this.userID; + } + + var query = this.getPageQuery().next; + var testCallback = function (doc) { + var item = doc.data(); + return (item.userID === userID); + } + return FindFirst(query, testCallback) + .then(function (result) { + return Promise.resolve({ userID: userID, rank: result.index }); + }) +}; + + +export default GetRank; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetScore.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetScore.js new file mode 100644 index 000000000..3b633f5e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetScore.js @@ -0,0 +1,13 @@ +var GetScore = function (userID) { + return this.getMyRecordQuery(userID).get() + .then(function (querySnapshot) { + var item; + if (querySnapshot.size > 0) { + var doc = querySnapshot.docs[0]; + item = doc.data(); + } + return Promise.resolve(item); + }); +} + +export default GetScore; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetTime.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetTime.js new file mode 100644 index 000000000..6554f800c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/GetTime.js @@ -0,0 +1,17 @@ +var GetTime = function (timeStamp) { + var date = (timeStamp) ? (new Date(timeStamp)) : (new Date()); + var y = date.getFullYear(); + var m = date.getMonth() + 1; + var d = date.getDate(); + var Jan1st = new Date(date.getFullYear(), 0, 1); + var w = Math.ceil((((date - Jan1st) / 86400000) + Jan1st.getDay() + 1) / 7); + return { + d: `${y}-${m}-${d}`, // day filter + w: `${y}-${w}`, // week filter + m: `${y}-${m}`, // month filter + y: `${y}`, // year filter + a: '' // all-time filter + }; +} + +export default GetTime; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/LeaderBoard.d.ts b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/LeaderBoard.d.ts new file mode 100644 index 000000000..e7bd1f9fd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/LeaderBoard.d.ts @@ -0,0 +1,98 @@ +export default LeaderBoard; + +declare namespace LeaderBoard { + type TimeFiltersType = { + day?: boolean, week?: boolean, month?: boolean, year?: boolean, all?: boolean, + } + + type TimeFilterType = 'day' | 'd' | 'week' | 'w' | 'month' | 'm' | 'year' | 'y' | 'all' | 'a'; + + interface IConfig { + root?: string, + timeFilters?: boolean | TimeFiltersType, + timeFilterType?: TimeFilterType, + pageItemCount?: number + boardID?: string, + tag?: string, + } + + interface IRecord { + userID: string; userName?: string; + boardID?: string; tag?: string; + + score?: number; + tagD?: string; tagW?: string; tagM?: string; tagY?: string; tagA?: string; + scoreD?: number; scoreW?: number; scoreM?: number; scoreY?: number; scoreA?: number + + // Other properties + [name: string]: any; + } + + type RankResultType = { + userID: string, + rank: number + } +} + +declare class LeaderBoard { + constructor( + config?: LeaderBoard.IConfig + ); + + setUser(userID: string, userName?: string): this; + + setUser( + config: { userID: string, userName?: string } + ): this; + + userID: string; + readonly userInfo: { userID?: string, userName?: string }; + + setBoardID(boardID?: string): this; + readonly boardID: string; + + setTag(tag?: string): this; + readonly tag: string; + + setTimeFilterType(type: LeaderBoard.TimeFilterType): this; + readonly timeFilters: boolean | LeaderBoard.TimeFiltersType; + + post( + score: number, + extraData?: { [name: string]: any }, + timestamp?: number + ): Promise; + + getScore( + userID?: string + ): Promise; + + getRank( + userID?: string + ): Promise; + + loadFirstPage( + ): Promise; + + loadNextPage( + ): Promise; + + loadPreviousPage( + ): Promise; + + loadCurrentPage( + ): Promise; + + readonly pageIndex: number; + readonly isFirstPage: boolean; + readonly isLastPage: boolean; + + deleteUserScore( + userID?: string + ): Promise; + + deleteBoard( + boardID?: string, tag?: string + ): Promise; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/LeaderBoard.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/LeaderBoard.js new file mode 100644 index 000000000..3b79ebbfa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/LeaderBoard.js @@ -0,0 +1,135 @@ +import GetValue from '../../../utils/object/GetValue.js'; +import IsPlainObject from '../../../utils/object/IsPlainObject.js'; +import Post from './Post.js';; +import LoadMethods from './LoadMethods.js'; +import GetScore from './GetScore.js'; +import GetRank from './GetRank.js'; +import DeleteMethods from './DeleteMethods.js'; +import GetQueryMethods from './GetQueryMethods.js'; +import PageLoader from '../pageloader/PageLoader.js'; + +class LeaderBoard { + constructor(config) { + this.database = firebase.firestore(); + this.setRootPath(GetValue(config, 'root', '')); + + this.userInfo = { userID: undefined, userName: undefined }; + this.setUser(GetValue(config, 'userID', ''), GetValue(config, 'userName', undefined)); + this.setBoardID(GetValue(config, 'boardID', undefined)); + this.setTag(GetValue(config, 'tag', undefined)); + this.setTimeFilters(GetValue(config, 'timeFilters', false)); + this.setTimeFilterType(GetValue(config, 'timeFilterType', 'year')); + + this.page = new PageLoader({ + dataMode: 'dynamic', + itemCount: GetValue(config, 'pageItemCount', 100) + }); + this.resetQueryFlag = true; + } + + shutdown() { + } + + destroy() { + this.shutdown(); + } + + get userID() { + return this.userInfo.userID; + } + + set userID(value) { + this.userInfo.userID = value; + } + + get userName() { + return this.userInfo.userName; + } + + set userName(value) { + this.userInfo.userName = value; + } + + setRootPath(rootPath) { + this.resetQueryFlag |= (this.rootPath !== rootPath); + this.rootPath = rootPath; + this.rootRef = this.database.collection(rootPath); + return this; + } + + setUser(userID, userName) { + if (IsPlainObject(userID)) { + this.userInfo = userID; + } else { + this.userID = userID; + this.userName = userName; + } + return this; + } + + setBoardID(boardID) { + this.resetQueryFlag |= (this.boardID !== boardID); + this.boardID = boardID; + return this; + } + + setTag(tag) { + this.resetQueryFlag |= (this.tag !== tag); + this.tag = tag; + return this; + } + + setTimeFilters(filters) { + if (filters === false) { + this.timeFilters = false; + } else { // filters is true, or a plain object + this.timeFilters = { + d: GetValue(filters, 'day', true), + w: GetValue(filters, 'week', true), + m: GetValue(filters, 'month', true), + y: GetValue(filters, 'year', true), + a: GetValue(filters, 'all', true) + } + } + return this; + } + + setTimeFilterType(type) { + this.resetQueryFlag |= (this.timeFilterType !== type); + this.timeFilterType = type; + return this; + } + + setPageItemCount(count) { + this.page.setItemCount(count); + return this; + } + + get pageIndex() { + return this.page.pageIndex; + } + + get isFirstPage() { + return (this.page.pageIndex === 0); + } + + get isLastPage() { + return (this.page.isFullPage === false); + } +} + +var methods = { + post: Post, + getScore: GetScore, + getRank: GetRank +} + +Object.assign( + LeaderBoard.prototype, + methods, + GetQueryMethods, + LoadMethods, + DeleteMethods +); + +export default LeaderBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/LoadMethods.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/LoadMethods.js new file mode 100644 index 000000000..2c54cd5cc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/LoadMethods.js @@ -0,0 +1,85 @@ +import { TimeTagKeys, ScoreKeys } from './Const.js'; + +var Methods = { + loadFirstPage() { + this.resetPageQuery(); + + var self = this; + return this.page.loadFirstPage() + .then(function (docs) { + return Promise.resolve(DocsToDataArray.call(self, docs)); + }) + }, + + loadNextPage() { + this.resetPageQuery(); + + var self = this; + return this.page.loadNextPage() + .then(function (docs) { + return Promise.resolve(DocsToDataArray.call(self, docs)); + }) + }, + + loadPreviousPage() { + this.resetPageQuery(); + + var self = this; + return this.page.loadPreviousPage() + .then(function (docs) { + return Promise.resolve(DocsToDataArray.call(self, docs)); + }) + }, + + loadCurrentPage() { + this.resetPageQuery(); + + var self = this; + return this.page.loadCurrentPage() + .then(function (docs) { + return Promise.resolve(DocsToDataArray.call(self, docs)); + }) + }, + + load(count, skip) { + this.resetPageQuery(); + + var self = this; + return this.page.load(count, skip) + .then(function (docs) { + return Promise.resolve(DocsToDataArray.call(self, docs)); + }) + }, + + resetPageQuery() { + if (!this.resetQueryFlag) { + return this; + } + + this.resetQueryFlag = false; + this.page.setQuery(this.getPageQuery()); + return this; + } +} + +var DocsToDataArray = function (docs) { + var items = [], item; + + var scoreKey = ScoreKeys[this.timeFilterType[0]]; + for (var i = 0, cnt = docs.length; i < cnt; i++) { + item = docs[i].data(); + + if (this.timeFilters !== false) { + item.score = item[scoreKey]; + // Remove timeFilterKeys, and scoreKeys + for (var t in this.timeFilters) { + delete item[TimeTagKeys[t]]; + delete item[ScoreKeys[t]]; + } + } + items.push(item); + } + return items; +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Post.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Post.js new file mode 100644 index 000000000..039871bf4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/Post.js @@ -0,0 +1,68 @@ +import GetTime from './GetTime.js'; +import { TimeTagKeys, ScoreKeys } from './Const.js'; + +var Post = function (score, extraData, timeStamp) { + var newRecord = { + userID: this.userID + }; + if (this.boardID !== undefined) { + newRecord.boardID = this.boardID; + } + if (this.userName) { + newRecord.userName = this.userName; + } + var curTimeData = GetTime(timeStamp); + if (this.timeFilters !== false) { + for (var t in this.timeFilters) { + if (!this.timeFilters[t]) { + continue; + } + newRecord[TimeTagKeys[t]] = curTimeData[t]; + newRecord[ScoreKeys[t]] = score; + } + } else { // No time filters + newRecord.score = score; + } + if (this.tag) { + newRecord.tag = this.tag; + } + if (extraData) { + Object.assign(newRecord, extraData); + } + + var self = this; + return this.getMyRecordQuery().get() + .then(function (querySnapshot) { + var prevRecord, docID; + if (querySnapshot.size > 0) { + var doc = querySnapshot.docs[0]; + prevRecord = doc.data(); + docID = doc.id; + } + + if (prevRecord) { + if (self.timeFilters !== false) { + for (var t in self.timeFilters) { + if (!self.timeFilters[t]) { + continue; + } + + var timeTagKey = TimeTagKeys[t]; + if (prevRecord[timeTagKey] === newRecord[timeTagKey]) { + var scoreKey = ScoreKeys[t]; + newRecord[scoreKey] = Math.max(prevRecord[scoreKey], newRecord[scoreKey]); + } + } + } else { // No time filters + newRecord.score = Math.max(prevRecord.score, newRecord.score); + } + } + if (docID === undefined) { + docID = self.rootRef.doc().id; + } + return self.rootRef.doc(docID) + .set(newRecord); + }); +} + +export default Post; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/schema.md b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/schema.md new file mode 100644 index 000000000..fb7a2b5ac --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/leaderboard/schema.md @@ -0,0 +1,14 @@ +- + - `userID` - Unique ID of owner + - `boardID` - Unique ID of board, optional + - `tag` - Custom filter tag, optional + - `tagD` - Updated day of day score, filter key of day mode + - `tagW` - Updated week of week score, filter key of week mode + - `tagM` - Updated month of month score, filter key of month mode + - `tagY` - Updated year of year score, filter key of year mode + - `scoreD` - Day score, sorting key of day mode + - `scoreW` - Week score, sorting key of week mode + - `scoreM` - Month score, sorting key of month mode + - `scoreY` - Year score, sorting key of year mode + - `userName` - Name of the owner, optional + - ... \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/Factory.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/Factory.js new file mode 100644 index 000000000..950e4b7cf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/Factory.js @@ -0,0 +1,11 @@ +import Messages from './Messages.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('messages', function (config) { + return new Messages(config); +}); + +SetValue(window, 'RexPlugins.Fire.Messages', Messages); + +export default Messages; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/GetQueryMethods.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/GetQueryMethods.js new file mode 100644 index 000000000..f12a400c9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/GetQueryMethods.js @@ -0,0 +1,22 @@ +var Methods = { + getReceiverQuery(receiverID) { + if (receiverID === undefined) { + receiverID = this.receiverID; + } + var query = this.rootRef; + query = (receiverID !== undefined) ? query.where('receiverID', '==', receiverID) : query; + return query; + }, + + getPageQuery(receiverID) { + var baseQuery = this.getReceiverQuery(receiverID); + var nextPageQuery = baseQuery.orderBy('timestamp'); + var prevPageQuery = baseQuery.orderBy('timestamp', 'desc'); + return { + next: nextPageQuery, + previous: prevPageQuery + } + } +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/Messages.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/Messages.js new file mode 100644 index 000000000..d24bde695 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/Messages.js @@ -0,0 +1,102 @@ +import EventEmitterMethods from '../../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import IsPlainObject from '../../../utils/object/IsPlainObject.js'; +import Send from './Send.js'; +import ReceiveMethods from './ReceiveMethods.js'; +import GetQueryMethods from './GetQueryMethods.js'; +import PageLoader from '../pageloader/PageLoader.js'; + +class Messages { + constructor(config) { + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + + this.database = firebase.firestore(); + this.setRootPath(GetValue(config, 'root', '')); + + this.userInfo = { userID: '', userName: undefined }; + this.setSender(GetValue(config, 'senderID', ''), GetValue(config, 'senderName', '')); + this.setReceiver(GetValue(config, 'receiverID', undefined)); + + this.skipFirst = true; + this.unsubscribe = undefined; + this.page = new PageLoader(); + this.setPageItemCount(GetValue(config, 'pageItemCount', 100)); + this.resetQueryFlag = true; + this.cacheMessages = []; + } + + shutdown() { + this + .stopReceiving() + .destroyEventEmitter(); + } + + destroy() { + this.shutdown(); + } + + get userID() { + return this.userInfo.userID; + } + + set userID(value) { + this.userInfo.userID = value; + } + + get userName() { + return this.userInfo.userName; + } + + set userName(value) { + this.userInfo.userName = value; + } + + setRootPath(rootPath) { + this.resetQueryFlag |= (this.rootPath !== rootPath); + this.rootPath = rootPath; + this.rootRef = this.database.collection(rootPath); + return this; + } + + setSender(userID, userName) { + if (IsPlainObject(userID)) { + this.userInfo = userID; + } else { + this.userID = userID; + this.userName = userName; + } + return this; + } + + setReceiver(receiverID) { + this.resetQueryFlag |= (this.receiverID !== receiverID); + this.receiverID = receiverID; + return this; + } + + setPageItemCount(count) { + this.page.setItemCount(count); + return this; + } + + get hasPreviousMessage() { + return (this.page.isFullPage !== false); + } +} + +var methods = { + send: Send +} + +Object.assign( + Messages.prototype, + EventEmitterMethods, + methods, + ReceiveMethods, + GetQueryMethods +); + +export default Messages; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/ReceiveMethods.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/ReceiveMethods.js new file mode 100644 index 000000000..14e136719 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/ReceiveMethods.js @@ -0,0 +1,89 @@ +var Methods = { + startReceiving() { + var query = this.getReceiverQuery(this.receiverID).orderBy('timestamp', 'desc').limit(1); + var self = this; + this.unsubscribe = query.onSnapshot( + { + includeMetadataChanges: true + }, + function (querySnapshot) { + if (querySnapshot.size > 0) { // Load data + var doc = querySnapshot.docs[0]; + if (doc.metadata.hasPendingWrites) { // Load local message + if (self.skipFirst) { // Local doc dose not have timestamp + self.skipFirst = false; + } + return; + } + + self.resetPageQuery(self.receiverID, doc); + + if (self.skipFirst) { // Load previos data + self.skipFirst = false; + } else { + var d = DocToMessage(doc); + self.cacheMessages.push(d); + self.emit('receive', d); + } + } else { + if (self.skipFirst) { // Start from an empty collection + self.skipFirst = false; + } + } + }, + function (error) { + debugger + } + ) + + return this; + }, + + stopReceiving() { + if (this.unsubscribe) { + this.unsubscribe(); + } + + // Reset to initial state + this.resetQueryFlag = true; + this.cacheMessages.length = 0; + return this; + }, + + loadPreviousMessages() { + this.resetPageQuery(this.receiverID); + + var self = this; + return this.page.loadNextPage() + .then(function (docs) { + var messages = []; + for (var i = 0, cnt = docs.length; i < cnt; i++) { + messages.push(DocToMessage(docs[i])); + } + + self.cacheMessages.splice(0, 0, ...messages); + return Promise.resolve(messages); + }) + }, + + resetPageQuery(receiverID, baselineDoc) { + if (!this.resetQueryFlag) { + return this; + } + + this.resetQueryFlag = false; + var baselineMode = (this.skipFirst) ? 'startAt' : 'startAfter'; + this.page + .setBaselineDoc(baselineDoc, baselineMode) + .setQuery(this.getPageQuery(receiverID)); + return this; + } +} + +var DocToMessage = function (doc) { + var message = doc.data(); + message.timestamp = message.timestamp.seconds * 1000; + return message; +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/Send.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/Send.js new file mode 100644 index 000000000..89f2c03bd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/Send.js @@ -0,0 +1,17 @@ +var Send = function (message) { + var d = { + senderID: this.userID, + message: message, + timestamp: firebase.firestore.FieldValue.serverTimestamp() + } + if (this.userName !== undefined) { + d.senderName = this.userName; + } + if (this.receiverID !== undefined) { + d.receiverID = this.receiverID; + } + + return this.rootRef.add(d); +} + +export default Send; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/schema.md b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/schema.md new file mode 100644 index 000000000..967eff85d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/messages/schema.md @@ -0,0 +1,6 @@ +- + - `senderID` - Unique ID of sender + - `senderName` - Name of sender + - `message` - Message + - `timestamp` - Server-timestamp + - `receiverID` - Unique ID of receiver, optional \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/Factory.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/Factory.js new file mode 100644 index 000000000..e85842eec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/Factory.js @@ -0,0 +1,11 @@ +import PageLoader from './PageLoader.js'; +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('pageLoader', function (config) { + return new PageLoader(config); +}); + +SetValue(window, 'RexPlugins.Fire.PageLoader', PageLoader); + +export default PageLoader; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadCurrentPage.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadCurrentPage.js new file mode 100644 index 000000000..b348b1014 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadCurrentPage.js @@ -0,0 +1,40 @@ +import Load from '../utils/query/Load.js'; + +var LoadCurrentPage = function () { + if ((this.pageIndex === undefined) || (this.pageIndex === 0)) { + return this.loadFirstPage(); + } + + var callback = (this.dataMode === 0) ? LoadStaticPage : LoadDynamicPage; + return callback.call(this); +} + +var LoadStaticPage = function () { + var self = this; + return Load(this.nextQuery, this.itemCount, 0, this.prevPageEndDocRef, 'startAfter') + .then(function (docs) { + var docCount = docs.length; + self.cacheItems = docs; + self.endItemIndex = self.startItemIndex + docCount - 1; + self.isFullPage = (docCount === self.itemCount); + // Doc reference for paging + self.currPageStartDocRef = docs[0]; + self.currPageEndDocRef = docs[docCount - 1]; + return Promise.resolve(self.cacheItems); + }) +} + +var LoadDynamicPage = function () { + var skip = this.pageIndex * this.itemCount; + var self = this; + return Load(this.nextQuery, this.itemCount, skip, this.baselineDocRef, this.baselineMode) + .then(function (docs) { + var docCount = docs.length; + self.cacheItems = docs; + self.endItemIndex = self.startItemIndex + docCount - 1; + self.isFullPage = (docCount === self.itemCount); + return Promise.resolve(self.cacheItems); + }) +} + +export default LoadCurrentPage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadFirstPage.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadFirstPage.js new file mode 100644 index 000000000..da308675a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadFirstPage.js @@ -0,0 +1,40 @@ +import Load from '../utils/query/Load.js'; + +var LoadFirstPage = function () { + var callback = (this.dataMode === 0) ? LoadStaticPage : LoadDynamicPage; + return callback.call(this); +} + +var LoadStaticPage = function () { + var self = this; + return Load(this.nextQuery, this.itemCount, 0, this.baselineDocRef, this.baselineMode) + .then(function (docs) { + var docCount = docs.length; + self.cacheItems = docs; + self.pageIndex = 0; + self.startItemIndex = 0; + self.endItemIndex = self.startItemIndex + docCount - 1; + self.isFullPage = (docCount === self.itemCount); + // Doc reference for paging + self.prevPageEndDocRef = undefined; + self.currPageStartDocRef = docs[0]; + self.currPageEndDocRef = docs[docCount - 1]; + return Promise.resolve(self.cacheItems); + }) +} + +var LoadDynamicPage = function () { + var self = this; + return Load(this.nextQuery, this.itemCount, 0, this.baselineDocRef, this.baselineMode) + .then(function (docs) { + var docCount = docs.length; + self.cacheItems = docs; + self.pageIndex = 0; + self.startItemIndex = 0; + self.endItemIndex = self.startItemIndex + docCount - 1; + self.isFullPage = (docCount === self.itemCount); + return Promise.resolve(self.cacheItems); + }) +} + +export default LoadFirstPage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadInRange.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadInRange.js new file mode 100644 index 000000000..f1503afb8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadInRange.js @@ -0,0 +1,21 @@ +import Load from '../utils/query/Load.js'; + +var LoadInRange = function (count, skip) { + if (skip === undefined) { + skip = 0; + } + + var self = this; + return Load(this.nextQuery, count, skip, this.baselineDocRef, this.baselineMode) + .then(function (docs) { + var docCount = docs.length; + self.cacheItems = docs; + self.pageIndex = undefined; // Not in Page mode + self.startItemIndex = skip; + self.endItemIndex = self.startItemIndex + docCount - 1; + self.isFullPage = (count === undefined) ? true : (docCount === count); + return Promise.resolve(self.cacheItems); + }) +} + +export default LoadInRange; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadNextPage.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadNextPage.js new file mode 100644 index 000000000..699e50646 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadNextPage.js @@ -0,0 +1,45 @@ +import Load from '../utils/query/Load.js'; + +var LoadNextPage = function () { + if (this.pageIndex === undefined) { + return this.loadFirstPage(); + } + + var callback = (this.dataMode === 0) ? LoadStaticPage : LoadDynamicPage; + return callback.call(this); +} + +var LoadStaticPage = function () { + var self = this; + return Load(this.nextQuery, this.itemCount, 0, this.currPageEndDocRef, 'startAfter') + .then(function (docs) { + var docCount = docs.length; + self.cacheItems = docs; + self.pageIndex += 1; + self.startItemIndex = self.endItemIndex + 1; + self.endItemIndex = self.startItemIndex + docCount - 1; + self.isFullPage = (docCount === self.itemCount); + // Doc reference for paging + self.prevPageEndDocRef = self.currPageEndDocRef; + self.currPageStartDocRef = docs[0]; + self.currPageEndDocRef = docs[docCount - 1]; + return Promise.resolve(self.cacheItems); + }) +} + +var LoadDynamicPage = function () { + var skip = (this.pageIndex + 1) * this.itemCount; + var self = this; + return Load(this.nextQuery, this.itemCount, skip, this.baselineDocRef, this.baselineMode) + .then(function (docs) { + var docCount = docs.length; + self.cacheItems = docs; + self.pageIndex += 1; + self.startItemIndex = self.endItemIndex + 1; + self.endItemIndex = self.startItemIndex + docCount - 1; + self.isFullPage = (docCount === self.itemCount); + return Promise.resolve(self.cacheItems); + }) +} + +export default LoadNextPage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadPreviousPage.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadPreviousPage.js new file mode 100644 index 000000000..8c4ebc92e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/LoadPreviousPage.js @@ -0,0 +1,49 @@ +import Load from '../utils/query/Load.js'; + +var LoadPreviousPage = function () { + if ((this.pageIndex === undefined) || (this.pageIndex === 1)) { + return this.loadFirstPage(); + } + + var callback = (this.dataMode === 0) ? LoadStaticPage : LoadDynamicPage; + return callback.call(this); +} + +var LoadStaticPage = function () { + var self = this; + return Load(this.prevQuery, (this.itemCount + 1), 0, this.currPageStartDocRef, 'startAfter') + .then(function (docs) { + // Get one more document for previous page end + var docCount = docs.length - 1; + self.cacheItems = docs; + self.cacheItems.pop(); // Pop up endDoc of previous page + self.cacheItems.reverse(); + self.pageIndex -= 1; + self.endItemIndex = self.startItemIndex - 1; + self.startItemIndex = self.endItemIndex - docCount + 1; + self.isFullPage = (docCount === self.itemCount); + // Doc reference for paging + self.prevPageEndDocRef = docs[docCount]; + self.currPageStartDocRef = docs[docCount - 1]; + self.currPageEndDocRef = docs[0]; + return Promise.resolve(self.cacheItems); + }) +} + +var LoadDynamicPage = function () { + var skip = (this.pageIndex - 1) * this.itemCount; + var self = this; + return Load(this.nextQuery, this.itemCount, skip, this.baselineDocRef, this.baselineMode) + .then(function (docs) { + // Get one more document for previous page end + var docCount = docs.length; + self.cacheItems = docs; + self.pageIndex -= 1; + self.endItemIndex = self.startItemIndex - 1; + self.startItemIndex = self.endItemIndex - docCount + 1; + self.isFullPage = (docCount === self.itemCount); + return Promise.resolve(self.cacheItems); + }) +} + +export default LoadPreviousPage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/PageLoader.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/PageLoader.js new file mode 100644 index 000000000..10b302192 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/pageloader/PageLoader.js @@ -0,0 +1,81 @@ +import GetValue from '../../../utils/object/GetValue.js'; +import IsPlainObject from '../../../utils/object/IsPlainObject.js'; +import LoadFirstPage from './LoadFirstPage.js'; +import LoadNextPage from './LoadNextPage.js'; +import LoadPreviousPage from './LoadPreviousPage.js'; +import LoadCurrentPage from './LoadCurrentPage.js'; +import LoadInRange from './LoadInRange.js'; + +class PageLoader { + constructor(config) { + this.setItemCount(GetValue(config, 'itemCount', 100)); + this.setQuery(GetValue(config, 'query', undefined)); + this.setDataMode(GetValue(config, 'dataMode', 0)); + this.setBaselineDoc(GetValue(config, 'baselineDoc', undefined), GetValue(config, 'baselineMode', undefined)); + this.pageIndex = undefined; + this.baselineDocRef = undefined; + this.baselineMode = 'startAt'; + this.startItemIndex = undefined; + this.endItemIndex = undefined; + this.cacheItems = undefined; + this.isFullPage = undefined; + } + + setItemCount(count) { + this.itemCount = count; + return this; + } + + setQuery(nextQuery, prevQuery) { + if (IsPlainObject(nextQuery)) { + var config = nextQuery; + this.nextQuery = config.next; + this.prevQuery = config.previous; + } else { + this.nextQuery = nextQuery; + this.prevQuery = prevQuery; + } + + this.pageIndex = undefined; + this.isFullPage = undefined; + return this; + } + + setDataMode(mode) { + if (typeof (mode) === 'string') { + mode = DATAMODE[mode]; + } + this.dataMode = mode; + return this; + } + + setBaselineDoc(doc, mode) { + if (doc) { + this.baselineDocRef = doc.ref; + this.baselineMode = mode; // 'startAt' or 'startAfter' + } else { + this.baselineDocRef = undefined; + } + return this; + } +} + +var methods = { + loadFirstPage: LoadFirstPage, + loadNextPage: LoadNextPage, + loadPreviousPage: LoadPreviousPage, + loadCurrentPage: LoadCurrentPage, + load: LoadInRange +} + +Object.assign( + PageLoader.prototype, + methods +); + +const DATAMODE = { + static: 0, + dynamic: 1 +} + +export default PageLoader; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/Delete.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/Delete.js new file mode 100644 index 000000000..a47b87097 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/Delete.js @@ -0,0 +1,34 @@ +import Load from './Load.js'; + +var Delete = function (query) { + return Load(query) + .then(function (docs) { + if (docs.length === 0) { // Last page, task done + return Promise.resolve(); + } + + var tasks = []; + var batch, actionCount; + for (var i = 0, cnt = docs.length; i < cnt; i++) { + if (batch === undefined) { + batch = firebase.firestore().batch(); + actionCount = 0; + } + + batch.delete(docs[i].ref); + actionCount++; + if (actionCount >= 500) { + tasks.push(batch.commit()); + batch = undefined; + } + } + + if (batch) { + tasks.push(batch.commit()); + } + + return Promise.all(tasks); + }) +} + +export default Delete; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/FindFirst.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/FindFirst.js new file mode 100644 index 000000000..bae5c0f9b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/FindFirst.js @@ -0,0 +1,30 @@ +import Query from './Query'; + +var FindFirst = function (query, testCallback) { + var out = { + doc: undefined, + index: undefined + } + var startIndex = 0; + return Query({ + query: query, + forEachPageCallback: function (querySnapshot) { + var docs = querySnapshot.docs, + doc; + for (var i = 0, cnt = docs.length; i < cnt; i++) { + doc = docs[i]; + if (testCallback(doc)) { + out.doc = doc; + out.index = startIndex + i; + return true; + } + } + startIndex += querySnapshot.size; + }, + resolveCallback: function () { + return out; + } + }); +} + +export default FindFirst; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/Load.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/Load.js new file mode 100644 index 000000000..e6a07b0b5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/Load.js @@ -0,0 +1,38 @@ +import Query from './Query'; + +var Load = function (query, count, skip, startDocRef, startMode) { + if (count === undefined) { + count = Infinity; + } + if (skip === undefined) { + skip = 0; + } + + var out = []; + var startIndex = 0; + return Query({ + query: query, + totalLines: (skip + count), + startDocRef: startDocRef, + startMode: startMode, + forEachPageCallback: function (querySnapshot) { + var validDocs; + var docCount = querySnapshot.size; + var localStart = skip - startIndex; + if (localStart <= 0) { + validDocs = querySnapshot.docs; + } else if (localStart < docCount) { + validDocs = querySnapshot.docs.slice(localStart, docCount); + } + if (validDocs) { + out.push(...validDocs); + } + startIndex += docCount; + }, + resolveCallback: function () { + return out; + } + }); +} + +export default Load; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/Query.js b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/Query.js new file mode 100644 index 000000000..f641a91ce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/firestore/utils/query/Query.js @@ -0,0 +1,42 @@ +var Query = function (config) { + if (config.totalLines === undefined) { + config.totalLines = Infinity; + } + if (config.linesPerPage === undefined) { + config.linesPerPage = 1000; + } + config.remainderLines = config.totalLines; + + return QueryNextPage(config); +} + +var QueryNextPage = function (config) { + var query = config.query; + if (config.startDocRef) { + query = query[config.startMode](config.startDocRef); + } + + var lineCount = Math.min(config.remainderLines, config.linesPerPage); + config.remainderLines -= lineCount; + return query.limit(lineCount).get() + .then(function (querySnapshot) { + var done = (config.remainderLines === 0) || (querySnapshot.size < lineCount); // Is last page + if (config.forEachPageCallback) { + done |= !!config.forEachPageCallback(querySnapshot); + } + + if (done) { + var out; + if (config.resolveCallback) { + out = config.resolveCallback(); + } + return Promise.resolve(out); + } else { + config.startDocRef = querySnapshot.docs[querySnapshot.size - 1]; + config.startMode = 'startAfter'; + return QueryNextPage(config); + } + }) +} + +export default Query; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/preload/AvailableTest.js b/ui/src/phaser3-rex-plugins/plugins/firebase/preload/AvailableTest.js new file mode 100644 index 000000000..9aa3e52e6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/preload/AvailableTest.js @@ -0,0 +1,39 @@ +import Delay from '../../utils/promise/Delay.js'; + +var AvailableTestPromise = function (config) { + if (AvailableTest(config)) { + return Promise.resolve(); + } + + // console.log('test again') + return Delay(10) + .then(function () { + return AvailableTestPromise(config); + }); +} + +var AvailableTest = function (config) { + var testCallback; + for (var k in config) { + if (!config[k]) { + continue; + } + testCallback = TestCallbacks[k]; + if (testCallback && !testCallback()) { + return false; + } + } + return true; +} + +var TestCallbacks = { + database: function () { + return (firebase.database !== undefined); + }, + + firestore: function () { + return (firebase.firestore !== undefined); + } +} + +export default AvailableTestPromise; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/preload/GetDefaultUrl.js b/ui/src/phaser3-rex-plugins/plugins/firebase/preload/GetDefaultUrl.js new file mode 100644 index 000000000..6c9c76b98 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/preload/GetDefaultUrl.js @@ -0,0 +1,23 @@ +const VERSION = '7.19.0'; + +var GetDefaultUrl = function (version) { + if (version === undefined) { + version = VERSION + } + return { + app: `https://www.gstatic.com/firebasejs/${version}/firebase-app.js`, + + // auth: `https://www.gstatic.com/firebasejs/${version}/firebase-auth.js`, + database: `https://www.gstatic.com/firebasejs/${version}/firebase-database.js`, + firestore: `https://www.gstatic.com/firebasejs/${version}/firebase-firestore.js`, + // storage: `https://www.gstatic.com/firebasejs/${version}/firebase-storage.js`, + + // analytics: `https://www.gstatic.com/firebasejs/${version}/firebase-analytics.js`, + // functions: `https://www.gstatic.com/firebasejs${version}/firebase-functions.js`, + // messaging: `https://www.gstatic.com/firebasejs/${version}/firebase-messaging.js`, + // performance: `https://www.gstatic.com/firebasejs/${version}/firebase-performance.js`, + // 'remote-config': `https://www.gstatic.com/firebasejs/${version}/firebase-remote-config.js` + } +} + +export default GetDefaultUrl; diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/preload/LoaderCallback.js b/ui/src/phaser3-rex-plugins/plugins/firebase/preload/LoaderCallback.js new file mode 100644 index 000000000..438b1a5dd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/preload/LoaderCallback.js @@ -0,0 +1,19 @@ +import Preload from './Preload.js'; +import AwaitFile from '../../loader/awaitloader/AwaitFile.js'; + +const LoaderCallback = function (urlConfig, firebaseConfig) { + var callback = function (successCallback, failureCallback) { + return Preload(urlConfig, firebaseConfig) + .then(function () { + setTimeout(successCallback, 0); + }) + .catch(failureCallback) + } + + this.addFile(new AwaitFile(this, { + config: { callback: callback } + })); + return this; +} + +export default LoaderCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/firebase/preload/Preload.js b/ui/src/phaser3-rex-plugins/plugins/firebase/preload/Preload.js new file mode 100644 index 000000000..7d0b1bb65 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/firebase/preload/Preload.js @@ -0,0 +1,45 @@ +import GetDefaultUrl from './GetDefaultUrl.js'; +import MergeRight from '../../utils/object/MergeRight.js'; +import LoadScriptPromise from '../../utils/loader/LoadScriptPromise.js'; +import AvailableTest from './AvailableTest.js'; + +var Preload = function (urlConfig, firebaseConfig) { + if (typeof (urlConfig) === 'string') { // Get specific version + urlConfig = GetDefaultUrl(urlConfig); + } else { // Get default version + urlConfig = MergeRight(GetDefaultUrl(), urlConfig); + } + + return LoadScriptPromise(urlConfig.app) // Load firebase-app + .then(function () { // Load other SDK + var promises = []; + var url; + for (var k in urlConfig) { + if (k === 'app') { + continue; + } + url = urlConfig[k]; + if (!url) { + continue; + } + promises.push(LoadScriptPromise(url)) + } + + if (promises.length === 0) { + return Promise.resolve(); + } else { + return Promise.all(promises); + } + }) + .then(function () { // Wait until all vairalbe are available + return AvailableTest(urlConfig); + }) + .then(function () { + if (firebaseConfig !== undefined) { + firebase.initializeApp(firebaseConfig); + } + return Promise.resolve(); + }) +} + +export default Preload; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fisheyepipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/fisheyepipeline-plugin.d.ts new file mode 100644 index 000000000..57e40d170 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fisheyepipeline-plugin.d.ts @@ -0,0 +1,29 @@ +// import * as Phaser from 'phaser'; +import FishEyePostFxPipeline from './fisheyepipeline'; + +export default FishEyePipelinePlugin; + +declare namespace FishEyePipelinePlugin { + + interface IConfig extends FishEyePostFxPipeline.IConfig { + name?: string + } + +} + +declare class FishEyePipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: FishEyePipelinePlugin.IConfig + ): FishEyePostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): FishEyePostFxPipeline | FishEyePostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fisheyepipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/fisheyepipeline-plugin.js new file mode 100644 index 000000000..760b98fec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fisheyepipeline-plugin.js @@ -0,0 +1,14 @@ +import FishEyePostFxPipeline from './fisheyepipeline'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class FishEyePipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(FishEyePostFxPipeline, 'rexFishEyePostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.FishEyePostFx', FishEyePostFxPipeline); + +export default FishEyePipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fisheyepipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/fisheyepipeline.d.ts new file mode 100644 index 000000000..801a23a50 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fisheyepipeline.d.ts @@ -0,0 +1,2 @@ +import FishEyePostFxPipeline from './shaders/fisheye/FishEyePostFxPipeline'; +export default FishEyePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fisheyepipeline.js b/ui/src/phaser3-rex-plugins/plugins/fisheyepipeline.js new file mode 100644 index 000000000..801a23a50 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fisheyepipeline.js @@ -0,0 +1,2 @@ +import FishEyePostFxPipeline from './shaders/fisheye/FishEyePostFxPipeline'; +export default FishEyePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/flash-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/flash-plugin.d.ts new file mode 100644 index 000000000..0cca0fde0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/flash-plugin.d.ts @@ -0,0 +1,9 @@ +import Flash from './flash'; + +export default class FlashPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Flash.IConfig + ): Flash; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/flash-plugin.js b/ui/src/phaser3-rex-plugins/plugins/flash-plugin.js new file mode 100644 index 000000000..f237c2458 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/flash-plugin.js @@ -0,0 +1,19 @@ +import Flash from './flash.js'; + +class FlashPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Flash(gameObject, config); + } +} + +export default FlashPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/flash.d.ts b/ui/src/phaser3-rex-plugins/plugins/flash.d.ts new file mode 100644 index 000000000..fecc0a093 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/flash.d.ts @@ -0,0 +1,2 @@ +import Flash from './behaviors/flash/Flash'; +export default Flash; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/flash.js b/ui/src/phaser3-rex-plugins/plugins/flash.js new file mode 100644 index 000000000..651b3a238 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/flash.js @@ -0,0 +1,2 @@ +import Flash from './behaviors/flash/Flash.js'; +export default Flash; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/flip-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/flip-plugin.d.ts new file mode 100644 index 000000000..87966f3d5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/flip-plugin.d.ts @@ -0,0 +1,9 @@ +import Flip from './flip'; + +export default class FlipPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Flip.IConfig + ): Flip; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/flip-plugin.js b/ui/src/phaser3-rex-plugins/plugins/flip-plugin.js new file mode 100644 index 000000000..9b82b16fa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/flip-plugin.js @@ -0,0 +1,19 @@ +import Flip from './flip.js'; + +class FlipPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Flip(gameObject, config); + } +} + +export default FlipPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/flip.d.ts b/ui/src/phaser3-rex-plugins/plugins/flip.d.ts new file mode 100644 index 000000000..a78302543 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/flip.d.ts @@ -0,0 +1,2 @@ +import Flip from './behaviors/flip/Flip'; +export default Flip; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/flip.js b/ui/src/phaser3-rex-plugins/plugins/flip.js new file mode 100644 index 000000000..2f9e05f43 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/flip.js @@ -0,0 +1,2 @@ +import Flip from './behaviors/flip/Flip.js'; +export default Flip; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fsm-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/fsm-plugin.d.ts new file mode 100644 index 000000000..9bbd028b2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fsm-plugin.d.ts @@ -0,0 +1,8 @@ +import FSM from './fsm'; + +export default class FSMPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: FSM.IConfig + ): FSM; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fsm-plugin.js b/ui/src/phaser3-rex-plugins/plugins/fsm-plugin.js new file mode 100644 index 000000000..28343fc4c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fsm-plugin.js @@ -0,0 +1,23 @@ +import FSM from './fsm.js'; +import SetValue from './utils/object/SetValue.js'; + +class FSMPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new FSM(config); + } + +} + +SetValue(window, 'RexPlugins.FSM', FSM); + +export default FSMPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fsm.d.ts b/ui/src/phaser3-rex-plugins/plugins/fsm.d.ts new file mode 100644 index 000000000..46db5644d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fsm.d.ts @@ -0,0 +1,2 @@ +import FSM from './logic/fsm/FSM'; +export default FSM; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fsm.js b/ui/src/phaser3-rex-plugins/plugins/fsm.js new file mode 100644 index 000000000..8f1a7603b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fsm.js @@ -0,0 +1,2 @@ +import FSM from './logic/fsm/FSM.js'; +export default FSM; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fullwindowrectangle-plugin.js b/ui/src/phaser3-rex-plugins/plugins/fullwindowrectangle-plugin.js new file mode 100644 index 000000000..674d310ab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fullwindowrectangle-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/shape/fullwindowrectangle/Factory.js'; +import Creator from './gameobjects/shape/fullwindowrectangle/Creator.js'; +import FullWindowRectangle from './gameobjects/shape/fullwindowrectangle/FullWindowRectangle.js'; +import SetValue from './utils/object/SetValue.js'; + +class FullWindowRectanglePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexFullWindowRectangle', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.FullWindowRectangle', FullWindowRectangle); + +export default FullWindowRectanglePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fullwindowrectangle.d.ts b/ui/src/phaser3-rex-plugins/plugins/fullwindowrectangle.d.ts new file mode 100644 index 000000000..a7c7ffddb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fullwindowrectangle.d.ts @@ -0,0 +1,2 @@ +import FullWindowRectangle from './gameobjects/shape/fullwindowrectangle/FullWindowRectangle.js'; +export default FullWindowRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fullwindowrectangle.js b/ui/src/phaser3-rex-plugins/plugins/fullwindowrectangle.js new file mode 100644 index 000000000..a7c7ffddb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fullwindowrectangle.js @@ -0,0 +1,2 @@ +import FullWindowRectangle from './gameobjects/shape/fullwindowrectangle/FullWindowRectangle.js'; +export default FullWindowRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fuzzy-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/fuzzy-plugin.d.ts new file mode 100644 index 000000000..5979eafd5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fuzzy-plugin.d.ts @@ -0,0 +1,5 @@ +import BuildFuzzyModule from './fuzzy'; + +export default class FuzzyPlugin extends Phaser.Plugins.BasePlugin { + add: typeof BuildFuzzyModule +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fuzzy-plugin.js b/ui/src/phaser3-rex-plugins/plugins/fuzzy-plugin.js new file mode 100644 index 000000000..986e71ec6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fuzzy-plugin.js @@ -0,0 +1,20 @@ +import BuildFuzzyModule from './fuzzy'; + +class FuzzyPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return BuildFuzzyModule(config); + } + +} + +export default FuzzyPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fuzzy.d.ts b/ui/src/phaser3-rex-plugins/plugins/fuzzy.d.ts new file mode 100644 index 000000000..62100a8f4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fuzzy.d.ts @@ -0,0 +1,2 @@ +import BuildFuzzyModule from './math/fuzzy/BuildFuzzyModule'; +export default BuildFuzzyModule; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/fuzzy.js b/ui/src/phaser3-rex-plugins/plugins/fuzzy.js new file mode 100644 index 000000000..85ea4ea58 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/fuzzy.js @@ -0,0 +1,2 @@ +import BuildFuzzyModule from './math/fuzzy/BuildFuzzyModule.js'; +export default BuildFuzzyModule; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/BitmapText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/BitmapText.js new file mode 100644 index 000000000..60a8fb5e0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/BitmapText.js @@ -0,0 +1,51 @@ +import Blitter from '../blitterbase/BlitterBase.js'; +import Methods from './methods/Methods.js'; +import PenManager from './penmanager/PenManager.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class BitmapText extends Blitter { + constructor(scene, x, y, font, text, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + font = GetValue(config, 'font', ''); + text = GetValue(config, 'text', ''); + } + + super(scene, x, y); + this.type = 'rexBitmapText'; + + this.fontData = undefined; + this.fromAtlas = undefined; + this._fontSize = 0; + this._text = ''; + this.penManager = new PenManager(this, config); + + this.setFixedSize(GetValue(config, 'fixedWidth', 0), GetValue(config, 'fixedHeight', 0)); + this.setPadding(GetValue(config, 'padding', 0)); + this.setLetterSpacing(GetValue(config, 'letterSpacing', 0)); + + + this.setFont(font); + + this.setText(text); + } + + get text() { + return this._text; + } + + set text(value) { + this.setText(text); + } +} + +Object.assign( + BitmapText.prototype, + Methods +); + +export default BitmapText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/AppendText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/AppendText.js new file mode 100644 index 000000000..2f3fffbcb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/AppendText.js @@ -0,0 +1,21 @@ +var AppendText = function (value) { + if (value == null) { + value = ''; + } else if (Array.isArray(value)) { + value = value.join('\n'); + } else { + value = value.toString(); + } + + if (value === '') { + return this; + } + + this._text += value; + + this.penManager.addTextPens(value); + + return this; +} + +export default AppendText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/Methods.js new file mode 100644 index 000000000..b3287a5af --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/Methods.js @@ -0,0 +1,25 @@ +import SetFixedSize from './SetFixedSize.js'; +import SetPadding from './SetPadding.js'; +import SetLetterSpacing from './SetLetterSpacing.js'; + +import SetFont from './SetFont.js'; +import SetText from './SetText.js'; +import AppendText from './AppendText.js'; +import SetWrapConfig from './SetWrapConfig.js'; +import UpdateText from './UpdateText.js'; + +const Methods = { + setFixedSize: SetFixedSize, + setPadding: SetPadding, + setLetterSpacing: SetLetterSpacing, + + setFont: SetFont, + setText: SetText, + appendText: AppendText, + setWrapConfig: SetWrapConfig, + updateText: UpdateText, + + +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/RunVerticalWrap.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/RunVerticalWrap.js new file mode 100644 index 000000000..9cf287920 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/RunVerticalWrap.js @@ -0,0 +1,5 @@ +var RunVerticalWrap = function () { + +} + +export default RunVerticalWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/RunWordWrap.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/RunWordWrap.js new file mode 100644 index 000000000..00f133642 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/RunWordWrap.js @@ -0,0 +1,5 @@ +var RunWordWrap = function () { + +} + +export default RunWordWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetFixedSize.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetFixedSize.js new file mode 100644 index 000000000..1dfe78593 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetFixedSize.js @@ -0,0 +1,15 @@ +var SetFixedSize = function (width, height) { + if (width === undefined) { + width = 0; + } + if (height === undefined) { + height = 0; + } + + this.fixedWidth = width; + this.fixedHeight = height; + + return this; +} + +export default SetFixedSize; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetFont.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetFont.js new file mode 100644 index 000000000..7224d02f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetFont.js @@ -0,0 +1,30 @@ +var SetFont = function (key) { + if (key === this.font) { + return this; + } + + var entry = this.scene.sys.cache.bitmapFont.get(key); + + this.font = key; + + if (entry) { + this.fontData = entry.data; + this.fromAtlas = (entry.fromAtlas === true); + this._fontSize = this.fontData.size; + + this.setTexture(entry.texture, entry.frame); + } else { + console.warn(`Invalid BitmapText key: ${key}`); + + this.fontData = undefined; + this.fromAtlas = undefined; + this._fontSize = 0; + this.setTexture(); + } + + this.updateText(); + + return this; +} + +export default SetFont; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetLetterSpacing.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetLetterSpacing.js new file mode 100644 index 000000000..44e34ecdc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetLetterSpacing.js @@ -0,0 +1,6 @@ +var SetLetterSpacing = function (spacing) { + this.letterSpacing = spacing; + return this; +} + +export default SetLetterSpacing; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetPadding.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetPadding.js new file mode 100644 index 000000000..55e2a752a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetPadding.js @@ -0,0 +1,9 @@ +import { SetPadding as SetPaddingBase } from '../../../../utils/padding/PaddingMethods.js'; + +var SetPadding = function (key, value) { + SetPaddingBase(this.padding, key, value); + + return this; +}; + +export default SetPadding; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetText.js new file mode 100644 index 000000000..5677eedb6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetText.js @@ -0,0 +1,21 @@ +var SetText = function (value) { + if (value == null) { + value = ''; + } else if (Array.isArray(value)) { + value = value.join('\n'); + } else { + value = value.toString(); + } + + if (value === this._text) { + return this; + } + + this._text = value; + + this.penManager.setTextPens(value); + + return this; +} + +export default SetText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetWrapConfig.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetWrapConfig.js new file mode 100644 index 000000000..46856752e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/SetWrapConfig.js @@ -0,0 +1,10 @@ +var SetWrapConfig = function (config) { + if (config === undefined) { + config = {}; + } + + this.wrapConfig = config; + return this; +} + +export default SetWrapConfig; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/UpdateCharacterDataManager.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/UpdateCharacterDataManager.js new file mode 100644 index 000000000..152adbde3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/UpdateCharacterDataManager.js @@ -0,0 +1,13 @@ +var UpdateCharacterDataManager = function (text, wrapMode, wrapWidth, lineHeight, characterDataManager) { + if (characterDataManager === undefined) { + characterDataManager = this.characterDataManager; + } + characterDataManager.clear(); + if (text === "") { + return characterDataManager; + } + + +} + +export default UpdateCharacterDataManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/UpdateText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/UpdateText.js new file mode 100644 index 000000000..b90d5a7bb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/methods/UpdateText.js @@ -0,0 +1,4 @@ +var UpdateText = function () { +} + +export default UpdateText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/PenManager.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/PenManager.js new file mode 100644 index 000000000..78b9dafe5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/PenManager.js @@ -0,0 +1,30 @@ +import Methods from './methods/Methods.js'; +import Pool from '../../../../pool.js'; + +const GetFastValue = Phaser.Utils.Objects.GetFastValue; + +var PensPool = new Pool(); // default pens pool +var LinesPool = new Pool(); // default lines pool + +class PenManager { + constructor(bitmapText, config) { + this.bitmapText = bitmapText; + this.pens = []; + this.lines = []; // pens in lines [ [],[],[],.. ] + this.maxLinesWidth = undefined; + + this.PensPool = GetFastValue(config, 'pensPool', PensPool); + this.LinesPool = GetFastValue(config, 'linesPool', LinesPool); + } + + destroy() { + this.clear(); + } +} + +Object.assign( + PenManager.prototype, + Methods +); + +export default PenManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/AddTextPens.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/AddTextPens.js new file mode 100644 index 000000000..b672193a1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/AddTextPens.js @@ -0,0 +1,13 @@ +import CharPen from '../pen/CharPen.js'; + +var AddTextPens = function (text) { + for (var i = 0, cnt = text.length; i < cnt; i++) { + var pen = new CharPen(this) + .setChar(text.charAt(i)) + this.pens.push(pen); + } + + return this; +} + +export default AddTextPens; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/Methods.js new file mode 100644 index 000000000..0759ad1be --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/Methods.js @@ -0,0 +1,19 @@ +import RemovePenMethods from './RemovePenMethods.js'; +import AddTextPens from './AddTextPens.js'; +import SetTextPens from './SetTextPens.js'; +import RunWordWrap from './runwordwrap/RunWordWrap.js'; +import RunVerticalWrap from './runverticalwrap/RunVerticalWrap.js'; + +var Methods = { + addTextPens: AddTextPens, + setTextPens: SetTextPens, + runWordWrap: RunWordWrap, + runVerticalWrap: RunVerticalWrap, +} + +Object.assign( + Methods, + RemovePenMethods +); + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/RemovePenMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/RemovePenMethods.js new file mode 100644 index 000000000..0e963b9c5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/RemovePenMethods.js @@ -0,0 +1,56 @@ +const RemoveItem = Phaser.Utils.Array.Remove; + +var Methods = { + free(pen) { + if (!this.PensPool) { + return this; + } + this.PensPool.push(pen); + pen.onFree(); + + for (var i = 0, cnt = this.lines.length; i < cnt; i++) { + RemoveItem(this.lines[i], pen); + } + return this; + }, + + freeMultiple(pens) { + if (!this.PensPool) { + return this; + } + + for (var i = 0, cnt = pens.length; i < cnt; i++) { + this.free(pens[i]); + } + + return this; + }, + + clear() { + // 1. Remove/recycle all children of blitter + this.parent.removeChildren(); + // 2. Free all pens. + for (var i = 0, cnt = this.pens.length; i < cnt; i++) { + this.pens[i].onFree(); + } + // 3. Free all lines + for (var i = 0, len = this.lines.length; i < len; i++) { + this.lines[i].length = 0; + } + + // 4. + this.PensPool.pushMultiple(this.pens); + this.LinesPool.pushMultiple(this.lines); + this.maxLinesWidth = undefined; + + return this; + }, + + remove(pen) { + this.free(pen); + + return this; + } +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/SetTextPens.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/SetTextPens.js new file mode 100644 index 000000000..edbd6d134 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/SetTextPens.js @@ -0,0 +1,9 @@ +var SetTextPens = function (text) { + this + .clear() + .addTextPens(text); + + return this; +} + +export default SetTextPens; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/runverticalwrap/RunVerticalWrap.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/runverticalwrap/RunVerticalWrap.js new file mode 100644 index 000000000..fb84c3210 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/runverticalwrap/RunVerticalWrap.js @@ -0,0 +1,5 @@ +var RunVerticalWrap = function (config) { + +} + +export default RunVerticalWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/runwordwrap/GetWord.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/runwordwrap/GetWord.js new file mode 100644 index 000000000..a405dd30b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/runwordwrap/GetWord.js @@ -0,0 +1,36 @@ +var GetWord = function (pens, startIndex, charMode, result) { + if (result === undefined) { + result = { word: [], width: 0 }; + } + + result.word.length = 0; + + var endIndex = pens.length; + var currentIndex = startIndex; + var word = result.word, wordWidth = 0; + while (currentIndex < endIndex) { + var pen = pens[currentIndex]; + var char = pen.char; + if ((char !== undefined) && (char !== ' ') && (char !== '\n')) { + word.push(pen); + wordWidth += pen.outerWidth; + currentIndex++; + // Continue + } else { // Get non-text pen, a space, or a new-line + if (currentIndex === startIndex) { // Single pen + word.push(pen); + wordWidth += pen.outerWidth; + } + break; + } + + if (charMode) { // Word only contains 1 character + break; + } + } + + result.width = wordWidth; + return result; +} + +export default GetWord; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/runwordwrap/RunWordWrap.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/runwordwrap/RunWordWrap.js new file mode 100644 index 000000000..82214c45c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/methods/runwordwrap/RunWordWrap.js @@ -0,0 +1,17 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +var RunWordWrap = function () { + var bitmapText = this.bitmapText; + + var startX = bitmapText.padding.left, + startY = bitmapText.padding.top, + x = startX, + y = startY; + + var pens = this.pens; + for (var i = 0, cnt = pens.length; i < cnt; i++) { + + } +} + +export default RunWordWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/pen/Base.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/pen/Base.js new file mode 100644 index 000000000..f6ed91f13 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/pen/Base.js @@ -0,0 +1,46 @@ +import BobBase from '../../../blitterbase/bob/Base.js'; + +class Base { + constructor(parent) { + this + .setParent(parent) + + this.bobs = undefined; // bob, or dictionary of bobs + } + + setParent(parent) { + this.parent = parent; + return this; + } + + get bitmapText() { + if (this.parent) { + return this.parent.bitmapText; + } else { + return undefined; + } + } + + onFree() { + this + .setParent(); + + if (this.bobs instanceof BobBase) { + this.bobs.destroy(); + this.bobs = undefined; + } else { + var bobs = this.bobs; + for (var key in bobs) { + bobs[key].destroy(); + delete bobs[key]; + } + } + } + + destroy() { + this.parent.remove(this); + } + +} + +export default Base; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/pen/CharPen.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/pen/CharPen.js new file mode 100644 index 000000000..ccd6b8c79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/pen/CharPen.js @@ -0,0 +1,24 @@ +import ImagePen from './ImagePen.js'; + +class CharPen extends ImagePen { + onFree() { + this.char = undefined; + super.onFree(); + } + + setChar(char) { + this.char = char; + var fontData = this.bitmapText.fontData; + if (!fontData) { + return this; + } + + var frame = fontData.chars[char.charCodeAt(0)]; + this.setFrame(frame); + + return this; + } + +} + +export default CharPen; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/pen/ImagePen.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/pen/ImagePen.js new file mode 100644 index 000000000..47bf0243d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/bitmaptext/penmanager/pen/ImagePen.js @@ -0,0 +1,127 @@ +import Base from './Base.js'; +import AddImage from '../../../blitterbase/utils/AddImage.js'; + +class ImagePen extends Base { + constructor(parent) { + super(parent); + this.bobs = {}; + + this._x = 0; + this._y = 0; + this.leftSpace = 0; + this.rightSpace = 0; + + this.addImage('main', 0); + } + + onFree() { + this.frame = undefined; + + this + .setLeftSpace(0) + .setRightSpace(0); + + super.onFree(); + } + + setFrame(frame) { + this.frame = frame; + + var bobs = this.bobs; + for (var key in bobs) { + bobs[key].setFrame(frame); + } + + return this; + } + + addImage(key, depth) { + var bob = AddImage(this.bitmapText, this.frame) + .setPosition(this.x, this.y) + .setDepth(depth); + + this.bobs[key] = bob; + + return this; + } + + get x() { + return this._x; + } + + set x(value) { + var dx = value - this._x; + this._x = value; + + var bobs = this.bobs; + for (var key in bobs) { + bobs[key].x += dx; + } + } + + get y() { + return this._y; + } + + set y(value) { + var dy = value - this._y; + this._y = value; + + var bobs = this.bobs; + for (var key in bobs) { + bobs[key].y += dy; + } + } + + setPosition(x, y) { + this.x = x; + this.y = y; + return this; + } + + setLeftSpace(value) { + this.leftSpace = value; + return this; + } + + setRightSpace(value) { + this.rightSpace = value; + return this; + } + + get width() { + return this.bobs.main.width; + } + + get outerWidth() { + return this.width + this.leftSpace + this.rightSpace; + } + + setShadow(x, y, color, alpha) { + this.shadowX = x; + this.shadowY = y; + this.shadowColor = color; + this.shadowAlpha = alpha; + + if (!this.bobs.shadow) { + this.addImage('shadow', -1); + } + + var bob = this.bobs.shadow; + if (x === undefined) { + bob.setActive(false); + } else { + bob + .setActive(true) + .setPosition(this.x + this.shadowX, this.y + this.shadowY) + .setAlpha(alpha) + .setColor(color) + .setTintEffect(2) + } + + return this; + } + +} + +export default ImagePen; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Blitter.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Blitter.d.ts new file mode 100644 index 000000000..5fec746b4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Blitter.d.ts @@ -0,0 +1,14 @@ +import BlitterBase from '../blitterbase/BlitterBase.js'; +import ImageData from '../blitterbase/bob/image/ImageData'; + +export default Blitter; + +declare class Blitter extends BlitterBase { + addImage( + frame: string + ): this; + + addImage( + config: ImageData.IModifyConfig + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Blitter.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Blitter.js new file mode 100644 index 000000000..5c00bdcd5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Blitter.js @@ -0,0 +1,11 @@ +import BlitterBase from '../blitterbase/BlitterBase.js'; +import AddImage from '../blitterbase/utils/AddImage.js'; + +class Blitter extends BlitterBase { + addImage(config) { + AddImage(this, config); + return this; + } +} + +export default Blitter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Creator.js new file mode 100644 index 000000000..cfadd8ec5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Creator.js @@ -0,0 +1,16 @@ +import Blitter from './Blitter.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var key = GetAdvancedValue(config, 'key', null); + var frame = GetAdvancedValue(config, 'frame', null); + var gameObject = new Blitter(this.scene, 0, 0, key, frame, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Factory.js new file mode 100644 index 000000000..f07560a19 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitter/Factory.js @@ -0,0 +1,7 @@ +import Blitter from './Blitter.js'; + +export default function (x, y, texture, frame, config) { + var gameObject = new Blitter(this.scene, x, y, texture, frame, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/BlitterBase.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/BlitterBase.d.ts new file mode 100644 index 000000000..b5f7e4ce7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/BlitterBase.d.ts @@ -0,0 +1,150 @@ +import BobBase from './bob/Base'; + +export default BlitterBase; + +declare namespace BlitterBase { + interface IConfig { + reuseBob?: boolean, + } +} + +declare class BlitterBase extends Phaser.GameObjects.GameObject { + constructor( + scene: Phaser.Scene, + x?: number, + y?: number, + texture?: string, + frame?: string, + config?: BlitterBase.IConfig + ); + + children: Phaser.Structs.List; + + texture: Phaser.Textures.Texture | Phaser.Textures.CanvasTexture; + frame: Phaser.Textures.Frame; + setTexture( + key: string, + frame?: string | number | undefined, + ): this; + + resize(width: number, height: number): this; + setSize(width: number, height: number): this; + + addChild( + bob: BobBase + ): this; + + removeChild( + bob: BobBase + ): this; + + removeChildren(): this; + + clear(): this; + + getLastAppendedChildren( + ): BobBase[]; + + getChildren( + ): BobBase[]; + + + setTint(tint: number): this; + setTintFill(tint: number): this; + clearTint(): this; + tint: number; + tintFill: boolean; + + // Components + clearAlpha(): this; + setAlpha(topLeft?: number, topRight?: number, bottomLeft?: number, bottomRight?: number): this; + alpha: number; + alphaTopLeft: number; + alphaTopRight: number; + alphaBottomLeft: number; + alphaBottomRight: number; + + blendMode: Phaser.BlendModes | string; + setBlendMode(value: string | Phaser.BlendModes): this; + + width: number; + height: number; + displayWidth: number; + displayHeight: number; + setDisplaySize(width: number, height: number): this; + + depth: number; + setDepth(value: number): this; + + getCenter(output?: O): O; + getTopLeft(output?: O, includeParent?: boolean): O; + getTopCenter(output?: O, includeParent?: boolean): O; + getTopRight(output?: O, includeParent?: boolean): O; + getLeftCenter(output?: O, includeParent?: boolean): O; + getRightCenter(output?: O, includeParent?: boolean): O; + getBottomLeft(output?: O, includeParent?: boolean): O; + getBottomCenter(output?: O, includeParent?: boolean): O; + getBottomRight(output?: O, includeParent?: boolean): O; + getBounds(output?: O): O; + + mask: Phaser.Display.Masks.BitmapMask | Phaser.Display.Masks.GeometryMask; + setMask(mask: Phaser.Display.Masks.BitmapMask | Phaser.Display.Masks.GeometryMask): this; + clearMask(destroyMask?: boolean): this; + createBitmapMask(renderable?: Phaser.GameObjects.GameObject): Phaser.Display.Masks.BitmapMask; + createGeometryMask(graphics?: Phaser.GameObjects.Graphics): Phaser.Display.Masks.GeometryMask; + + originX: number; + originY: number; + displayOriginX: number; + displayOriginY: number; + setOrigin(x?: number, y?: number): this; + setOriginFromFrame(): this; + setDisplayOrigin(x?: number, y?: number): this; + updateDisplayOrigin(): this; + + defaultPipeline: Phaser.Renderer.WebGL.WebGLPipeline; + pipeline: Phaser.Renderer.WebGL.WebGLPipeline; + hasPostPipeline: boolean; + postPipelines: Phaser.Renderer.WebGL.Pipelines.PostFXPipeline[]; + pipelineData: object; + initPipeline(pipeline: string | Phaser.Renderer.WebGL.WebGLPipeline): boolean; + setPipeline(pipeline: string | Phaser.Renderer.WebGL.WebGLPipeline, pipelineData?: object, copyData?: boolean): this; + setPostPipeline(pipelines: string | string[] | Function | Function[] | Phaser.Renderer.WebGL.Pipelines.PostFXPipeline | Phaser.Renderer.WebGL.Pipelines.PostFXPipeline[], pipelineData?: object, copyData?: boolean): this; + setPipelineData(key: string, value?: any): this; + getPostPipeline(pipeline: string | Function | Phaser.Renderer.WebGL.Pipelines.PostFXPipeline): Phaser.Renderer.WebGL.Pipelines.PostFXPipeline | Phaser.Renderer.WebGL.Pipelines.PostFXPipeline[]; + resetPipeline(resetPostPipelines?: boolean, resetData?: boolean): boolean; + resetPostPipeline(resetData?: boolean): void; + removePostPipeline(pipeline: string | Phaser.Renderer.WebGL.Pipelines.PostFXPipeline): this; + getPipelineName(): string; + + scrollFactorX: number; + scrollFactorY: number; + setScrollFactor(x: number, y?: number): this; + + x: number; + y: number; + z: number; + w: number; + scale: number; + scaleX: number; + scaleY: number; + angle: number; + rotation: number; + setPosition(x?: number, y?: number, z?: number, w?: number): this; + copyPosition(source: Phaser.Types.Math.Vector2Like | Phaser.Types.Math.Vector3Like | Phaser.Types.Math.Vector4Like): this; + setRandomPosition(x?: number, y?: number, width?: number, height?: number): this; + setRotation(radians?: number): this; + setAngle(degrees?: number): this; + setScale(x: number, y?: number): this; + setX(value?: number): this; + setY(value?: number): this; + setZ(value?: number): this; + setW(value?: number): this; + getLocalTransformMatrix(tempMatrix?: Phaser.GameObjects.Components.TransformMatrix): Phaser.GameObjects.Components.TransformMatrix; + getWorldTransformMatrix(tempMatrix?: Phaser.GameObjects.Components.TransformMatrix, parentMatrix?: Phaser.GameObjects.Components.TransformMatrix): Phaser.GameObjects.Components.TransformMatrix; + getLocalPoint(x: number, y: number, point?: Phaser.Math.Vector2, camera?: Phaser.Cameras.Scene2D.Camera): Phaser.Math.Vector2; + getParentRotation(): number; + + visible: boolean; + setVisible(value: boolean): this; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/BlitterBase.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/BlitterBase.js new file mode 100644 index 000000000..7f39928f6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/BlitterBase.js @@ -0,0 +1,114 @@ +import Render from './render/Render.js'; +import Methods from './methods/Methods.js'; +import PoolManager from './poolmanager/PoolManager.js'; + +const GameObject = Phaser.GameObjects.GameObject; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const List = Phaser.Structs.List; +const StableSort = Phaser.Utils.Array.StableSort; + +class Blitter extends GameObject { + constructor(scene, x, y, texture, frame, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + texture = GetValue(config, 'texture'); + frame = GetValue(config, 'frame'); + } + + if (x === undefined) { + x = 0; + } + if (y === undefined) { + y = 0; + } + + super(scene, 'rexBlitter'); + + this.children = new List(); + this.renderList = []; + this.displayListDirty = false; + this.lastAppendedChildren = []; + + var reuseBob = GetValue(config, 'reuseBob', true); + this.poolManager = (reuseBob) ? (new PoolManager(config)) : undefined; + + this + .setTexture(texture, frame) + .setPosition(x, y) + .setOrigin(0, 0) + .clearTint() + .initPipeline() + + } + + preDestroy() { + this.removeChildren(); + this.children.destroy(); + this.renderList.length = 0; + + if (this.poolManager) { + this.poolManager.destroy(); + } + } + + getRenderList() { + if (this.displayListDirty) { + this.renderList.length = 0; + var needDepthSort = false; + + var children = this.children.list; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if (ChildCanRender(child)) { + this.renderList.push(child); + + if (!needDepthSort) { + needDepthSort = (child.depth !== 0); + } + } + } + + if (needDepthSort) { + StableSort(this.renderList, SortByDepth) + } + + this.displayListDirty = false; + } + + return this.renderList; + } +} + +var ChildCanRender = function (child) { + return (child.active && child.visible && (child.alpha > 0)); +} + +var SortByDepth = function (childA, childB) { + return childA._depth - childB._depth; +} + +const Components = Phaser.GameObjects.Components; +Phaser.Class.mixin(Blitter, + [ + Components.Alpha, + Components.BlendMode, + Components.ComputedSize, + Components.Depth, + Components.GetBounds, + Components.Mask, + Components.Origin, + Components.Pipeline, + Components.ScrollFactor, + Components.Transform, + Components.Visible, + Render, + + Methods + ] +); + + +export default Blitter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/Base.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/Base.d.ts new file mode 100644 index 000000000..2c10560f2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/Base.d.ts @@ -0,0 +1,23 @@ +import BlitterBase from '../BlitterBase'; +import DataMethods from '../../../../utils/data/DataMethods'; + +export default BobBase; + +declare class BobBase extends DataMethods{ + constructor( + parent: BlitterBase, + type: string + ); + + parent: BlitterBase; + setParent( + parent?: BlitterBase + ): this; + + active: boolean; + setActive( + active?: boolean + ): this; + + reset(): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/Base.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/Base.js new file mode 100644 index 000000000..418b7f903 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/Base.js @@ -0,0 +1,81 @@ +import DataMethods from '../../../../utils/data/DataMethods.js' + +class Base { + constructor(parent, type) { + this.type = type; + + this.data = undefined; + + this + .setParent(parent) + .reset() + .setActive(); + + } + + destroy() { + if (this.parent) { + this.parent.removeChild(this); + // Remove this bob from blitter, and free it (bob.onFree()) + // Will set this.parent to undefined + } + } + + setParent(parent) { + this.parent = parent; + return this; + } + + // get scene() { + // if (this.parent) { + // return this.parent.scene; + // } else { + // return null; + // } + // } + + setDisplayListDirty(displayListDirty) { + if (displayListDirty && this.parent) { + this.parent.displayListDirty = true; + } + return this; + } + + get active() { + return this._active; + } + + set active(value) { + this.setDisplayListDirty(this._active != value); + this._active = value; + } + + setActive(active) { + if (active === undefined) { + active = true; + } + this.active = active; + return this; + } + + modifyPorperties(o) { + return this; + } + + // Override + reset() { + this.clearData(); + } + + // Override + onFree() { + this.reset().setActive(false).setParent(); + } +} + +Object.assign( + Base.prototype, + DataMethods +); + +export default Base; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/RenderBase.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/RenderBase.d.ts new file mode 100644 index 000000000..662f3d70e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/RenderBase.d.ts @@ -0,0 +1,81 @@ +import BobBase from './Base'; + +export default RenderBase; + +declare namespace RenderBase { + interface IModifyConfig { + x?: number, y?: number, + + rotation?: number, angle?: number, + + alpha?: number, + + width?: number, height?: number, + scale?: number, scaleX?: number, scaleY?: number, + displayWidth?: number, displayHeight?: number, + + origin?: number, originX?: number, originY?: number, + + depth?: number + } +} + +declare class RenderBase extends BobBase { + visible: boolean; + setVisible(visible?: boolean): this; + + alpha: number; + setAlpha(alpha: number): this; + + x: number; + y: number; + setX(x: number): this; + setY(y: number): this; + setPosition(x: number, y: number): this; + + rotation: number; + angle: number; + setRotation(rotation: number): this; + setAngle(angle: number): this; + + width: number; + height: number; + setWidth( + width: number, + keepAspectRatio?: boolean + ): this; + setHeight( + height: number, + keepAspectRatio?: boolean + ): this; + + scaleX: number; + scaleY: number; + setScaleX(scaleX: number): this; + setScaleY(scaleY: number): this; + setScale(scaleX: number, scaleY: number): this; + + displayWidth: number; + displayHeight: number; + setDisplayWidth( + width: number, + keepAspectRatio?: boolean + ): this; + setDisplayHeight( + height: number, + keepAspectRatio?: boolean + ): this; + + originX: number; + originY: number; + setOriginX(originX: number): this; + setOriginY(originY: number): this; + setOrigin(originX: number, originY: number): this; + + depth: number; + setDepth(depth?: number): this; + + modifyPorperties( + o?: RenderBase.IModifyConfig + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/RenderBase.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/RenderBase.js new file mode 100644 index 000000000..63c40b151 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/RenderBase.js @@ -0,0 +1,319 @@ +import Base from './Base.js'; + +const DegToRad = Phaser.Math.DegToRad; +const RadToDeg = Phaser.Math.RadToDeg; +const GetValue = Phaser.Utils.Objects.GetValue; + +class RenderBase extends Base { + + get visible() { + return this._visible; + } + + set visible(value) { + this.setDisplayListDirty(this._visible != value); + this._visible = value; + } + + setVisible(visible) { + if (visible === undefined) { + visible = true; + } + + this.visible = visible; + return this; + } + + get alpha() { + return this._alpha; + } + + set alpha(value) { + this.setDisplayListDirty(!!this._alpha !== !!value); + this._alpha = value; + } + + setAlpha(alpha) { + this.alpha = alpha; + return this; + } + + setX(x) { + this.x = x; + return this; + } + + setY(y) { + this.y = y; + return this; + } + + setPosition(x, y) { + this.x = x; + this.y = y; + return this; + } + + setRotation(rotation) { + this.rotation = rotation; + return this; + } + + get angle() { + return RadToDeg(this.rotation); + } + + set angle(value) { + this.rotation = DegToRad(value); + } + + setAngle(angle) { + this.angle = angle; + return this; + } + + setScaleX(scaleX) { + this.scaleX = scaleX; + return this; + } + + get width() { + return this._width; + } + + set width(value) { + this._width = value; + } + + setWidth(width, keepAspectRatio) { + if (keepAspectRatio === undefined) { + keepAspectRatio = false; + } + this.width = width; + + if (keepAspectRatio) { + this.scaleY = this.scaleX; + } + return this; + } + + setScaleY(scaleY) { + this.scaleY = scaleY; + return this; + } + + setScale(scaleX, scaleY) { + if (scaleY === undefined) { + scaleY = scaleX; + } + this.scaleX = scaleX; + this.scaleY = scaleY; + return this; + } + + get height() { + return this._height; + } + + set height(value) { + this._height = value; + } + + setHeight(height, keepAspectRatio) { + if (keepAspectRatio === undefined) { + keepAspectRatio = false; + } + this.height = height; + + if (keepAspectRatio) { + this.scaleX = this.scaleY; + } + return this; + } + + setScale(scaleX, scaleY) { + if (scaleY === undefined) { + scaleY = scaleX; + } + + this.scaleX = scaleX; + this.scaleY = scaleY; + return this; + } + + get displayWidth() { + return this._width * this.scaleX; + } + + set displayWidth(value) { + this.scaleX = value / this._width; + } + + setDisplayWidth(width, keepAspectRatio) { + if (keepAspectRatio === undefined) { + keepAspectRatio = false; + } + + this.displayWidth = width; + + if (keepAspectRatio) { + this.scaleY = this.scaleX; + } + return this; + } + + get displayHeight() { + return this._height * this.scaleY; + } + + set displayHeight(value) { + this.scaleY = value / this._height; + } + + setDisplayHeight(height, keepAspectRatio) { + if (keepAspectRatio === undefined) { + keepAspectRatio = false; + } + + this.displayHeight = height; + + if (keepAspectRatio) { + this.scaleX = this.scaleY; + } + return this; + } + + setOriginX(originX) { + this.originX = originX; + this._displayOriginX = this.width * originX; + return this; + } + + setOriginY(originY) { + this.originY = originY; + this._displayOriginY = this.height * originY; + return this; + } + + setOrigin(originX, originY) { + if (originY === undefined) { + originY = originX; + } + this.setOriginX(originX).setOriginY(originY); + return this; + } + + get depth() { + return this._depth; + } + + set depth(value) { + this.setDisplayListDirty(this._depth != value); + this._depth = value; + } + + setDepth(depth) { + if (depth === undefined) { + depth = 0; + } + + this.depth = depth; + return this; + } + + modifyPorperties(o) { + if (!o) { + return this; + } + + if (o.hasOwnProperty('x')) { + this.setX(o.x); + } + if (o.hasOwnProperty('y')) { + this.setY(o.y); + } + + if (o.hasOwnProperty('rotation')) { + this.setRotation(o.rotation); + } else if (o.hasOwnProperty('angle')) { + this.setAngle(o.angle); + } + + if (o.hasOwnProperty('alpha')) { + this.setAlpha(o.alpha); + } + + // ScaleX, ScaleY + var width = GetValue(o, 'width', undefined); + var height = GetValue(o, 'height', undefined); + var scale = GetValue(o, 'scale', undefined); + var scaleX = GetValue(o, 'scaleX', scale); + var scaleY = GetValue(o, 'scaleY', scale); + + if (width !== undefined) { + if ((height === undefined) && (scaleY === undefined)) { + this.setWidth(width, true); + } else { + this.setWidth(width); + } + } else if (scaleX !== undefined) { + this.setScaleX(scaleX); + } else if (o.hasOwnProperty('displayWidth')) { + this.setDisplayWidth(o.displayWidth); + } + + if (height !== undefined) { + if ((width === undefined) && (scaleX === undefined)) { + this.setHeight(height, true); + } else { + this.setHeight(height); + } + } else if (scaleY !== undefined) { + this.setScaleY(scaleY); + } else if (o.hasOwnProperty('displayHeight')) { + this.setDisplayHeight(o.displayHeight); + } + + var origin = GetValue(o, 'origin', undefined); + if (origin !== undefined) { + this.setOrigin(origin); + } else { + if (o.hasOwnProperty('originX')) { + this.setOriginX(o.originX); + } + if (o.hasOwnProperty('originY')) { + this.setOriginY(o.originY); + } + } + + if (o.hasOwnProperty('depth')) { + this.setDepth(o.depth); + } + + return this; + } + + reset() { + super.reset(); + + this + .setVisible() + .setAlpha(1) + .setPosition(0, 0) + .setRotation(0) + .setScale(1, 1) + .setOrigin(0) + .setDepth(0) + + return this; + } + + // Override + webglRender(pipeline, calcMatrix, alpha, dx, dy, texture, textureUnit, roundPixels) { + } + // Override + canvasRender(ctx, dx, dy, roundPixels) { + } +} + +export default RenderBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/Types.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/Types.js new file mode 100644 index 000000000..5b1111d9f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/Types.js @@ -0,0 +1,5 @@ +const ImageTypeName = 'image'; + +export { + ImageTypeName, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/CanvasRender.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/CanvasRender.js new file mode 100644 index 000000000..ef740db9c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/CanvasRender.js @@ -0,0 +1,45 @@ +var CanvasRender = function (ctx, dx, dy, roundPixels) { + + ctx.save(); + + var width = this._width, + height = this._height; + var displayOriginX = width * this.originX, + displayOriginY = height * this.originY; + var x = this.x - displayOriginX, + y = this.y - displayOriginY; + + var flipX = 1; + var flipY = 1; + + if (this.flipX) { + x += width; + flipX = -1; + } + if (this.flipY) { + y += height; + flipY = -1; + } + + if (roundPixels) { + x = Math.round(x); + y = Math.round(y); + } + + ctx.translate(x, y); + + ctx.rotate(this.rotation); + + ctx.scale(this.scaleX * flipX, this.scaleY * flipY); + + var frame = this.frame; + ctx.drawImage( + frame.source.image, + frame.cutX, frame.cutY, width, height, + 0, 0, width, height, + ); + + ctx.restore(); + +} +export default CanvasRender; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/ImageData.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/ImageData.d.ts new file mode 100644 index 000000000..722c8751d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/ImageData.d.ts @@ -0,0 +1,46 @@ +import RenderBase from '../RenderBase'; + +export default ImageData; + +declare namespace ImageData { + interface IFrame { + width: number, + height: number, + u0: number, + v0: number, + u1: number, + v1: number, + } + + interface IModifyConfig extends RenderBase.IModifyConfig { + frame?: string | IFrame, + + flipX?: boolean, flipY?: boolean, + + tint?: number, tintFill?: number + } +} + +declare class ImageData extends RenderBase { + setFrame( + frame?: string | ImageData.IFrame + ): this; + + + flipX: boolean; + flipY: boolean; + setFlipX(flipX?: boolean): this; + setFlipY(flipY?: boolean): this; + resetFlip(): this; + + tint: number; + tintFill: boolean; + setTint(value: number): this; + setTintFill(value: number): this; + clearTint(): this; + resetTint(): this; + + modifyPorperties( + o?: ImageData.IModifyConfig + ): this; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/ImageData.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/ImageData.js new file mode 100644 index 000000000..61222729a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/ImageData.js @@ -0,0 +1,172 @@ +import RenderBase from '../RenderBase.js'; +import { ImageTypeName } from '../Types.js'; +import WebglRender from './WebglRender.js'; +import CanvasRender from './CanvasRender.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +class ImageData extends RenderBase { + constructor(parent, frame) { + super(parent, ImageTypeName); + + this.setFrame(frame); + } + + get width() { + return this._width; + } + + set width(value) { + } + + get height() { + return this._height; + } + + set height(value) { + } + + setFrame(frame) { + if (arguments.length > 0 && !IsPlainObject(frame)) { + frame = this.parent.texture.get(frame); + } + this.frame = frame; + this._width = (frame) ? frame.width : 0; + this._height = (frame) ? frame.height : 0; + return this; + } + + setFlipX(flipX) { + if (flipX === undefined) { + flipX = true; + } + this.flipX = flipX; + return this; + } + + setFlipY(flipY) { + if (flipY === undefined) { + flipY = true; + } + this.flipY = flipY; + return this; + } + + resetFlip() { + this.flipX = false; + this.flipY = false; + return this; + } + + get tint() { + if (this._tint === undefined) { + return this.parent.tint; + } else { + return this._tint; + } + } + + set tint(value) { + this._tint = value; + } + + + setTint(value) { + this.tint = value; + this.tintFill = false; + return this; + } + + setTintFill(value) { + this.tint = value; + this.tintFill = true; + return this; + } + + clearTint() { + this.setTint(0xffffff); + return this; + } + + resetTint() { + this.tint = undefined; + this.tintFill = undefined; + return this; + } + + get tintFill() { + if (this._tintFill === undefined) { + return this.parent.tintFill; + } else { + return this._tintFill; + } + } + + set tintFill(value) { + this._tintFill = value; + } + + reset() { + super.reset(); + + this + .resetFlip() + .resetTint() + .setFrame(); + + return this; + } + + modifyPorperties(o) { + if (!o) { + return this; + } + + // Size of Image is equal to frame size, + // Move width, height properties to displayWidth,displayHeight + if (o.hasOwnProperty('width')) { + o.displayWidth = o.width; + delete o.width; + } + if (o.hasOwnProperty('height')) { + o.displayHeight = o.height; + delete o.height; + } + + if (o.hasOwnProperty('frame')) { + this.setFrame(o.frame); + } + + super.modifyPorperties(o); + + if (o.hasOwnProperty('flipX')) { + this.setFlipX(o.flipX); + } + if (o.hasOwnProperty('flipY')) { + this.setFlipY(o.flipY); + } + + if (o.hasOwnProperty('tint')) { + this.setTint(o.tint); + } + + if (o.hasOwnProperty('tintFill')) { + this.setTintFill(o.tintFill); + } + + return this; + } + +} + +var methods = { + webglRender: WebglRender, + canvasRender: CanvasRender, +} + +Object.assign( + ImageData.prototype, + methods +); + +export default ImageData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/WebglRender.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/WebglRender.js new file mode 100644 index 000000000..ccccc9c6a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/bob/image/WebglRender.js @@ -0,0 +1,55 @@ +const TransformMatrix = Phaser.GameObjects.Components.TransformMatrix; +const GetTint = Phaser.Renderer.WebGL.Utils.getTintAppendFloatAlpha; + +var FrameMatrix = new TransformMatrix(); + +var WebglRender = function (pipeline, calcMatrix, alpha, dx, dy, texture, textureUnit, roundPixels) { + var width = this._width, + height = this._height; + var displayOriginX = width * this.originX, + displayOriginY = height * this.originY; + var x = this.x - dx, + y = this.y - dy; + + var flipX = 1; + var flipY = 1; + + if (this.flipX) { + x += width - (displayOriginX * 2); + flipX = -1; + } + if (this.flipY) { + y += height - (displayOriginY * 2); + flipY = -1; + } + + FrameMatrix.applyITRS(x, y, this.rotation, this.scaleX * flipX, this.scaleY * flipY); + calcMatrix.multiply(FrameMatrix, FrameMatrix); + + var tx = -displayOriginX; + var ty = -displayOriginY; + var tw = tx + width; + var th = ty + height; + + var quad = FrameMatrix.setQuad(tx, ty, tw, th, roundPixels); + + var u0 = this.frame.u0; + var v0 = this.frame.v0; + var u1 = this.frame.u1; + var v1 = this.frame.v1; + + var tint = GetTint(this.tint, this.alpha * alpha); + + pipeline.batchQuad( + this.parent, + quad[0], quad[1], quad[2], quad[3], quad[4], quad[5], quad[6], quad[7], + u0, v0, + u1, v1, + tint, tint, tint, tint, + this.tintFill, + texture, + textureUnit + ); +} + +export default WebglRender; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/AddChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/AddChild.js new file mode 100644 index 000000000..482525bba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/AddChild.js @@ -0,0 +1,15 @@ +var AddChild = function (bob) { + this.lastAppendedChildren.length = 0; + + if (Array.isArray(bob)) { + this.children.add(bob) + this.lastAppendedChildren.push(...bob); + } else { + this.children.add(bob); + this.lastAppendedChildren.push(bob); + } + + return this; +} + +export default AddChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/GetChildren.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/GetChildren.js new file mode 100644 index 000000000..169eaa94f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/GetChildren.js @@ -0,0 +1,5 @@ +var GetChildren = function () { + return this.children.list; +} + +export default GetChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/GetLastAppendedChildren.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/GetLastAppendedChildren.js new file mode 100644 index 000000000..d09335ca2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/GetLastAppendedChildren.js @@ -0,0 +1,5 @@ +var GetLastAppendedChildren = function () { + return this.lastAppendedChildren; +} + +export default GetLastAppendedChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/Methods.js new file mode 100644 index 000000000..d793706a4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/Methods.js @@ -0,0 +1,27 @@ +import SetTexture from './SetTexture.js'; +import Resize from './Resize.js'; +import AddChild from './AddChild.js'; +import RemoveChild from './RemoveChild.js'; +import RemoveChildren from './RemoveChildren.js'; +import GetLastAppendedChildren from './GetLastAppendedChildren.js'; +import GetChildren from './GetChildren.js'; +import TintMethods from './TintMethods.js'; + +var methods = { + setTexture: SetTexture, + resize: Resize, + setSize: Resize, + addChild: AddChild, + removeChild: RemoveChild, + removeChildren: RemoveChildren, + clear: RemoveChildren, + getLastAppendedChildren: GetLastAppendedChildren, + getChildren: GetChildren, +} + +Object.assign( + methods, + TintMethods +) + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/PopReusedBob.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/PopReusedBob.js new file mode 100644 index 000000000..6bd1864c5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/PopReusedBob.js @@ -0,0 +1,6 @@ +var PopReusedBob = function (typeName) { + var bob = (this.poolManager) ? this.poolManager.allocate(typeName) : null; + return bob; +} + +export default PopReusedBob; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/RemoveChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/RemoveChild.js new file mode 100644 index 000000000..b1d4a86c6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/RemoveChild.js @@ -0,0 +1,16 @@ +const RemoveItem = Phaser.Utils.Array.Remove; + +var RemoveChild = function (bob) { + if (this.poolManager) { + // Free this bob (bob.onFree()) + this.poolManager.free(bob); + } + + // Remove this bob from blitter + RemoveItem(this.children.list, bob); + this.lastAppendedChildren.length = 0; + this.dirty = true; + return this; +} + +export default RemoveChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/RemoveChildren.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/RemoveChildren.js new file mode 100644 index 000000000..5b55c6bff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/RemoveChildren.js @@ -0,0 +1,14 @@ +var RemoveChildren = function () { + if (this.poolManager) { + // Free all bobs (bob.onFree()) + this.poolManager.freeMultiple(this.children.list); + } + + // Remove all bobs from blitter + this.children.list.length = 0; + this.lastAppendedChildren.length = 0; + this.dirty = true; + return this; +} + +export default RemoveChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/Resize.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/Resize.js new file mode 100644 index 000000000..b64482063 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/Resize.js @@ -0,0 +1,21 @@ +var Resize = function (width, height) { + if ((this.width === width) && (this.height === height)) { + return this; + } + + this.width = width; + this.height = height; + + this.updateDisplayOrigin(); + + var input = this.input; + + if (input && !input.customHitArea) { + input.hitArea.width = width; + input.hitArea.height = height; + } + + return this; +} + +export default Resize; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/SetTexture.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/SetTexture.js new file mode 100644 index 000000000..317c3f3f0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/SetTexture.js @@ -0,0 +1,7 @@ +var SetTexture = function (key, frame) { + this.texture = this.scene.sys.textures.get(key); + this.frame = this.texture.get(frame); + return this; +} + +export default SetTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/TintMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/TintMethods.js new file mode 100644 index 000000000..811883fbd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/methods/TintMethods.js @@ -0,0 +1,20 @@ +export default { + setTint(tint) { + // 0: Solid tint + texture alpha + this.tint = tint; + this.tintFill = false; + return this; + }, + + setTintFill(tint) { + // 1: Solid tint, no texture + this.tint = tint; + this.tintFill = true; + return this; + }, + + clearTint() { + this.setTint(0xffffff); + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/poolmanager/PoolManager.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/poolmanager/PoolManager.js new file mode 100644 index 000000000..6c3c5f647 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/poolmanager/PoolManager.js @@ -0,0 +1,48 @@ +import Pool from '../../../../pool.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var Pools = {}; +class PoolManager { + constructor(config) { + this.pools = GetValue(config, 'pools', Pools); + } + + destroy() { + this.pools = undefined; + } + + free(bob) { + if (!this.pools) { + return this; + } + + var bobType = bob.type; + if (!this.pools.hasOwnProperty(bobType)) { + this.pools[bobType] = new Pool(); + } + this.pools[bobType].push(bob); + bob.onFree(); + return this; + } + + freeMultiple(bobs) { + if (!this.pools) { + return this; + } + + for (var i = 0, cnt = bobs.length; i < cnt; i++) { + this.free(bobs[i]); + } + return this; + } + + allocate(bobType) { + if (!this.pools || !this.pools.hasOwnProperty(bobType)) { + return null; + } + return this.pools[bobType].pop(); + } +} + +export default PoolManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/render/CanvasRenderer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/render/CanvasRenderer.js new file mode 100644 index 000000000..19294f3af --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/render/CanvasRenderer.js @@ -0,0 +1,28 @@ +const SetTransform = Phaser.Renderer.Canvas.SetTransform; + +var CanvasRenderer = function (renderer, src, camera, parentMatrix) { + var ctx = renderer.currentContext; + + var bobs = src.getRenderList(); + if ((bobs.length === 0) || (!SetTransform(renderer, ctx, src, camera, parentMatrix))) { + return; + } + + camera.addToRenderList(src); + + var roundPixels = camera.roundPixels; + + var dx = -src._displayOriginX, + dy = -src._displayOriginY; + + ctx.translate(dx, dy); + + for (var i = 0, cnt = bobs.length; i < cnt; i++) { + bobs[i].canvasRender(ctx, dx, dy, roundPixels); + } + + // Restore the context saved in SetTransform + ctx.restore(); +}; + +export default CanvasRenderer; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/render/Render.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/render/Render.js new file mode 100644 index 000000000..404fc7750 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/render/Render.js @@ -0,0 +1,8 @@ +import WebGLRenderer from './WebGLRenderer.js'; +import CanvasRenderer from './CanvasRenderer.js'; + +export default { + renderWebGL: WebGLRenderer, + renderCanvas: CanvasRenderer + +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/render/WebGLRenderer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/render/WebGLRenderer.js new file mode 100644 index 000000000..5bad4fe94 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/render/WebGLRenderer.js @@ -0,0 +1,37 @@ +const GetCalcMatrix = Phaser.GameObjects.GetCalcMatrix; + +var WebGLRenderer = function (renderer, src, camera, parentMatrix) { + var bobs = src.getRenderList(); + if (bobs.length === 0) { + return; + } + + camera.addToRenderList(src); + + var pipeline = renderer.pipelines.set(src.pipeline); + + var texture = src.frame.glTexture; + + var textureUnit = pipeline.setGameObject(src); + + var roundPixels = camera.roundPixels; + + var result = GetCalcMatrix(src, camera, parentMatrix); + + var calcMatrix = pipeline.calcMatrix.copyFrom(result.calc); + + var dx = src._displayOriginX; + var dy = src._displayOriginY; + + var alpha = camera.alpha * src.alpha; + + renderer.pipelines.preBatch(src); + + for (var i = 0, cnt = bobs.length; i < cnt; i++) { + bobs[i].webglRender(pipeline, calcMatrix, alpha, dx, dy, texture, textureUnit, roundPixels); + } + + renderer.pipelines.postBatch(src); +}; + +export default WebGLRenderer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/utils/AddImage.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/utils/AddImage.js new file mode 100644 index 000000000..e7d5e6846 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/blitterbase/utils/AddImage.js @@ -0,0 +1,24 @@ +import { ImageTypeName } from '../../blitterbase/bob/Types.js'; +import ImageData from '../../blitterbase/bob/image/ImageData.js'; + +var AddImage = function (blitter, config) { + if (typeof (config) === 'string') { + config = { + frame: config + } + } + + var bob = (blitter.poolManager) ? blitter.poolManager.allocate(ImageTypeName) : null; + if (bob === null) { + bob = new ImageData(blitter); + } else { + bob.setParent(blitter).setActive(); + } + bob.modifyPorperties(config); + + blitter.addChild(bob); + + return bob; +} + +export default AddImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/Creator.js new file mode 100644 index 000000000..515ea877b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/Creator.js @@ -0,0 +1,13 @@ +import NinePatch from './NinePatch.js'; + +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var gameObject = new NinePatch(this.scene, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/Factory.js new file mode 100644 index 000000000..aaa324623 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/Factory.js @@ -0,0 +1,7 @@ +import NinePatch from './NinePatch.js'; + +export default function (x, y, width, height, key, baseFrame, columns, rows, config) { + var gameObject = new NinePatch(this.scene, x, y, width, height, key, baseFrame, columns, rows, config); + this.scene.add.existing(gameObject); + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/Methods.js new file mode 100644 index 000000000..feab6cee2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/Methods.js @@ -0,0 +1,7 @@ +import DrawImage from './texture/DrawImage.js'; +import DrawTileSprite from './texture/DrawTileSprite.js'; + +export default { + _drawImage: DrawImage, + _drawTileSprite: DrawTileSprite, +}; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/NinePatch.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/NinePatch.d.ts new file mode 100644 index 000000000..f5e46eb07 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/NinePatch.d.ts @@ -0,0 +1,115 @@ +// import * as Phaser from 'phaser'; +import Blitter from '../blitter/Blitter'; + +export default NinePatch; + +declare namespace NinePatch { + + interface IConfig { + x?: number, y?: number, + width?: number, height?: number, + + key?: string, baseFrame?: string, + getFrameNameCallback?: (colIndex: number, rowIndex: number, baseFrame: string) => (string | undefined), + + columns?: (number | undefined)[], + rows?: (number | undefined)[], + + stretchMode?: 0 | 1 | 'scale' | 'repeat' | + { + edge?: 0 | 1 | 'scale' | 'repeat', + internal?: 0 | 1 | 'scale' | 'repeat', + }, + + maxFixedPartScale?: number, + maxFixedPartScaleX?: number, + maxFixedPartScaleY?: number, + + preserveRatio?: boolean, + } + +} + +declare class NinePatch extends Blitter { + constructor( + scene: Phaser.Scene, + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + key: string, + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + key: string, + columns: (number | undefined)[], rows: (number | undefined)[], + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + key: string, baseFrame: string, + columns: (number | undefined)[], rows: (number | undefined)[], + config?: NinePatch.IConfig + ) + + resize(width: number, height: number): this; + + setBaseTexture( + key: string, + baseFrame: string | undefined, + columns: (number | undefined)[], + rows: (number | undefined)[] + ): this; + + setStretchMode( + mode: 0 | 1 | 'scale' | 'repeat' | + { + edge?: 0 | 1 | 'scale' | 'repeat', + internal?: 0 | 1 | 'scale' | 'repeat', + } + ): this; + + setGetFrameNameCallback( + callback: (colIndex: number, rowIndex: number, baseFrame: string) => (string | undefined) + ): this; + + updateTexture(): this; + + setPreserveRatio(enable?: boolean): this; + preserveRatio: boolean; + + setMaxFixedPartScale(scaleX: number, scaleY?: number): this; + maxFixedPartScaleX: number; + maxFixedPartScaleY: number; + + readonly minWidth: number; + + readonly minHeight: number; + + readonly fixedPartScaleX: number; + + readonly fixedPartScaleY: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/NinePatch.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/NinePatch.js new file mode 100644 index 000000000..5be3ea4cb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/NinePatch.js @@ -0,0 +1,18 @@ +import Blitter from '../blitterbase/BlitterBase.js' +import NinePatchBase from '../../../utils/ninepatch/NinePatch.js'; +import Methods from './Methods.js'; + +class NinePatch extends NinePatchBase(Blitter, 'rexNinePatch2') { + setBaseTexture(key, baseFrameName, columns, rows) { + this.setTexture(key, baseFrameName); + super.setBaseTexture(key, baseFrameName, columns, rows); + return this; + } +} + +Object.assign( + NinePatch.prototype, + Methods +); + +export default NinePatch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/texture/DrawImage.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/texture/DrawImage.js new file mode 100644 index 000000000..c011a417c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/texture/DrawImage.js @@ -0,0 +1,13 @@ +import AddImage from '../../blitterbase/utils/AddImage.js'; + +var DrawImage = function (key, frame, x, y, width, height) { + AddImage(this, { + frame: frame, + x: x, + y: y, + width: width, + height: height + }) +} + +export default DrawImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/texture/DrawTileSprite.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/texture/DrawTileSprite.js new file mode 100644 index 000000000..9bb9094a0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/blitter/ninepatch/texture/DrawTileSprite.js @@ -0,0 +1,24 @@ +import AddImage from '../../blitterbase/utils/AddImage.js'; + +var DrawTileSprite = function (key, frame, x, y, width, height) { + var frameObj = this.texture.get(frame); + var frameWidth = frameObj.width, + frameHeight = frameObj.height; + var colCount = Math.floor(width / frameWidth), + rowCount = Math.floor(height / frameHeight); + // Align images at center + x += (width - (colCount * frameWidth)) / 2; + y += (height - (rowCount * frameHeight)) / 2; + for (var colIndex = 0; colIndex < colCount; colIndex++) { + for (var rowIndex = 0; rowIndex < rowCount; rowIndex++) { + AddImage(this, { + frame: frame, + x: x + (colIndex * frameWidth), + y: y + (rowIndex * frameHeight), + }) + } + } + +} + +export default DrawTileSprite; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/AlphaMaskImage.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/AlphaMaskImage.d.ts new file mode 100644 index 000000000..bf49ef8c4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/AlphaMaskImage.d.ts @@ -0,0 +1,31 @@ +import Canvas from '../canvas/Canvas'; + +export default AlphaMaskImage; + +declare namespace AlphaMaskImage { + + interface IConfig { + mask: { + key: string, + frame?: string, + invertAlpha?: boolean, + scale?: number, + }, + + backgroundColor?: string, + } +} + +declare class AlphaMaskImage extends Canvas { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + key?: string, frame?: string, + config?: AlphaMaskImage.IConfig + ); + + setTexture( + key?: string, frame?: string, + config?: AlphaMaskImage.IConfig + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/AlphaMaskImage.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/AlphaMaskImage.js new file mode 100644 index 000000000..475bf20bf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/AlphaMaskImage.js @@ -0,0 +1,102 @@ +import Canvas from '../canvasbase/Canvas.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class AlphaMaskImage extends Canvas { + constructor(scene, x, y, key, frame, config) { + super(scene, x, y); + + this.type = 'rexAlphaMaskImage'; + this.maskFrame = null; + this.setTexture(key, frame, config); + } + + setTexture(key, frame, config) { + if (typeof (frame) === 'object') { + config = frame; + frame = undefined; + } + + if (typeof (config) === 'string') { + config = { + mask: { + key: config + } + } + } + + var maskKey = GetValue(config, 'mask.key'); + var maskFrame = GetValue(config, 'mask.frame'); + var invertMaskAlpha = GetValue(config, 'mask.invertAlpha', false); + var maskScale = GetValue(config, 'mask.scale'); + var backgroundColor = GetValue(config, 'backgroundColor'); + + if (maskKey) { + this._maskKey = maskKey; + this._maskFrame = maskFrame; + this._maskScale = maskScale; + + var texture = (maskKey) ? this.scene.sys.textures.get(maskKey) : null; + this.maskFrame = (texture) ? texture.get(maskFrame) : null; + } + + this._textureKey = key; + this._frameName = frame; + + var maskTextureFrame = this.maskFrame; + if (maskTextureFrame === null) { + this.loadTexture(key, frame); + this.dirty = true; + return this; + } + + var hasBackgroundColor = (backgroundColor != null); + this.loadTexture(key, frame); + + // Draw mask + var canvas = this.canvas, + ctx = this.context; + var width = canvas.width, + height = canvas.height; + + ctx.save(); + ctx.globalCompositeOperation = (invertMaskAlpha) ? 'destination-out' : 'destination-in'; + + var maskWidth, maskHeight; + if (this._maskScale != null) { + maskWidth = maskTextureFrame.cutWidth * this._maskScale; + maskHeight = maskTextureFrame.cutHeight * this._maskScale; + } else { + maskWidth = width; + maskHeight = height; + } + var maskX = (width - maskWidth) / 2; + var maskY = (height - maskHeight) / 2; + + this.drawFrame( + this._maskKey, this._maskFrame, + maskX, maskY, maskWidth, maskHeight + ); + + ctx.restore(); + + if (hasBackgroundColor) { + ctx.save(); + ctx.globalCompositeOperation = 'destination-over'; + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, width, height); + ctx.restore(); + } + + this.dirty = true; + return this; + } + + resize(width, height) { + // Don't draw content again. + this.setDisplaySize(width, height); + return this; + } +} + +export default AlphaMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/Creator.js new file mode 100644 index 000000000..f6dfcfc79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/Creator.js @@ -0,0 +1,16 @@ +import AlphaMaskImage from './AlphaMaskImage.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var key = GetAdvancedValue(config, 'key', undefined); + var frame = GetAdvancedValue(config, 'frame', undefined); + var gameObject = new AlphaMaskImage(this.scene, 0, 0, key, frame, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/Factory.js new file mode 100644 index 000000000..ed7c507b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/alphamaskimage/Factory.js @@ -0,0 +1,7 @@ +import AlphaMaskImage from './AlphaMaskImage.js'; + +export default function (x, y, key, frame, config) { + var gameObject = new AlphaMaskImage(this.scene, x, y, key, frame, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Canvas.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Canvas.d.ts new file mode 100644 index 000000000..79cf8a554 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Canvas.d.ts @@ -0,0 +1,22 @@ +import CanvasBase from '../canvasbase/Canvas'; + +export default class Canvas extends CanvasBase { + loadFromURL( + url: string, + callback?: () => void + ): this; + + loadFromURLPromise( + url: string + ): Promise; + + loadFromFile( + file: File, + callback?: () => void + ): this; + + loadFromFilePromise( + file: File + ): Promise; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Canvas.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Canvas.js new file mode 100644 index 000000000..ed1254996 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Canvas.js @@ -0,0 +1,13 @@ +import CanvasBase from '../canvasbase/Canvas.js'; +import LoadImageMethods from './LoadImageMethods.js'; + +class Canvas extends CanvasBase { + +} + +Object.assign( + Canvas.prototype, + LoadImageMethods, +) + +export default Canvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Creator.js new file mode 100644 index 000000000..9088365ba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Creator.js @@ -0,0 +1,18 @@ +import Canvas from './Canvas.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', 256); + var height = GetAdvancedValue(config, 'height', width); + var gameObject = new Canvas(this.scene, 0, 0, width, height); + BuildGameObject(this.scene, gameObject, config); + var fillColor = GetAdvancedValue(config, 'fill', null); + gameObject.fill(fillColor); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Factory.js new file mode 100644 index 000000000..7493058f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/Factory.js @@ -0,0 +1,7 @@ +import Canvas from './Canvas.js'; + +export default function (x, y, width, height) { + var gameObject = new Canvas(this.scene, x, y, width, height); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/LoadImageMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/LoadImageMethods.js new file mode 100644 index 000000000..9c0e58f1e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvas/LoadImageMethods.js @@ -0,0 +1,51 @@ +export default { + loadFromURL(url, callback) { + var self = this; + var img = new Image(); + img.onload = function () { + if ((self.width !== img.width) || (self.height !== img.height)) { + self.resize(img.width, img.height); + } else { + self.clear(); + } + self.context.drawImage(img, 0, 0); + self.updateTexture(); + + if (callback) { + callback(); + } + + img.onload = null; + img.src = ''; + img.remove(); + } + img.src = url; + return this; + }, + + loadFromURLPromise(url) { + var self = this; + return new Promise(function (resolve, reject) { + self.loadFromURL(url, resolve); + }); + }, + + loadFromFile(file, callback) { + var url = URL.createObjectURL(file); + this.loadFromURL(url, function () { + URL.revokeObjectURL(url); + if (callback) { + callback(); + } + }) + + return this; + }, + + loadFromFilePromise(file) { + var self = this; + return new Promise(function (resolve, reject) { + self.loadFromFile(file, resolve); + }); + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/Canvas.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/Canvas.d.ts new file mode 100644 index 000000000..f28ad906c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/Canvas.d.ts @@ -0,0 +1,72 @@ +// import * as Phaser from 'phaser'; +import CanvasGameObjectBase from '../../../utils/types/CanvasGameObjectBase'; + +export default class Canvas extends CanvasGameObjectBase { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number + ); + + setSize(width: number, height: number): this; + resize(width: number, height: number): this; + setCanvasSize(width: number, height: number): this; + + getCanvas(readOnly?: boolean): HTMLCanvasElement; + canvas: HTMLCanvasElement; + getContext(readOnly?: boolean): CanvasRenderingContext2D; + context: CanvasRenderingContext2D; + + needRedraw(): this; + dirty: boolean; + + clear(): this; + fill(color: string): this; + + updateTexture( + callback?: (canvasElem: HTMLCanvasElement, context: CanvasRenderingContext2D) => void, + scope?: object + ): this; + + generateTexture( + key: string | number, + x?: number, y?: number, + width?: number, height?: number + ): this; + + loadTexture( + key: string, + frame?: string, + ): this; + + drawFrame( + key: string, + frame?: string, + dx?: number, + dy?: number, + dWidth?: number, + dHeight?: number, + sxOffset?: number, + syOffset?: number, + sWidth?: number, + sHeight?: number, + ): this; + + getDataURL( + type?: string, + encoderOptions?: number + ): string; + + getPixel( + x: number, y: number + ): Phaser.Display.Color; + + setPixel( + x: number, y: number, + r: number | Phaser.Display.Color, + g?: number, + b?: number, + a?: number + ): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/Canvas.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/Canvas.js new file mode 100644 index 000000000..06814bbcf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/Canvas.js @@ -0,0 +1,181 @@ +import Render from './render/Render.js'; +import CanvasMethods from './CanvasMethods.js'; +import TextureMethods from './TextureMethods.js'; + +const CanvasPool = Phaser.Display.Canvas.CanvasPool; +const GameObject = Phaser.GameObjects.GameObject; + +class Canvas extends GameObject { + constructor(scene, x, y, width, height) { + if (x === undefined) { + x = 0; + } + if (y === undefined) { + y = 0; + } + if (width === undefined) { + width = 1; + } + if (height === undefined) { + height = 1; + } + + super(scene, 'rexCanvas'); + + this.renderer = scene.sys.game.renderer; + + this.resolution = 1; + this._width = width; + this._height = height; + width = Math.max(Math.ceil(width * this.resolution), 1); + height = Math.max(Math.ceil(height * this.resolution), 1); + this.canvas = CanvasPool.create(this, width, height); + this.context = this.canvas.getContext('2d', { willReadFrequently: true }); + this.dirty = false; + + this.setPosition(x, y); + this.setOrigin(0.5, 0.5); + this.initPipeline(); + + this._crop = this.resetCropObject(); + + // Create a Texture for this Text object + this.texture = scene.sys.textures.addCanvas(null, this.canvas, true); + + // Get the frame + this.frame = this.texture.get(); + + // Set the resolution + this.frame.source.resolution = this.resolution; + + if (this.renderer && this.renderer.gl) { + // Clear the default 1x1 glTexture, as we override it later + this.renderer.deleteTexture(this.frame.source.glTexture); + this.frame.source.glTexture = null; + } + + this.dirty = true; + } + + preDestroy() { + CanvasPool.remove(this.canvas); + this.texture.destroy(); + + this.canvas = null; + this.context = null; + } + + get width() { + return this._width; + } + + set width(value) { + this.setSize(value, this._height); + } + + get height() { + return this._height; + } + + set height(value) { + this.setSize(this._width, value); + } + + setCanvasSize(width, height) { + if ((this._width === width) && (this._height === height)) { + return this; + } + + this._width = width; + this._height = height; + + this.updateDisplayOrigin(); + + width = Math.max(Math.ceil(width * this.resolution), 1); + height = Math.max(Math.ceil(height * this.resolution), 1); + this.canvas.width = width; + this.canvas.height = height; + + this.frame.setSize(width, height); + + this.dirty = true; + return this; + } + + // setSize might be override + setSize(width, height) { + this.setCanvasSize(width, height); + return this; + } + + get displayWidth() { + return this.scaleX * this._width; + } + + set displayWidth(value) { + this.scaleX = value / this._width; + } + + get displayHeight() { + return this.scaleY * this._height; + } + + set displayHeight(value) { + this.scaleY = value / this._height; + } + + setDisplaySize(width, height) { + this.displayWidth = width; + this.displayHeight = height; + return this; + } + + getCanvas(readOnly) { + if (!readOnly) { + this.dirty = true; + } + return this.canvas; + } + + getContext(readOnly) { + if (!readOnly) { + this.dirty = true; + } + return this.context; + } + + needRedraw() { + this.dirty = true; + return this; + } + + resize(width, height) { + this.setSize(width, height); + return this; + } +} + +const Components = Phaser.GameObjects.Components; +Phaser.Class.mixin(Canvas, + [ + Components.Alpha, + Components.BlendMode, + Components.Crop, + Components.Depth, + Components.Flip, + Components.GetBounds, + Components.Mask, + Components.Origin, + Components.Pipeline, + Components.PostPipeline, + Components.ScrollFactor, + Components.Tint, + Components.Transform, + Components.Visible, + Render, + CanvasMethods, + TextureMethods, + ] +); + +export default Canvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/CanvasMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/CanvasMethods.js new file mode 100644 index 000000000..23ca31efe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/CanvasMethods.js @@ -0,0 +1,85 @@ +const Color = Phaser.Display.Color; + +export default { + clear() { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + this.dirty = true; + return this; + }, + + fill(color) { + this.context.fillStyle = color; + this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + this.dirty = true; + return this; + }, + + drawFrame(key, frame, dx, dy, dWidth, dHeight, sxOffset, syOffset, sWidth, sHeight) { + + var textureFrame = this.scene.sys.textures.getFrame(key, frame); + if (!textureFrame) { + return this; + } + + var frameWidth = textureFrame.cutWidth, + frameHeight = textureFrame.cutHeight; + + if (dx === undefined) { dx = 0; } + if (dy === undefined) { dy = 0; } + if (dWidth === undefined) { dWidth = frameWidth; } + if (dHeight === undefined) { dHeight = frameHeight; } + if (sxOffset === undefined) { sxOffset = 0; } + if (syOffset === undefined) { syOffset = 0; } + if (sWidth === undefined) { sWidth = frameWidth; } + if (sHeight === undefined) { sHeight = frameHeight; } + + var sx = textureFrame.cutX + sxOffset; + var sy = textureFrame.cutY + syOffset; + + this.context.drawImage( + textureFrame.source.image, // image + sx, sy, sWidth, sHeight, + dx, dy, dWidth, dHeight + ); + + this.dirty = true; + + return this; + }, + + getDataURL(type, encoderOptions) { + return this.canvas.toDataURL(type, encoderOptions); + }, + + getPixel(x, y, out) { + if (out === undefined) { + out = new Color(); + } + var rgb = this.context.getImageData(x, y, 1, 1); + out.setTo(rgb.data[0], rgb.data[1], rgb.data[2], rgb.data[3]) + return out; + }, + + setPixel(x, y, r, g, b, a) { + if (typeof (r) !== 'number') { + var color = r; + r = color.red; + g = color.green; + b = color.blue; + a = color.alpha; + } + + if (a === undefined) { + a = ((r !== 0) || (g !== 0) || (b !== 0)) ? 255 : 0; + } + + var imgData = this.context.createImageData(1, 1); + imgData.data[0] = r; + imgData.data[1] = g; + imgData.data[2] = b; + imgData.data[3] = a; + this.context.putImageData(imgData, x, y); + this.dirty = true; + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/TextureMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/TextureMethods.js new file mode 100644 index 000000000..17b787884 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/TextureMethods.js @@ -0,0 +1,65 @@ +import CopyCanvasToTexture from '../../../utils/texture/CopyCanvasToTexture.js'; + +export default { + updateTexture(callback, scope) { + if (callback) { + if (scope) { + callback.call(scope, this.canvas, this.context); + } else { + callback(this.canvas, this.context); + } + } + + if ((this.canvas.width !== this.frame.width) || (this.canvas.height !== this.frame.height)) { + this.frame.setSize(this.canvas.width, this.canvas.height); + } + if (this.renderer && this.renderer.gl) { + this.frame.source.glTexture = this.renderer.canvasToTexture(this.canvas, this.frame.source.glTexture, true); + this.frame.glTexture = this.frame.source.glTexture; + } + this.dirty = false; + + var input = this.input; + if (input && !input.customHitArea) { + input.hitArea.width = this.width; + input.hitArea.height = this.height; + } + return this; + }, + + generateTexture(key, x, y, width, height) { + var srcCanvas = this.canvas; + if (width === undefined) { + width = srcCanvas.width; + } else { + width *= this.resolution; + } + if (height === undefined) { + height = srcCanvas.height; + } else { + height *= this.resolution; + } + + CopyCanvasToTexture(this.scene, srcCanvas, key, x, y, width, height); + + return this; + }, + + loadTexture(key, frame) { + var textureFrame = this.scene.sys.textures.getFrame(key, frame); + if (!textureFrame) { + return this; + } + + if ((this.width !== textureFrame.cutWidth) || (this.height !== textureFrame.cutHeight)) { + this.setSize(textureFrame.cutWidth, textureFrame.cutHeight); + } else { + this.clear(); + } + + this.drawFrame(key, frame); + this.dirty = true; + return this; + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/render/CanvasRenderer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/render/CanvasRenderer.js new file mode 100644 index 000000000..6999ed8d8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/render/CanvasRenderer.js @@ -0,0 +1,18 @@ +// copy from Phaser.GameObjects.Text + +var CanvasRenderer = function (renderer, src, camera, parentMatrix) { + if (src.dirty) { + src.updateTexture(); + src.dirty = false; + } + + if ((src.width === 0) || (src.height === 0)) { + return; + } + + camera.addToRenderList(src); + + renderer.batchSprite(src, src.frame, camera, parentMatrix); +}; + +export default CanvasRenderer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/render/Render.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/render/Render.js new file mode 100644 index 000000000..404fc7750 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/render/Render.js @@ -0,0 +1,8 @@ +import WebGLRenderer from './WebGLRenderer.js'; +import CanvasRenderer from './CanvasRenderer.js'; + +export default { + renderWebGL: WebGLRenderer, + renderCanvas: CanvasRenderer + +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/render/WebGLRenderer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/render/WebGLRenderer.js new file mode 100644 index 000000000..e49f9ca5e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/canvasbase/render/WebGLRenderer.js @@ -0,0 +1,53 @@ +// copy from Phaser.GameObjects.Text + +const Utils = Phaser.Renderer.WebGL.Utils; + +var WebGLRenderer = function (renderer, src, camera, parentMatrix) { + if (src.dirty) { + src.updateTexture(); + src.dirty = false; + } + + if ((src.width === 0) || (src.height === 0)) { + return; + } + + camera.addToRenderList(src); + + var frame = src.frame; + var width = frame.width; + var height = frame.height; + var getTint = Utils.getTintAppendFloatAlpha; + var pipeline = renderer.pipelines.set(src.pipeline, src); + var textureUnit = pipeline.setTexture2D(frame.glTexture, src); + + renderer.pipelines.preBatch(src); + + pipeline.batchTexture( + src, + frame.glTexture, + width, height, + src.x, src.y, + width / src.resolution, height / src.resolution, + src.scaleX, src.scaleY, + src.rotation, + src.flipX, src.flipY, + src.scrollFactorX, src.scrollFactorY, + src.displayOriginX, src.displayOriginY, + 0, 0, width, height, + getTint(src.tintTopLeft, camera.alpha * src._alphaTL), + getTint(src.tintTopRight, camera.alpha * src._alphaTR), + getTint(src.tintBottomLeft, camera.alpha * src._alphaBL), + getTint(src.tintBottomRight, camera.alpha * src._alphaBR), + src.tintFill, + 0, 0, + camera, + parentMatrix, + false, + textureUnit + ); + + renderer.pipelines.postBatch(src); +}; + +export default WebGLRenderer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/CircleMaskImage.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/CircleMaskImage.d.ts new file mode 100644 index 000000000..0598d5dc5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/CircleMaskImage.d.ts @@ -0,0 +1,41 @@ +import Canvas from '../canvas/Canvas'; + +export default CircleMaskImage; + +declare namespace CircleMaskImage { + + interface IConfig { + maskType?: null | 0 | 1 | 2 | 'circle' | 'ellipse' | 'roundRectangle', + radius?: number | + { x?: number, y?: number } | + { + tl?: number | { x?: number, y?: number }, + tr?: number | { x?: number, y?: number }, + bl?: number | { x?: number, y?: number }, + br?: number | { x?: number, y?: number } + }, + + backgroundColor?: string, + + strokeColor?: string, + strokeLineWidth?: number, + } +} + +declare class CircleMaskImage extends Canvas { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + key?: string, frame?: string, + config?: + null | 0 | 1 | 2 | 'circle' | 'ellipse' | 'roundRectangle' | + CircleMaskImage.IConfig + ); + + setTexture( + key?: string, frame?: string, + config?: + null | 0 | 1 | 2 | 'circle' | 'ellipse' | 'roundRectangle' | + CircleMaskImage.IConfig + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/CircleMaskImage.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/CircleMaskImage.js new file mode 100644 index 000000000..5d8f85900 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/CircleMaskImage.js @@ -0,0 +1,134 @@ +import Canvas from '../canvasbase/Canvas.js'; +import AddRoundRectanglePath from '../../../utils/canvas/AddRoundRectanglePath.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class CircleMaskImage extends Canvas { + constructor(scene, x, y, key, frame, config) { + super(scene, x, y); + + this.type = 'rexCircleMaskImage'; + this.setTexture(key, frame, config); + } + + setTexture(key, frame, config) { + if (typeof (frame) === 'object') { + config = frame; + frame = undefined; + } + + if (typeof (config) === 'string') { + config = { + maskType: config + }; + } + + var maskType = GetValue(config, 'maskType', 0); + var backgroundColor = GetValue(config, 'backgroundColor', undefined); + var strokeColor = GetValue(config, 'strokeColor', undefined); + + var defaultStrokeWidth = (strokeColor != null) ? 10 : 0; + var strokeWidth = GetValue(config, 'strokeWidth', defaultStrokeWidth); + + if (maskType === undefined) { + maskType = 0; + } else if (typeof (maskType) === 'string') { + maskType = MASKTYPE[maskType]; + } + + this._textureKey = key; + this._frameName = frame; + + if (maskType === null) { + this.loadTexture(key, frame); + this.dirty = true; + return this; + } + + var textureFrame = this.scene.sys.textures.getFrame(key, frame); + if (!textureFrame) { + return this; + } + // Resize to frame size + if ((textureFrame.cutWidth !== this.width) || (textureFrame.cutHeight !== this.height)) { + this.setCanvasSize(textureFrame.cutWidth, textureFrame.cutHeight); + } else { + this.clear(); + } + + var canvas = this.canvas, + ctx = this.context; + var width = canvas.width, + height = canvas.height; + + // Fill background + if (backgroundColor != null) { + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, width, height); + } + + ctx.save(); + ctx.beginPath(); + + // Build clip path + var halfStrokeLineWidth = strokeWidth / 2; + switch (maskType) { + case 1: // ellipse + var centerX = Math.floor(width / 2); + var centerY = Math.floor(height / 2); + var radiusX = centerX - halfStrokeLineWidth; + var radiusY = centerY - halfStrokeLineWidth; + ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, (2 * Math.PI)); + break; + + case 2: + var radiusConfig = GetValue(config, 'radius', 0); + var iteration = GetValue(config, 'iteration', undefined); + + AddRoundRectanglePath( + ctx, + halfStrokeLineWidth, halfStrokeLineWidth, + width - strokeWidth, height - strokeWidth, + radiusConfig, + iteration + ); + break; + + default: // circle + var centerX = Math.floor(width / 2); + var centerY = Math.floor(height / 2); + var radius = Math.min(centerX, centerY) - halfStrokeLineWidth; + ctx.arc(centerX, centerY, radius, 0, (2 * Math.PI)); + break; + } + + // Draw stroke line + if (strokeColor != null) { + ctx.strokeStyle = strokeColor; + ctx.lineWidth = strokeWidth; + ctx.stroke(); + } + + // Clip frame image + ctx.clip(); + this.loadTexture(key, frame); + ctx.restore(); + + this.dirty = true; + return this; + } + + resize(width, height) { + // Don't draw content again. + this.setDisplaySize(width, height); + return this; + } +} + +const MASKTYPE = { + circle: 0, + ellipse: 1, + roundRectangle: 2 +} + +export default CircleMaskImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/Creator.js new file mode 100644 index 000000000..795410967 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/Creator.js @@ -0,0 +1,16 @@ +import CircleMaskImage from './CircleMaskImage.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var key = GetAdvancedValue(config, 'key', undefined); + var frame = GetAdvancedValue(config, 'frame', undefined); + var gameObject = new CircleMaskImage(this.scene, 0, 0, key, frame, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/Factory.js new file mode 100644 index 000000000..166e88177 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circlemaskimage/Factory.js @@ -0,0 +1,7 @@ +import CircleMaskImage from './CircleMaskImage.js'; + +export default function (x, y, key, frame, config) { + var gameObject = new CircleMaskImage(this.scene, x, y, key, frame, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/CircularProgress.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/CircularProgress.d.ts new file mode 100644 index 000000000..42e60da44 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/CircularProgress.d.ts @@ -0,0 +1,114 @@ +import Canvas from '../canvas/Canvas'; + +export default CircularProgressCanvas; + +declare namespace CircularProgressCanvas { + + type ValueChangeCallbackType = ( + newValue: number, + oldValue: number, + circularProgress: CircularProgressCanvas + ) => void; + + + interface IConfig { + x?: number, y?: number, + radius?: number, + + barColor?: string | number, + trackColor?: string | number, + centerColor?: string | number, + thickness?: number, + startAngle?: number, + anticlockwise?: boolean, + + textColor?: string | number, + textStrokeColor?: string | number, + textStrokeThickness?: number, + textSize?: string, + textFamily?: string, + textStyle?: string, + textFormatCallback?: (value: number) => string, + textFormatCallbackScope?: object, + + value?: number, + + easeValue?: { + duration?: number, + ease?: string + }, + + valuechangeCallback: ValueChangeCallbackType, + + } + + namespace Events { + type ValueChangeCallbackType = ( + newValue: number, + oldValue: number, + circularProgress: CircularProgressCanvas + ) => void; + } +} + +declare class CircularProgressCanvas extends Canvas { + constructor( + scene: Phaser.Scene, + config?: CircularProgressCanvas.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + radius?: number, + barColor?: string | number, + value?: number, + config?: CircularProgressCanvas.IConfig + ); + + value: number; + getValue(min?: number, max?: number): number; + setValue(value?: number, min?: number, max?: number): this; + addValue(inc?: number, min?: number, max?: number): this; + + easeValueTo(value?: number, min?: number, max?: number): this; + stopEaseValue(): this; + setEaseValueDuration(duration: number): this; + setEaseValueFunction(ease: string): this; + + radius: number; + setRadius(radius: number): this; + + trackColor: string; + setTrackColor(trackColor?: string | number): this; + + setThickness(thickness: number): this; + + barColor: string; + setBarColor(barColor?: string | number): this; + + startAngle: number; + setStartAngle(startAngle: number): this; + + anticlockwise: boolean; + setAnticlockwise(anticlockwise: boolean): this; + + centerColor: string; + setCenterColor(centerColor?: string | number): this; + + textColor: string; + setTextColor(color?: string | number): this; + + textStrokeColor: string; + textStrokeThickness: number; + setTextStrokeColor(color?: string | number, thickness?: number): this; + + textFont: string; + setTextFont(fontSize: string, fontFamily: string, fontStyle: string): this; + setTextFont(font: string): this; + + setTextFormatCallback( + callback: (value: number) => string, + scope?: object + ): this +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/CircularProgress.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/CircularProgress.js new file mode 100644 index 000000000..2240f27c8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/CircularProgress.js @@ -0,0 +1,281 @@ +import Canvas from '../canvasbase/Canvas.js'; +import ProgressBase from '../../../utils/progressbase/ProgressBase.js'; +import GetStyle from '../../../utils/canvas/GetStyle.js'; +import DrawContent from './DrawContent.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const Clamp = Phaser.Math.Clamp; + +const DefaultStartAngle = Phaser.Math.DegToRad(270); + +class CircularProgress extends ProgressBase(Canvas) { + constructor(scene, x, y, radius, barColor, value, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + radius = GetValue(config, 'radius', 1); + barColor = GetValue(config, 'barColor', undefined); + value = GetValue(config, 'value', 0); + } + var width = radius * 2; + super(scene, x, y, width, width); + this.type = 'rexCircularProgressCanvas'; + + this.bootProgressBase(config); + + this.setRadius(radius); + this.setTrackColor(GetValue(config, 'trackColor', undefined)); + this.setBarColor(barColor); + this.setCenterColor(GetValue(config, 'centerColor', undefined)); + + this.setThickness(GetValue(config, 'thickness', 0.2)); + this.setStartAngle(GetValue(config, 'startAngle', DefaultStartAngle)); + this.setAnticlockwise(GetValue(config, 'anticlockwise', false)); + + this.setTextColor(GetValue(config, 'textColor', undefined)); + this.setTextStrokeColor( + GetValue(config, 'textStrokeColor', undefined), + GetValue(config, 'textStrokeThickness', undefined) + ); + + var textFont = GetValue(config, 'textFont', undefined); + if (textFont) { + this.setTextFont(textFont); + } else { + this.setTextFont( + GetValue(config, 'textSize', '16px'), + GetValue(config, 'textFamily', 'Courier'), + GetValue(config, 'textStyle', '') + ); + } + this.setTextFormatCallback( + GetValue(config, 'textFormatCallback', undefined), + GetValue(config, 'textFormatCallbackScope', undefined) + ); + + this.setValue(value); + } + + resize(width, height) { + width = Math.floor(Math.min(width, height)); + if (width === this.width) { + return this; + } + + super.resize(width, width); + this.setRadius(width / 2); + return this; + } + + get radius() { + return this._radius; + } + + set radius(value) { + this.dirty = this.dirty || (this._radius != value); + this._radius = value; + var width = value * 2; + this.resize(width, width); + } + + setRadius(radius) { + this.radius = radius; + return this; + } + + get trackColor() { + return this._trackColor; + } + + set trackColor(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty = this.dirty || (this._trackColor != value); + this._trackColor = value; + } + + setTrackColor(color) { + this.trackColor = color; + return this; + } + + get barColor() { + return this._barColor; + } + + set barColor(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty = this.dirty || (this._barColor != value); + this._barColor = value; + } + + setBarColor(color) { + this.barColor = color; + return this; + } + + get startAngle() { + return this._startAngle; + } + + set startAngle(value) { + this.dirty = this.dirty || (this._startAngle != value); + this._startAngle = value; + } + + setStartAngle(angle) { + this.startAngle = angle; + return this; + } + + get anticlockwise() { + return this._anticlockwise; + } + + set anticlockwise(value) { + this.dirty = this.dirty || (this._anticlockwise != value); + this._anticlockwise = value; + } + + setAnticlockwise(anticlockwise) { + if (anticlockwise === undefined) { + anticlockwise = true; + } + this.anticlockwise = anticlockwise; + return this; + } + + get thickness() { + return this._thickness; + } + + set thickness(value) { + value = Clamp(value, 0, 1); + this.dirty = this.dirty || (this._thickness != value); + this._thickness = value; + } + + setThickness(thickness) { + this.thickness = thickness; + return this; + } + + get centerColor() { + return this._centerColor; + } + + set centerColor(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty = this.dirty || (this._centerColor != value); + this._centerColor = value; + } + + get centerColor2() { + return this._centerColor2; + } + + set centerColor2(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty = this.dirty || (this._centerColor2 != value); + this._centerColor2 = value; + } + + setCenterColor(color, color2) { + this.centerColor = color; + this.centerColor2 = color2; + return this; + } + + get textColor() { + return this._textColor; + } + + set textColor(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty = this.dirty || (this._textColor != value); + this._textColor = value; + } + + setTextColor(color) { + this.textColor = color; + return this; + } + + get textStrokeColor() { + return this._textStrokeColor; + } + + set textStrokeColor(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty = this.dirty || (this._textStrokeColor != value); + this._textStrokeColor = value; + } + + get textStrokeThickness() { + return this._textStrokeThickness; + } + + set textStrokeThickness(value) { + this.dirty = this.dirty || (this._textStrokeThickness != value); + this._textStrokeThickness = value; + } + + setTextStrokeColor(color, thickness) { + if (thickness === undefined) { + thickness = 2; + } + this.textStrokeColor = color; + this.textStrokeThickness = thickness; + return this; + } + + get textFont() { + return this._textFont; + } + + set textFont(value) { + this.dirty = this.dirty || (this._textFont != value); + this._textFont = value; + } + + setTextFont(fontSize, fontFamily, fontStyle) { + var font; + if (fontFamily === undefined) { + font = fontSize; + } else { + font = fontStyle + ' ' + fontSize + ' ' + fontFamily; + } + this.textFont = font; + return this; + } + + setTextFormatCallback(callback, scope) { + this.textFormatCallback = callback; + this.textFormatCallbackScope = scope; + return this; + } + + updateTexture() { + this.clear(); + DrawContent.call(this); + super.updateTexture(); + return this; + } + + getFormatText(value) { + if (value === undefined) { + value = this.value; + } + + var text; + if (this.textFormatCallbackScope) { + text = this.textFormatCallback(value); + } else { + text = this.textFormatCallback.call(this.textFormatCallbackScope, value); + } + return text; + } +} + +export default CircularProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/Creator.js new file mode 100644 index 000000000..ddb2252eb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/Creator.js @@ -0,0 +1,13 @@ +import CircularProgress from './CircularProgress.js'; + +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var gameObject = new CircularProgress(this.scene, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/DrawContent.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/DrawContent.js new file mode 100644 index 000000000..f76b323ce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/DrawContent.js @@ -0,0 +1,99 @@ +import DrawCicle from '../../../utils/canvas/DrawCircle.js'; +import DrawText from '../../../utils/canvas/DrawText.js'; + + +var DrawContent = function () { + var x = this.radius; + var lineWidth = this.thickness * this.radius; + var barRadius = this.radius - (lineWidth / 2); + var centerRadius = this.radius - lineWidth; + var canvas = this.canvas, + context = this.context; + + // Draw track + if (this.trackColor && (lineWidth > 0)) { + context.save(); + + DrawCicle( + canvas, context, + x, x, + barRadius, barRadius, + undefined, + this.trackColor, + lineWidth + ); + + context.restore(); + } + + // Draw bar + if ((this.barColor) && (barRadius > 0)) { + var anticlockwise, startAngle, endAngle; + if (this.value === 1) { + anticlockwise = false; + startAngle = 0; + endAngle = 2 * Math.PI; + } else { + anticlockwise = this.anticlockwise; + startAngle = this.startAngle; + var deltaAngle = 2 * Math.PI * ((anticlockwise) ? (1 - this.value) : this.value); + endAngle = deltaAngle + startAngle; + } + + context.save(); + + DrawCicle( + canvas, context, + x, x, + barRadius, barRadius, + undefined, + this.barColor, + lineWidth, + startAngle, endAngle, anticlockwise + ); + + context.restore(); + } + + // Draw center + if (this.centerColor && (centerRadius > 0)) { + var fillStyle; + if (this.centerColor2) { + fillStyle = this.context.createRadialGradient(x, x, 0, x, x, centerRadius); + fillStyle.addColorStop(0, this.centerColor); + fillStyle.addColorStop(1, this.centerColor2); + } else { + fillStyle = this.centerColor; + } + + context.save(); + + DrawCicle( + canvas, context, + x, x, + centerRadius, centerRadius, + fillStyle + ); + + context.restore(); + } + + // Draw text + if (this.textFormatCallback && (this.textColor || this.textStrokeColor)) { + + context.save(); + + DrawText( + canvas, context, + x, x, + this.getFormatText(), this.textFont, + this.textColor, this.textStrokeColor, this.textStrokeThickness, + 'center', // textAlign + 'middle' // textBaseline + ) + + context.restore(); + } +} + +export default DrawContent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/Factory.js new file mode 100644 index 000000000..15c3a0717 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/circularprogress/Factory.js @@ -0,0 +1,7 @@ +import CircularProgress from './CircularProgress.js'; + +export default function (x, y, radius, barColor, value, config) { + var gameObject = new CircularProgress(this.scene, x, y, radius, barColor, value, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/Creator.js new file mode 100644 index 000000000..c1555fb7c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/Creator.js @@ -0,0 +1,13 @@ +import LineProgress from './LineProgress.js'; + +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var gameObject = new LineProgress(this.scene, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/DrawContent.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/DrawContent.js new file mode 100644 index 000000000..f6e3db92c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/DrawContent.js @@ -0,0 +1,144 @@ +import DrawPolygon from '../../../utils/canvas/DrawPolygon.js'; + +var DrawContent = function () { + var skewX = this.skewX; + var width = this.width - Math.abs(skewX); + var height = this.height; + var canvas = this.canvas, + context = this.context; + + // Has track + if (this.trackColor || this.trackStrokeColor) { + BuildPolygon( + 0, 0, // x0, y0 + width, height, // x1, y1 + skewX, // skewX + this.trackPoints + ) + } + + // Has bar + var barX0, barX1; + if (this.barColor) { + if (!this.rtl) { + barX0 = 0; + barX1 = width * this.value; + } else { + barX0 = width * (1 - this.value); + barX1 = width; + } + + BuildPolygon( + barX0, 0, // x0, y0 + barX1, height, // x1, y1 + skewX, // skewX + this.barPoints + ) + } + + if (this.trackColor) { + context.save(); + + DrawPolygon( + canvas, context, + this.trackPoints, + this.trackColor, + ) + + context.restore(); + } + + if (this.barColor) { + context.save(); + + var style; + if (this.barColor2) { + var grd; + if (this.isHorizontalGradient) { + var helfHeight = height / 2; + grd = context.createLinearGradient(barX0, helfHeight, barX1, helfHeight); + } else { + var helfWidth = width / 2; + grd = context.createLinearGradient(helfWidth, 0, helfWidth, height); + } + grd.addColorStop(0, (this.rtl) ? this.barColor : this.barColor2); + grd.addColorStop(1, (this.rtl) ? this.barColor2 : this.barColor); + style = grd; + } else { + style = this.barColor; + } + + DrawPolygon( + canvas, context, + this.barPoints, + style, + ) + + context.restore(); + } + + if (this.trackStrokeColor && this.trackStrokeThickness > 0) { + context.save(); + + DrawPolygon( + canvas, context, + this.trackPoints, + undefined, + this.trackStrokeColor, this.trackStrokeThickness, + ) + + context.restore(); + } +} + +var BuildPolygon = function (x0, y0, x1, y1, skewX, out) { + if (out === undefined) { + out = []; + } + out.length = 4; + + for (var i = 0; i < 4; i++) { + if (!out[i]) { + out[i] = {}; + } + } + + var p; + if (skewX >= 0) { + p = out[0]; + p.x = x0 + skewX; + p.y = y0; + + p = out[1]; + p.x = x1 + skewX; + p.y = y0; + + p = out[2]; + p.x = x1; + p.y = y1; + + p = out[3]; + p.x = x0; + p.y = y1; + } else { + p = out[0]; + p.x = x0; + p.y = y0; + + p = out[1]; + p.x = x1; + p.y = y0; + + p = out[2]; + p.x = x1 - skewX; + p.y = y1; + + p = out[3]; + p.x = x0 - skewX; + p.y = y1; + } + + return out; +} + +export default DrawContent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/Factory.js new file mode 100644 index 000000000..084a4206d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/Factory.js @@ -0,0 +1,7 @@ +import LineProgress from './LineProgress.js'; + +export default function (x, y, width, height, barColor, value, config) { + var gameObject = new LineProgress(this.scene, x, y, width, height, barColor, value, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/LineProgress.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/LineProgress.d.ts new file mode 100644 index 000000000..4cafcdb67 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/LineProgress.d.ts @@ -0,0 +1,104 @@ +import Canvas from '../canvas/Canvas'; + +// import * as Phaser from 'phaser'; +export default LineProgress; + +declare namespace LineProgress { + + type ValueChangeCallbackType = ( + newValue: number, + oldValue: number, + circularProgress: LineProgress + ) => void; + + interface IConfig { + x?: number, y?: number, + width?: number, height?: number, + + trackColor?: string | number, + trackThickness?: number, + trackStrokeColor?: string | number, + barColor?: string | number, + barColor2?: string | number, + isHorizontalGradient?: boolean, + + skewX?: number, + + rtl?: boolean, + + value?: number, + + easeValue?: { + duration?: number, + ease?: string + }, + + valuechangeCallback: ValueChangeCallbackType, + } + + namespace Events { + type ValueChangeCallbackType = ( + newValue: number, + oldValue: number, + circularProgress: LineProgress + ) => void; + } +} + +declare class LineProgress extends Canvas { + constructor( + scene: Phaser.Scene, + config?: LineProgress.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + config?: LineProgress.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + barColor?: string | number, + value?: number, + config?: LineProgress.IConfig + ); + + value: number; + getValue(min?: number, max?: number): number; + setValue(value?: number, min?: number, max?: number): this; + addValue(inc?: number, min?: number, max?: number): this; + + easeValueTo(value?: number, min?: number, max?: number): this; + stopEaseValue(): this; + setEaseValueDuration(duration: number): this; + setEaseValueFunction(ease: string): this; + + trackColor: string; + setTrackColor(radius?: string | number): this; + + trackStrokeThickness: number; + trackStrokeColor: string; + setTrackStroke( + lineWidth?: number, + color?: string | number + ): this; + + barColor: string; + barColor2: string; + isHorizontalGradient: boolean; + setBarColor( + barColor?: string | number, + barColor2?: string | number, + isHorizontalGradient?: boolean, + ): this; + + skewX: number; + setSkewX(skewX: number): this; + + rtl: boolean; + setRTL(enable?: boolean): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/LineProgress.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/LineProgress.js new file mode 100644 index 000000000..7774a57de --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/lineprogress/LineProgress.js @@ -0,0 +1,172 @@ +import Canvas from '../canvasbase/Canvas.js'; +import ProgressBase from '../../../utils/progressbase/ProgressBase.js'; +import GetStyle from '../../../utils/canvas/GetStyle.js'; +import DrawContent from './DrawContent.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +class LineProgress extends ProgressBase(Canvas) { + constructor(scene, x, y, width, height, barColor, value, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 2); + height = GetValue(config, 'height', 2); + barColor = GetValue(config, 'barColor', undefined); + value = GetValue(config, 'value', 0); + } else if (IsPlainObject(width)) { + config = width; + width = GetValue(config, 'width', 2); + height = GetValue(config, 'height', 2); + barColor = GetValue(config, 'barColor', undefined); + value = GetValue(config, 'value', 0); + } else if (IsPlainObject(barColor)) { + config = barColor; + barColor = GetValue(config, 'barColor', undefined); + value = GetValue(config, 'value', 0); + } + super(scene, x, y, width, height); + this.type = 'rexLineProgressCanvas'; + this.trackPoints = []; + this.barPoints = []; + + this.bootProgressBase(config); + + this.setTrackColor(GetValue(config, 'trackColor', undefined)); + this.setBarColor( + barColor, + GetValue(config, 'barColor2', undefined), + GetValue(config, 'isHorizontalGradient', undefined) + ); + this.setTrackStroke(GetValue(config, 'trackStrokeThickness', 2), GetValue(config, 'trackStrokeColor', undefined)); + + this.setSkewX(GetValue(config, 'skewX', 0)); + + this.setRTL(GetValue(config, 'rtl', false)); + + this.setValue(value); + } + + get trackColor() { + return this._trackColor; + } + + set trackColor(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty = this.dirty || (this._trackColor != value); + this._trackColor = value; + } + + setTrackColor(color) { + this.trackColor = color; + return this; + } + + get trackStrokeColor() { + return this._trackStrokeColor; + } + + set trackStrokeColor(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty = this.dirty || (this._trackStrokeColor != value); + this._trackStrokeColor = value; + } + + get trackStrokeThickness() { + return this._trackStrokeThickness; + } + + set trackStrokeThickness(value) { + this.dirty = this.dirty || (this._trackStrokeThickness != value); + this._trackStrokeThickness = value; + } + + setTrackStroke(lineWidth, color) { + this.trackStrokeThickness = lineWidth; + this.trackStrokeColor = color; + return this; + } + + get barColor() { + return this._barColor; + } + + set barColor(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty = this.dirty || (this._barColor != value); + this._barColor = value; + } + + get barColor2() { + return this._barColor2; + } + + set barColor2(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty = this.dirty || (this._barColor2 != value); + this._barColor2 = value; + } + + get isHorizontalGradient() { + return this._isHorizontalGradient; + } + + set isHorizontalGradient(value) { + this.dirty |= (this._isHorizontalGradient != value); + this._isHorizontalGradient = value; + } + + setBarColor(color, color2, isHorizontalGradient) { + if (isHorizontalGradient === undefined) { + isHorizontalGradient = true; + } + + this.barColor = color; + this.barColor2 = color2; + this.isHorizontalGradient = isHorizontalGradient; + return this; + } + + get skewX() { + return this._skewX; + } + + set skewX(value) { + this.dirty = this.dirty || (this._skewX != value); + this._skewX = value; + } + + setSkewX(value) { + this.skewX = value; + return this; + } + + get rtl() { + return this._rtl; + } + + set rtl(value) { + value = !!value; + this.dirty = this.dirty || (this._rtl != value); + this._rtl = value; + } + + setRTL(enable) { + if (enable === undefined) { + enable = true; + } + this.rtl = enable; + return this; + } + + updateTexture() { + this.clear(); + DrawContent.call(this); + super.updateTexture(); + return this; + } +} + +export default LineProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/Creator.js new file mode 100644 index 000000000..9f2cc596a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/Creator.js @@ -0,0 +1,22 @@ +import RoundRectangle from './RoundRectangle.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', undefined); + var radius = GetAdvancedValue(config, 'radius', undefined); + var fillStyle = GetAdvancedValue(config, 'fillStyle', undefined); + var strokeStyle = GetAdvancedValue(config, 'strokeStyle', undefined); + var lineWidth = GetAdvancedValue(config, 'lineWidth', undefined); + var fillColor2 = GetAdvancedValue(config, 'fillColor2', undefined); + var isHorizontalGradient = GetAdvancedValue(config, 'isHorizontalGradient', true); + var gameObject = new RoundRectangle(this.scene, 0, 0, width, height, radius, fillStyle, strokeStyle, lineWidth, fillColor2, isHorizontalGradient); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/DrawContent.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/DrawContent.js new file mode 100644 index 000000000..f09dbe3e4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/DrawContent.js @@ -0,0 +1,16 @@ +import DrawRoundRectangleBackground from '../utils/DrawRoundRectangleBackground.js'; + +var DrawContent = function () { + DrawRoundRectangleBackground( + this, + this.fillStyle, + this.strokeStyle, + this.lineWidth, + this.radius, + this.fillColor2, + this.isHorizontalGradient, + this.iteration + ); +} + +export default DrawContent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/Factory.js new file mode 100644 index 000000000..8346195e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/Factory.js @@ -0,0 +1,7 @@ +import RoundRectangle from './RoundRectangle.js'; + +export default function (x, y, width, height, radius, fillStyle, strokeStyle, lineWidth, fillColor2, isHorizontalGradient) { + var gameObject = new RoundRectangle(this.scene, x, y, width, height, radius, fillStyle, strokeStyle, lineWidth, fillColor2, isHorizontalGradient); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/RoundRectangle.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/RoundRectangle.d.ts new file mode 100644 index 000000000..caff6375f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/RoundRectangle.d.ts @@ -0,0 +1,62 @@ +import Canvas from '../canvas/Canvas'; + +export default RoundRectangle; + +declare namespace RoundRectangle { + + interface IRadiusConfig { + tl?: (number | { x?: number, y?: number }), + tr?: (number | { x?: number, y?: number }), + bl?: (number | { x?: number, y?: number }), + br?: (number | { x?: number, y?: number }), + + x?: number, + y?: number, + } + +} + +declare class RoundRectangle extends Canvas { + constructor( + scene: Phaser.Scene, + x: number, + y: number, + width: number, + height: number, + radiusConfig?: number | RoundRectangle.IRadiusConfig | + ({ + radius?: (number | RoundRectangle.IRadiusConfig), + iteration?: number + }), + fillStyle?: number | string | null, + strokeStyle?: number | string | null, + lineWidth?: number, + + fillColor2?: number | string | null, + isHorizontalGradient?: boolean + ); + + fillStyle: string; + fillColor2: string; + isHorizontalGradient: boolean; + setFillStyle( + fillStyle?: number | string | null, + fillColor2?: number | string | null, + isHorizontalGradient?: boolean, + ): this; + + strokeStyle: string; + lineWidth: number; + setStrokeStyle( + strokeStyle?: number | string | null, + lineWidth?: number, + ): this; + + radius: number | RoundRectangle.IRadiusConfig; + setRadius( + value?: number | RoundRectangle.IRadiusConfig + ): this; + + iteration: number; + setIteration(value?: number): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/RoundRectangle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/RoundRectangle.js new file mode 100644 index 000000000..be4c0dfaf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/roundrectangle/RoundRectangle.js @@ -0,0 +1,126 @@ +import Canvas from '../canvasbase/Canvas.js'; +import GetStyle from '../../../utils/canvas/GetStyle.js'; +import DrawContent from './DrawContent.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class RoundRectangle extends Canvas { + constructor(scene, x, y, width, height, radiusConfig, fillStyle, strokeStyle, lineWidth, fillColor2, isHorizontalGradient) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (width === undefined) { width = 1; } + if (height === undefined) { height = width; } + if (radiusConfig === undefined) { radiusConfig = 0; } + + super(scene, x, y, width, height); + this.type = 'rexRoundRectangleCanvas'; + + var radius = GetValue(radiusConfig, 'radius', radiusConfig); + var iteration = GetValue(radiusConfig, 'iteration', undefined); + this.setRadius(radius); + this.setIteration(iteration); + this.setFillStyle(fillStyle, fillColor2, isHorizontalGradient); + this.setStrokeStyle(strokeStyle, lineWidth); + } + + get radius() { + return this._radius; + } + + set radius(value) { + this.dirty |= (this._radius != value); + this._radius = value; + } + + setRadius(radius) { + this.radius = radius; + return this; + } + + get iteration() { + return this._iteration; + } + + set iteration(value) { + this.dirty |= (this._iteration != value); + this._iteration = value; + } + + setIteration(iteration) { + this.iteration = iteration; + return this; + } + + get fillStyle() { + return this._fillStyle; + } + + set fillStyle(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty |= (this._fillStyle != value); + this._fillStyle = value; + } + + get fillColor2() { + return this._fillColor2; + } + + set fillColor2(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty |= (this._fillColor2 != value); + this._fillColor2 = value; + } + + get isHorizontalGradient() { + return this._isHorizontalGradient; + } + + set isHorizontalGradient(value) { + this.dirty |= (this._isHorizontalGradient != value); + this._isHorizontalGradient = value; + } + + setFillStyle(fillStyle, fillColor2, isHorizontalGradient) { + if (isHorizontalGradient === undefined) { + isHorizontalGradient = true; + } + this.fillStyle = fillStyle; + this.fillColor2 = fillColor2; + this.isHorizontalGradient = isHorizontalGradient; + return this; + } + + get strokeStyle() { + return this._strokeStyle; + } + + set strokeStyle(value) { + value = GetStyle(value, this.canvas, this.context); + this.dirty |= (this._strokeStyle != value); + this._strokeStyle = value; + } + + get lineWidth() { + return this._lineWidth; + } + + set lineWidth(value) { + this.dirty |= (this._lineWidth != value); + this._lineWidth = value; + } + + setStrokeStyle(strokeStyle, lineWidth) { + this.strokeStyle = strokeStyle; + this.lineWidth = lineWidth; + return this; + } + + updateTexture() { + this.clear(); + DrawContent.call(this); + super.updateTexture(); + return this; + } +} + +export default RoundRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/utils/DrawRoundRectangleBackground.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/utils/DrawRoundRectangleBackground.js new file mode 100644 index 000000000..e3f6b369b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/canvas/utils/DrawRoundRectangleBackground.js @@ -0,0 +1,36 @@ +import DrawRoundRectangle from '../../../utils/canvas/DrawRoundRectangle.js'; + +var DrawRoundRectangleBackground = function ( + canvasObject, + color, + strokeColor, strokeLineWidth, + radius, + color2, isHorizontalGradient, + iteration +) { + + if ((color == null) && (strokeColor == null)) { + return; + } + + var width = canvasObject.canvas.width, + height = canvasObject.canvas.height; + + if (strokeColor == null) { + strokeLineWidth = 0; + } + var x = strokeLineWidth / 2; + width = Math.max(1, width - strokeLineWidth); // Min width is 1 + height = Math.max(1, height - strokeLineWidth); // Min height is 1 + DrawRoundRectangle(canvasObject.canvas, canvasObject.context, + x, x, + width, height, + radius, + color, + strokeColor, strokeLineWidth, + color2, isHorizontalGradient, + iteration + ); +} + +export default DrawRoundRectangleBackground; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/Carousel.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/Carousel.js new file mode 100644 index 000000000..19997d77b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/Carousel.js @@ -0,0 +1,26 @@ +import GridTable from '../gridtable/GridTable.js'; +import Methods from './methods/Methods.js'; + +class Carousel extends GridTable { + constructor(scene, x, y, width, height, config) { + if (config === undefined) { + config = {}; + } + config.columns = 1; // Force columns to 1 + if (!config.hasOwnProperty('clamplTableOXY')) { + config.clamplTableOXY = false; + } + + super(scene, x, y, width, height, config); + this.type = 'rexCarousel'; + + } +} + +// mixin +Object.assign( + Carousel.prototype, + Methods +); + +export default Carousel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/Creator.js new file mode 100644 index 000000000..2aa1a68c8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/Creator.js @@ -0,0 +1,23 @@ +import Carousel from './Carousel.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetValue(config, 'width', 256); + var height = GetValue(config, 'height', 256); + var gameObject = new Carousel(this.scene, 0, 0, width, height, config); + + // set properties wo modify children + gameObject.syncChildrenEnable = false; + BuildGameObject(this.scene, gameObject, config); + // sync properties of children + gameObject.syncChildrenEnable = true; + gameObject.syncPosition().syncVisible().syncAlpha(); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/Factory.js new file mode 100644 index 000000000..992213228 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/Factory.js @@ -0,0 +1,7 @@ +import Carousel from './Carousel.js'; + +export default function (x, y, width, height, config) { + var gameObject = new Carousel(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/methods/Methods.js new file mode 100644 index 000000000..f19d37e68 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/methods/Methods.js @@ -0,0 +1,7 @@ +import ShowCells from './ShowCells.js'; + +var Methods = { + showCells: ShowCells +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/methods/ShowCells.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/methods/ShowCells.js new file mode 100644 index 000000000..4854b8a37 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/carousel/methods/ShowCells.js @@ -0,0 +1,57 @@ +var ShowCells = function () { + if (this.cellsCount === 0) { + return; + } + var table = this.table; + + var startRowIdx = table.heightToRowIndex(-this.tableOY); + if (startRowIdx <= 0) { + startRowIdx = 0; //Turn -0 to 0 + } + var rowIdx = startRowIdx; + + var startColIdx = table.widthToColIndex(-this.tableOX); + if (startColIdx <= 0) { + startColIdx = 0; //Turn -0 to 0 + } + var colIdx = startColIdx; + + var cellIdx = table.colRowToCellIndex(colIdx, rowIdx); + var bottomBound = this.bottomBound; + var rightBound = this.rightBound; + var lastIdx = table.cellsCount - 1; + var lastColIdx = table.colCount - 1; + + var startCellTLX = this.getCellTLX(colIdx), + cellTLX = startCellTLX; + var cellTLY = this.getCellTLY(rowIdx); + while ((cellTLY < bottomBound) && (cellIdx <= lastIdx)) { + if (this.table.isValidCellIdx(cellIdx)) { + var cell = table.getCell(cellIdx, true); + this.visibleCells.set(cell); + if (!this.preVisibleCells.contains(cell)) { + this.showCell(cell); + } + if (this.scrollMode === 0) { + cell.setXY(cellTLX, cellTLY); + } else { + cell.setXY(cellTLY, cellTLX); + } + } + + if ((cellTLX < rightBound) && (colIdx < lastColIdx)) { + cellTLX += table.getColWidth(colIdx); + colIdx += 1; + } else { + cellTLX = startCellTLX; + cellTLY += table.getRowHeight(rowIdx); + + colIdx = startColIdx; + rowIdx += 1; + } + + cellIdx = table.colRowToCellIndex(colIdx, rowIdx); + } +} + +export default ShowCells; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Active.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Active.js new file mode 100644 index 000000000..cd0d5995e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Active.js @@ -0,0 +1,47 @@ +import GetLocalState from './utils/GetLocalState.js'; + +export default { + updateChildActive(child) { + var localState = GetLocalState(child); + var parent = localState.parent; + child.active = parent.active && localState.active; + return this; + }, + + syncActive() { + if (this.syncChildrenEnable) { + this.children.forEach(this.updateChildActive, this); + } + return this; + }, + + resetChildActiveState(child) { + var localState = GetLocalState(child); + localState.active = child.active; + return this; + }, + + setChildActive(child, active) { + child.active = active; + this.resetChildActiveState(child); + return this; + }, + + setChildLocalActive(child, active) { + if (active === undefined) { + active = true; + } + var localState = GetLocalState(child); + localState.active = active; + this.updateChildActive(child); + return this; + }, + + resetLocalActiveState() { + var parent = GetLocalState(this).parent; + if (parent) { + parent.resetChildActiveState(this); + } + return this; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/AddChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/AddChild.js new file mode 100644 index 000000000..a861463d9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/AddChild.js @@ -0,0 +1,136 @@ +import Base from './Base.js'; +import GetLocalState from './utils/GetLocalState.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const BaseAdd = Base.prototype.add; + +var Add = function (gameObject, config) { + this.setParent(gameObject); + + var state = GetLocalState(gameObject); + SetupSyncFlags(state, config); + + this + .resetChildState(gameObject) // Reset local state of child + .updateChildVisible(gameObject) // Apply parent's visible to child + .updateChildActive(gameObject) // Apply parent's active to child + .updateChildScrollFactor(gameObject) // Apply parent's scroll factor to child + .updateChildMask(gameObject); // Apply parent's mask to child + + BaseAdd.call(this, gameObject); + + this.addToParentContainer(gameObject); + this.addToRenderLayer(gameObject); + + return this; +} + +var AddLocal = function (gameObject, config) { + this.setParent(gameObject); + + // Set local state from child directly + var state = GetLocalState(gameObject); + SetupSyncFlags(state, config); + // Position + state.x = gameObject.x; + state.y = gameObject.y; + state.rotation = gameObject.rotation; + state.scaleX = gameObject.scaleX; + state.scaleY = gameObject.scaleY; + // Alpha + state.alpha = gameObject.alpha; + // Visible + state.visible = gameObject.visible; + // Active + state.active = gameObject.active; + + this + .updateChildPosition(gameObject) + .updateChildAlpha(gameObject) + .updateChildVisible(gameObject) // Apply parent's visible to child + .updateChildActive(gameObject) // Apply parent's active to child + .updateChildScrollFactor(gameObject) // Apply parent's scroll factor to child + .updateChildMask(gameObject); // Apply parent's mask to child + + BaseAdd.call(this, gameObject); + + this.addToRenderLayer(gameObject); + + return this; +} + +var SetupSyncFlags = function (state, config) { + if (config === undefined) { + config = true; + } + + if (typeof (config) === 'boolean') { + state.syncPosition = config; + state.syncRotation = config; + state.syncScale = config; + state.syncAlpha = config; + state.syncScrollFactor = config; + } else { + state.syncPosition = GetValue(config, 'syncPosition', true); + state.syncRotation = GetValue(config, 'syncRotation', true); + state.syncScale = GetValue(config, 'syncScale', true); + state.syncAlpha = GetValue(config, 'syncAlpha', true); + state.syncScrollFactor = GetValue(config, 'syncScrollFactor', true); + } + +} + +export default { + // Can override this method + add(gameObject) { + if (Array.isArray(gameObject)) { + this.addMultiple(gameObject); + } else { + Add.call(this, gameObject); + } + return this; + }, + + // Don't override this method + pin(gameObject, config) { + if (Array.isArray(gameObject)) { + this.addMultiple(gameObject, config); + } else { + Add.call(this, gameObject, config); + } + return this; + }, + + addMultiple(gameObjects) { + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + Add.call(this, gameObjects[i]); + } + return this; + }, + + addLocal(gameObject) { + if (Array.isArray(gameObject)) { + this.addMultiple(gameObject); + } else { + AddLocal.call(this, gameObject); + } + return this; + }, + + // Don't override this method + pinLocal(gameObject, config) { + if (Array.isArray(gameObject)) { + this.addMultiple(gameObject, config); + } else { + AddLocal.call(this, gameObject, config); + } + return this; + }, + + addLocalMultiple(gameObjects) { + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + AddLocal.call(this, gameObjects[i]); + } + return this; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Alpha.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Alpha.js new file mode 100644 index 000000000..5c620415f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Alpha.js @@ -0,0 +1,48 @@ +import GetLocalState from './utils/GetLocalState.js'; +import GetScale from './utils/GetScale.js'; + +export default { + updateChildAlpha(child) { + var state = GetLocalState(child); + var parent = state.parent; + if (state.syncAlpha) { + child.alpha = parent.alpha * state.alpha; + } + return this; + }, + + syncAlpha() { + if (this.syncChildrenEnable) { + this.children.forEach(this.updateChildAlpha, this); + } + return this; + }, + + resetChildAlphaState(child) { + var state = GetLocalState(child); + var parent = state.parent; + state.alpha = GetScale(child.alpha, parent.alpha); + return this; + }, + + setChildAlpha(child, alpha) { + child.alpha = alpha; + this.resetChildAlphaState(child); + return this; + }, + + setChildLocalAlpha(child, alpha) { + var state = GetLocalState(child); + state.alpha = alpha; + this.updateChildAlpha(child); + return this; + }, + + resetLocalAlphaState() { + var parent = GetLocalState(this).parent; + if (parent) { + parent.resetChildAlphaState(this); + } + return this; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Base.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Base.d.ts new file mode 100644 index 000000000..503f77c3c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Base.d.ts @@ -0,0 +1,43 @@ +export default Base; + +declare class Base extends Phaser.GameObjects.Zone { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + ); + + contains( + gameObject: Phaser.GameObjects.GameObject + ): boolean; + + add( + child: Phaser.GameObjects.GameObject | Phaser.GameObjects.GameObject[] + ): this; + + remove( + gameObject: Phaser.GameObjects.GameObject, + destroyChild?: boolean + ): this; + + clear( + destroyChild?: boolean + ): this; + + + // Components + clearAlpha(): this; + setAlpha(topLeft?: number, topRight?: number, bottomLeft?: number, bottomRight?: number): this; + alpha: number; + alphaTopLeft: number; + alphaTopRight: number; + alphaBottomLeft: number; + alphaBottomRight: number; + + toggleFlipX(): this; + toggleFlipY(): this; + setFlipX(value: boolean): this; + setFlipY(value: boolean): this; + setFlip(x: boolean, y: boolean): this; + resetFlip(): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Base.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Base.js new file mode 100644 index 000000000..53ee74f41 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Base.js @@ -0,0 +1,104 @@ +const Zone = Phaser.GameObjects.Zone; +const AddItem = Phaser.Utils.Array.Add; +const RemoveItem = Phaser.Utils.Array.Remove; + +class Base extends Zone { + constructor(scene, x, y, width, height) { + if (x === undefined) { + x = 0; + } + if (y === undefined) { + y = 0; + } + if (width === undefined) { + width = 1; + } + if (height === undefined) { + height = 1; + } + super(scene, x, y, width, height); + this.children = []; + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + if (fromScene) { + // Stop scene + var child; + for (var i = this.children.length - 1; i >= 0; i--) { + child = this.children[i]; + if (!child.parentContainer && // Not in container + !child.displayList // Not in scene, neither in layer + ) { + // Destroy child which is not in scene, container, or layer manually + child.destroy(fromScene); + } + } + } + + // Destroy/remove children + this.clear(!fromScene); + super.destroy(fromScene); + } + + contains(gameObject) { + return (this.children.indexOf(gameObject) !== -1); + } + + add(gameObjects) { + var parent = this; + AddItem(this.children, gameObjects, 0, + // Callback of item added + function (gameObject) { + gameObject.once('destroy', parent.onChildDestroy, parent); + }, this); + return this; + } + + remove(gameObjects, destroyChild) { + var parent = this; + RemoveItem(this.children, gameObjects, + // Callback of item removed + function (gameObject) { + gameObject.off('destroy', parent.onChildDestroy, parent); + if (destroyChild) { + gameObject.destroy(); + } + } + ); + return this; + } + + onChildDestroy(child, fromScene) { + // Only remove reference + this.remove(child, false); + } + + clear(destroyChild) { + var parent = this; + var gameObject; + for (var i = 0, cnt = this.children.length; i < cnt; i++) { + gameObject = this.children[i]; + gameObject.off('destroy', parent.onChildDestroy, parent); + if (destroyChild) { + gameObject.destroy(); + } + } + this.children.length = 0; + return this; + } +} + +const Components = Phaser.GameObjects.Components; +Phaser.Class.mixin(Base, + [ + Components.Alpha, + Components.Flip + ] +); + +export default Base; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ChangeOrigin.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ChangeOrigin.js new file mode 100644 index 000000000..dfd0a1b43 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ChangeOrigin.js @@ -0,0 +1,15 @@ +import ChangeOriginBase from '../../../utils/origin/ChangeOrigin.js'; + +var ChangeOrigin = function (originX, originY) { + this.syncChildrenEnable = false; + ChangeOriginBase(this, originX, originY); + this.syncChildrenEnable = true; + + var children = this.getAllChildren(); + for (var i = 0, cnt = children.length; i < cnt; i++) { + this.resetChildPositionState(children[i]); + } + return this; +} + +export default ChangeOrigin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ChildState.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ChildState.js new file mode 100644 index 000000000..fce11dc15 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ChildState.js @@ -0,0 +1,34 @@ +import GetLocalState from './utils/GetLocalState.js'; + +export default { + getLocalState(gameObject) { + return GetLocalState(gameObject); + }, + + resetChildState(gameObject) { + this + .resetChildPositionState(gameObject) + .resetChildVisibleState(gameObject) + .resetChildAlphaState(gameObject) + .resetChildActiveState(gameObject); + return this; + }, + + resetChildrenState(gameObjects) { + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + this.resetChildState(gameObjects[i]); + } + return this; + }, + + syncProperties() { + this + .syncPosition() + .syncVisible() + .syncAlpha() + .syncActive() + .syncScrollFactor() + .syncMask(); + return this; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Children.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Children.js new file mode 100644 index 000000000..cd2917f38 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Children.js @@ -0,0 +1,135 @@ +import { GetParent } from './GetParent.js'; +import { DepthFirstSearch, BreadthFirstSearch } from './utils/Traversal.js'; + +const ArrayUtils = Phaser.Utils.Array; + +export default { + getChildren(out) { + if (!out) { + out = this.children; // Return internal children array + } else { + for (var i = 0, cnt = this.children.length; i < cnt; i++) { + out.push(this.children[i]); + } + // Copy children + } + return out; + }, + + getAllChildren(out) { + if (out === undefined) { + out = []; + } + + var root = this; + BreadthFirstSearch(root, function (child) { + // Don't add root + if (child === root) { + return; + } + out.push(child); + }); + + return out; + }, + + getAllVisibleChildren(out) { + if (out === undefined) { + out = []; + } + + var root = this; + BreadthFirstSearch(root, function (child) { + // Don't add root + if (child === root) { + return; + } + // Don't add invisible child + if (!child.visible) { + return true; + } + out.push(child); + }); + + return out; + }, + + bfs(callback, root) { + if (root === undefined) { + root = this; + } + BreadthFirstSearch(root, callback); + return this; + }, + + dfs(callback, root) { + if (root === undefined) { + root = this; + } + DepthFirstSearch(root, callback); + return this; + }, + + contains(gameObject) { // Override Base.contains method + var parent = GetParent(gameObject); + if (!parent) { + return false; + } else if (parent === this) { + return true; + } else { + return this.contains(parent); + } + }, + + getByName(name, recursive) { + if (!recursive) { + return ArrayUtils.GetFirst(this.children, 'name', name); // object, or null if not found + + } else { // recursive + // Breadth-first search + var queue = [this]; + var parent, child; + while (queue.length) { + parent = queue.shift(); + + for (var i = 0, cnt = parent.children.length; i < cnt; i++) { + child = parent.children[i]; + if (child.name === name) { + return child; + } else if (child.isRexContainerLite) { + queue.push(child); + } + } + } + return null; + + } + + }, + + getRandom(startIndex, length) { + return ArrayUtils.GetRandom(this.children, startIndex, length); + }, + + getFirst(property, value, startIndex, endIndex) { + return ArrayUtils.GetFirstElement(this.children, property, value, startIndex, endIndex); + }, + + getAll(property, value, startIndex, endIndex) { + return ArrayUtils.GetAll(this.children, property, value, startIndex, endIndex); + }, + + count(property, value, startIndex, endIndex) { + return ArrayUtils.CountAllMatching(this.children, property, value, startIndex, endIndex); + }, + + swap(child1, child2) { + ArrayUtils.Swap(this.children, child1, child2); + return this; + }, + + setAll(property, value, startIndex, endIndex) { + ArrayUtils.SetAll(this.children, property, value, startIndex, endIndex); + return this; + }, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ContainerLite.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ContainerLite.d.ts new file mode 100644 index 000000000..a6083e399 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ContainerLite.d.ts @@ -0,0 +1,345 @@ +// import * as Phaser from 'phaser'; +import Base from './Base'; + +export default ContainerLite; + +declare namespace ContainerLite { + interface ILocalState { + parent: ContainerLite, + self: Phaser.GameObjects.GameObject, + layer: Phaser.GameObjects.Layer | null, + + x: number, y: number, + rotation: number, angle: number, + scaleX: number, scaleY: number, displayWidth: number, displayHeight: number, + alpha: number, + visible: boolean, + active: boolean, + } + + interface IAddChildConfig { + syncPosition?: boolean, + syncRotation?: boolean, + syncScale?: boolean, + syncAlpha?: boolean, + syncScrollFactor?: boolean, + + } + + interface IDrawBoundsConfig { + drawContainer?: boolean, + children?: Phaser.GameObjects.GameObject, + color?: number, + lineWidth?: number, + padding?: number, + } +} + +declare class ContainerLite extends Base { + isRexContainerLite: true; + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + children?: Phaser.GameObjects.GameObject[] + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + children?: Phaser.GameObjects.GameObject[] + ); + + add( + gameObject: Phaser.GameObjects.GameObject | Phaser.GameObjects.GameObject[] + ): this; + + pin( + gameObject: Phaser.GameObjects.GameObject | Phaser.GameObjects.GameObject[], + config?: ContainerLite.IAddChildConfig | boolean + ): this; + + unpin( + gameObject: Phaser.GameObjects.GameObject, + destroyChild?: boolean + ): this; + + addMultiple( + children: Phaser.GameObjects.GameObject[] + ): this; + + addLocal( + child: Phaser.GameObjects.GameObject | Phaser.GameObjects.GameObject[] + ): this; + + pinLocal( + child: Phaser.GameObjects.GameObject | Phaser.GameObjects.GameObject[], + config?: ContainerLite.IAddChildConfig | boolean + ): this; + + addLocalMultiple( + children: Phaser.GameObjects.GameObject[] + ): this; + + setChildPosition( + child: Phaser.GameObjects.GameObject, + x: number, + y: number + ): this; + + setChildLocalPosition( + child: Phaser.GameObjects.GameObject, + x: number, + y: number + ): this; + + setChildRotation( + child: Phaser.GameObjects.GameObject, + rotation: number + ): this; + + setChildAngle( + child: Phaser.GameObjects.GameObject, + angle: number + ): this; + + setChildLocalRotation( + child: Phaser.GameObjects.GameObject, + rotation: number + ): this; + + setChildLocalAngle( + child: Phaser.GameObjects.GameObject, + angle: number + ): this; + + setChildScale( + child: Phaser.GameObjects.GameObject, + scaleX: number, + scaleY: number + ): this; + + setChildLocalScale( + child: Phaser.GameObjects.GameObject, + scaleX: number, + scaleY: number + ): this; + + setChildDisplaySize( + child: Phaser.GameObjects.GameObject, + width: number, + height: number + ): this; + + setChildVisible( + child: Phaser.GameObjects.GameObject, + visible: boolean + ): this; + + setChildAlpha( + child: Phaser.GameObjects.GameObject, + alpha: number + ): this; + + setChildLocalAlpha( + child: Phaser.GameObjects.GameObject, + alpha: number + ): this; + + resetChildState( + child: Phaser.GameObjects.GameObject + ): this; + + resetChildPositionState( + child: Phaser.GameObjects.GameObject + ): this; + + resetChildRotationState( + child: Phaser.GameObjects.GameObject + ): this; + + resetChildScaleState( + child: Phaser.GameObjects.GameObject + ): this; + + resetChildAlphaState( + child: Phaser.GameObjects.GameObject + ): this; + + resetChildVisibleState( + child: Phaser.GameObjects.GameObject + ): this; + + resetChildActiveState( + child: Phaser.GameObjects.GameObject + ): this; + + setMask( + mask: Phaser.Display.Masks.BitmapMask | Phaser.Display.Masks.GeometryMask + ): this; + + clearMask( + destroyMask?: boolean + ): this; + + tween( + config: Phaser.Types.Tweens.TweenBuilderConfig | object + ): Phaser.Tweens.Tween; + + tweenChild( + config: Phaser.Types.Tweens.TweenBuilderConfig | object + ): Phaser.Tweens.Tween; + + tweenSelf( + config: Phaser.Types.Tweens.TweenBuilderConfig | object + ): Phaser.Tweens.Tween; + + createTweenChildConfig( + config: Phaser.Types.Tweens.TweenBuilderConfig | object + ): Phaser.Types.Tweens.TweenBuilderConfig; + + getChildren( + out?: Phaser.GameObjects.GameObject[] + ): Phaser.GameObjects.GameObject[]; + + getAllChildren( + out?: Phaser.GameObjects.GameObject[] + ): Phaser.GameObjects.GameObject[]; + + getAllVisibleChildren( + out?: Phaser.GameObjects.GameObject[] + ): Phaser.GameObjects.GameObject[]; + + bfs( + callback: (child: Phaser.GameObjects.GameObject) => boolean + ): this; + + dfs( + callback: (child: Phaser.GameObjects.GameObject) => boolean + ): this; + + getByName( + name: string, + recursive?: boolean + ): Phaser.GameObjects.GameObject; + + getRandom(): Phaser.GameObjects.GameObject; + + getFirst( + property: string, + value?: unknown, + startIndex?: number, + endIndex?: number + ): Phaser.GameObjects.GameObject; + + getAll( + property: string, + value?: unknown, + startIndex?: number, + endIndex?: number + ): Phaser.GameObjects.GameObject; + + count( + property: string, + value?: unknown, + startIndex?: number, + endIndex?: number + ): this; + + swap( + child1: Phaser.GameObjects.GameObject, + child2: Phaser.GameObjects.GameObject + ): number; + + setAll( + property: string, + value?: unknown, + startIndex?: number, + endIndex?: number + ): this; + + setDepth( + value: number, + containerOnly?: boolean + ): this; + + swapDepth( + containerB: ContainerLite + ): this; + + incDepth( + inc: number + ): this; + + bringToTop(): this; + + moveDepthBelow( + gameObject: Phaser.GameObjects.GameObject + ): this; + + moveDepthAbove( + gameObject: Phaser.GameObjects.GameObject + ): this; + + getParent( + name?: string + ): ContainerLite; + + getParent( + gameObject?: Phaser.GameObjects.GameObject, + name?: string + ): ContainerLite; + + getTopmostParent( + gameObject?: Phaser.GameObjects.GameObject + ): ContainerLite; + + getLocalState( + child: Phaser.GameObjects.GameObject + ): ContainerLite.ILocalState; + + addToLayer( + layer: Phaser.GameObjects.Layer + ): this; + + addToContainer( + container: Phaser.GameObjects.Container + ): this; + + removeFromContainer(): this; + + enableLayer(): this; + + getLayer(): Phaser.GameObjects.Layer; + + snapshot( + config?: { + renderTexture?: Phaser.GameObjects.RenderTexture, + padding?: number, + } + ): Phaser.GameObjects.RenderTexture; + + snapshot( + config?: { + padding?: number, + saveTexture: string, + } + ): this; + + changeOrigin( + originX: number, + originY: number + ): this; + + drawBounds( + graphics: Phaser.GameObjects.Graphics, + color?: number + ): this; + + drawBounds( + graphics: Phaser.GameObjects.Graphics, + config?: ContainerLite.IDrawBoundsConfig, + ): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ContainerLite.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ContainerLite.js new file mode 100644 index 000000000..67da68b86 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ContainerLite.js @@ -0,0 +1,270 @@ +import Base from './Base.js'; +import Methods from './Methods.js'; +import { GetParent } from './GetParent.js'; + +class ContainerLite extends Base { + constructor(scene, x, y, width, height, children) { + if (Array.isArray(width)) { + children = width; + width = undefined; + height = undefined; + } + super(scene, x, y, width, height); + this.type = 'rexContainerLite'; + this.isRexContainerLite = true; + this.syncChildrenEnable = true; + + this._active = true; + this._mask = null; + this._scrollFactorX = 1; + this._scrollFactorY = 1; + this.privateRenderLayer = undefined; + + if (children) { + this.add(children); + } + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + this.syncChildrenEnable = false; // Don't sync properties changing anymore + super.destroy(fromScene); + + if (this.privateRenderLayer) { + this.privateRenderLayer.list.length = 0; // Remove all children without trigger callback + this.privateRenderLayer.destroy(); + } + } + + resize(width, height) { + this.setSize(width, height); + return this; + } + + get x() { + return this._x; + } + + set x(value) { + if (this._x === value) { + return; + } + this._x = value; + + this.syncPosition(); + } + + get y() { + return this._y; + } + + set y(value) { + if (this._y === value) { + return; + } + this._y = value; + + this.syncPosition(); + } + + // Override + get rotation() { + return super.rotation; + } + + set rotation(value) { + if (this.rotation === value) { + return; + } + super.rotation = value; + + this.syncPosition(); + } + + // Override + get scaleX() { + return super.scaleX; + } + + set scaleX(value) { + if (this.scaleX === value) { + return; + } + super.scaleX = value; + + this.syncPosition(); + } + + // Override + get scaleY() { + return super.scaleY; + } + + set scaleY(value) { + if (this.scaleY === value) { + return; + } + super.scaleY = value; + + this.syncPosition(); + } + + // Override + get scale() { + return super.scale; + } + + set scale(value) { + if (this.scale === value) { + return; + } + super.scale = value; + + this.syncPosition(); + } + + // Override + get visible() { + return super.visible; + } + + set visible(value) { + if (super.visible === value) { + return; + } + super.visible = value; + + this.syncVisible(); + } + + // Override + get alpha() { + return super.alpha; + } + + set alpha(value) { + if (super.alpha === value) { + return; + } + super.alpha = value; + + this.syncAlpha(); + } + + // Override + get active() { + return this._active; + } + + set active(value) { + if (this._active === value) { + return; + } + this._active = value; + + this.syncActive(); + } + + // Override + get mask() { + return this._mask; + } + set mask(mask) { + if (this._mask === mask) { + return; + } + this._mask = mask; + + this.syncMask(); + } + + // Override + get scrollFactorX() { + return this._scrollFactorX; + } + + set scrollFactorX(value) { + if (this._scrollFactorX === value) { + return; + } + + this._scrollFactorX = value; + this.syncScrollFactor(); + } + get scrollFactorY() { + return this._scrollFactorY; + } + + set scrollFactorY(value) { + if (this._scrollFactorY === value) { + return; + } + + this._scrollFactorY = value; + this.syncScrollFactor(); + } + + // Compatiable with container plugin + get list() { + return this.children; + } + + static GetParent(child) { + return GetParent(child); + } + + // For p3-container + get parentContainer() { + return this._parentContainer; + } + + set parentContainer(value) { + // Initialize + if (!this._parentContainer && !value) { + this._parentContainer = value; + return; + } + + // Set this._parentContainer only, + // if under AddToContainer, or RemoveFromContainer methods + if (this.setParentContainerFlag) { + this._parentContainer = value; + return; + } + // else if (!this.setParentContainerFlag) + + // Add itself and all children to container, + // Or remove itseld and all children from container + if (this._parentContainer && !value) { + // Remove from container + this.removeFromContainer(); + this._parentContainer = value; + } else if (value) { + // Add to container + this._parentContainer = value; + this.addToContainer(value); + } else { + this._parentContainer = value; + } + } + + get setParentContainerFlag() { + if (this._setParentContainerFlag) { + return true; + } + var parent = GetParent(this); + return (parent) ? parent.setParentContainerFlag : false; + } + +} + +Object.assign( + ContainerLite.prototype, + Methods +); + +export default ContainerLite; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Creator.js new file mode 100644 index 000000000..5c098a686 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Creator.js @@ -0,0 +1,25 @@ +import ContainerLite from './ContainerLite.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const GetValue = Phaser.Utils.Objects.GetValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', 1); + var height = GetAdvancedValue(config, 'height', width); + var children = GetValue(config, 'children', undefined); + var gameObject = new ContainerLite(this.scene, 0, 0, width, height); + + // set properties wo modify children + gameObject.syncChildrenEnable = false; + BuildGameObject(this.scene, gameObject, config); + // sync properties of children + gameObject.syncChildrenEnable = true; + + gameObject.add(children); + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Depth.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Depth.js new file mode 100644 index 000000000..c945e6d16 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Depth.js @@ -0,0 +1,85 @@ +import SortGameObjectsByDepth from '../../../utils/system/SortGameObjectsByDepth.js'; + +export default { + setDepth(value, containerOnly) { + this.depth = value; + if (!containerOnly && this.children) { + var children = this.getAllChildren(); + for (var i = 0, cnt = children.length; i < cnt; i++) { + children[i].depth = value; + } + } + return this; + }, + + swapDepth(containerB) { + var depthA = this.depth; + var depthB = containerB.depth; + this.setDepth(depthB); + containerB.setDepth(depthA); + return this; + }, + + incDepth(inc) { + this.depth += inc; + if (this.children) { + var children = this.getAllChildren(); + for (var i = 0, cnt = children.length; i < cnt; i++) { + children[i].depth += inc; + } + } + return this; + }, + + bringToTop() { + var displayList = this.displayList; + var children = this.getAllChildren([this]); + SortGameObjectsByDepth(children, false); + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if (displayList.exists(child)) { + displayList.bringToTop(child); + } + } + return this; + }, + + moveDepthBelow(gameObject) { + var displayList = this.displayList; + if (gameObject.displayList !== displayList) { + // Do nothing if not at the same display list + return this; + } + + var children = this.getAllChildren([this]); + SortGameObjectsByDepth(children, false); + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if (displayList.exists(child)) { + displayList.moveBelow(gameObject, child); + break; + } + } + return this; + }, + + moveDepthAbove(gameObject) { + var displayList = this.displayList; + if (gameObject.displayList !== displayList) { + // Do nothing if not at the same display list + return this; + } + + var children = this.getAllChildren([this]); + SortGameObjectsByDepth(children, true); + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if (displayList.exists(child)) { + displayList.moveAbove(gameObject, child); + break; + } + } + return this; + }, + +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/DrawBounds.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/DrawBounds.js new file mode 100644 index 000000000..89592ddf1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/DrawBounds.js @@ -0,0 +1,24 @@ +import Draw from '../../../utils/bounds/DrawBounds.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var DrawBounds = function (graphics, config) { + var drawContainer = GetValue(config, 'drawContainer', true); + + var gameObjects = GetValue(config, 'children'); + if (gameObjects === undefined) { + gameObjects = this.getAllVisibleChildren([this]); + } + + if (!drawContainer) { + gameObjects = gameObjects.filter(function (gameObject) { + return !gameObject.isRexContainerLite; + }) + } + + Draw(gameObjects, graphics, config); + + return this; +} + +export default DrawBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Factory.js new file mode 100644 index 000000000..b3bbf910a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Factory.js @@ -0,0 +1,7 @@ +import ContainerLite from './ContainerLite.js'; + +export default function (x, y, width, height, children) { + var gameObject = new ContainerLite(this.scene, x, y, width, height, children); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/GetParent.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/GetParent.js new file mode 100644 index 000000000..c58650e95 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/GetParent.js @@ -0,0 +1,28 @@ +var GetParent = function (gameObject, name) { + var parent; + if (name === undefined) { + if (gameObject.hasOwnProperty('rexContainer')) { + parent = gameObject.rexContainer.parent; + } + } else { + parent = GetParent(gameObject); + while (parent) { + if (parent.name === name) { + break; + } + parent = GetParent(parent); + } + } + return parent; +} + +var GetTopmostParent = function (gameObject) { + var parent = GetParent(gameObject); + while (parent) { + gameObject = parent; + parent = GetParent(parent); + } + return gameObject; +} + +export { GetParent, GetTopmostParent }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Layer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Layer.js new file mode 100644 index 000000000..2b7f46335 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Layer.js @@ -0,0 +1,89 @@ +import GetLocalState from './utils/GetLocalState.js'; + +export default { + enableLayer() { + if (this.privateRenderLayer) { + return this; + } + + var layer = this.scene.add.layer(); + // layer.name = (this.name) ? `${this.name}.privateLayer` : 'privateLayer'; + + this.moveDepthBelow(layer); + + this.addToLayer(layer); + + this.privateRenderLayer = layer; + + return this; + }, + + getLayer() { + if (!this.privateRenderLayer) { + this.enableLayer(); + } + + return this.privateRenderLayer; + }, + + getRenderLayer() { + // This containerLite has a layer + if (this.privateRenderLayer) { + return this.privateRenderLayer; + } + + // One of parent container has a layer + var parent = this.getParent(); + while (parent) { + var layer = parent.privateRenderLayer; + if (layer) { + return layer; + } + parent = parent.getParent(); + } + + return null; + }, + + // Internal method for adding child + addToRenderLayer(gameObject) { + // Don't add to layer if gameObject is not in any displayList + if (!gameObject.displayList) { + return this; + } + + // Move gameObject from scene to layer + var layer = this.getRenderLayer(); + if (!layer) { + return this; + } + + if (gameObject.isRexContainerLite) { + // Add containerLite and its children + gameObject.addToLayer(layer); + } else { + // Add gameObject directly + layer.add(gameObject); + } + + var state = GetLocalState(gameObject); + state.layer = layer; + + return this; + }, + + // Internal method for removing child + removeFromRenderLayer(gameObject) { + // Move gameObject from layer to scene + var state = GetLocalState(gameObject); + var layer = state.layer; + if (!layer) { + return this; + } + + layer.remove(gameObject); + state.layer = null; + + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Mask.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Mask.js new file mode 100644 index 000000000..b4abefe6c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Mask.js @@ -0,0 +1,44 @@ +export default { + updateChildMask(child) { + // Don't propagate null mask to clear children's mask + if (this.mask == null) { + return this; + } + + var maskGameObject = (this.mask.hasOwnProperty('geometryMask')) ? this.mask.geometryMask : this.mask.bitmapMask; + if (maskGameObject !== child) { + child.mask = this.mask; + } + return this; + }, + + syncMask() { + if (this.syncChildrenEnable) { + this.children.forEach(this.updateChildMask, this); + } + return this; + }, + + setMask(mask) { + this.mask = mask; + return this; + }, + + clearMask(destroyMask) { + if (destroyMask === undefined) { + destroyMask = false; + } + + // Clear current mask + this._mask = null; + // Clear children mask + this.children.forEach(function (child) { + child.clearMask(false); + }); + + if (destroyMask && this.mask) { + this.mask.destroy(); + } + return this; + }, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Methods.js new file mode 100644 index 000000000..764aef551 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Methods.js @@ -0,0 +1,52 @@ +import Parent from './Parent.js'; +import AddChild from './AddChild.js'; +import RemoveChild from './RemoveChild.js'; +import ChildState from './ChildState.js'; +import Transform from './Transform.js'; +import Position from './Position.js'; +import Rotation from './Rotation.js'; +import Scale from './Scale.js'; +import Visible from './Visible.js'; +import Alpha from './Alpha.js'; +import Active from './Active.js'; +import ScrollFactor from './ScrollFactor.js'; +import Mask from './Mask.js'; +import Depth from './Depth.js'; +import Children from './Children.js'; +import Tween from './Tween.js'; +import P3Container from './P3Container.js'; +import Layer from './Layer.js'; +import RenderTexture from './RenderTexture.js'; + +import DrawBounds from './DrawBounds.js'; +import ChangeOrigin from './ChangeOrigin.js'; + +var methods = { + changeOrigin: ChangeOrigin, + drawBounds: DrawBounds, +}; + +Object.assign( + methods, + Parent, + AddChild, + RemoveChild, + ChildState, + Transform, + Position, + Rotation, + Scale, + Visible, + Alpha, + Active, + ScrollFactor, + Mask, + Depth, + Children, + Tween, + P3Container, + Layer, + RenderTexture, +); + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/P3Container.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/P3Container.js new file mode 100644 index 000000000..99276872b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/P3Container.js @@ -0,0 +1,86 @@ +import SortGameObjectsByDepth from '../../../utils/system/SortGameObjectsByDepth.js'; + +export default { + addToContainer(p3Container) { + this._setParentContainerFlag = true; + + var gameObjects = this.getAllChildren([this]); + SortGameObjectsByDepth(gameObjects); + p3Container.add(gameObjects); + + this._setParentContainerFlag = false; + return this; + }, + + addToLayer(layer) { + this.addToContainer(layer); + return this; + }, + + removeFromContainer() { + if (!this.parentContainer) { + return this; + } + + // Will add gameObjects to scene + var gameObjects = this.getAllChildren([this]) + .filter(function (gameObject) { + return !!gameObject.scene; + }); + + if (gameObjects.length === 0) { + return this; + } + + this._setParentContainerFlag = true; + + if (gameObjects.length > 1) { + SortGameObjectsByDepth(gameObjects); + gameObjects.reverse(); + } + + this.parentContainer.remove(gameObjects); + + this._setParentContainerFlag = false; + return this; + }, + + getParentContainer() { + if (this.parentContainer) { + return this.parentContainer; + } + + // One of parent container has a layer + var parent = this.getParent(); + while (parent) { + var p3Container = parent.parentContainer; + if (p3Container) { + return p3Container; + } + parent = parent.getParent(); + } + + return null; + }, + + addToParentContainer(gameObject) { + // Don't add to layer if gameObject is not in any displayList + if (!gameObject.displayList) { + return this; + } + var p3Container = this.getParentContainer(); + if (!p3Container) { + return this; + } + + if (gameObject.isRexContainerLite) { + // Add containerLite and its children + gameObject.addToContainer(p3Container); + } else { + // Add gameObject directly + p3Container.add(gameObject); + } + + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Parent.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Parent.js new file mode 100644 index 000000000..dfecb677d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Parent.js @@ -0,0 +1,37 @@ +import { GetParent, GetTopmostParent } from './GetParent.js'; +import GetLocalState from './utils/GetLocalState.js'; + +export default { + setParent(gameObject, parent) { + if (parent === undefined) { + parent = this; + } + var localState = GetLocalState(gameObject); + if (parent) { // Add to parent + localState.parent = parent; + localState.self = gameObject; + } else { // Remove from parent + localState.parent = null; + localState.self = null; + } + return this; + }, + + getParent(gameObject, name) { + if (typeof (gameObject) === 'string') { + name = gameObject; + gameObject = undefined; + } + if (gameObject === undefined) { + gameObject = this; + } + return GetParent(gameObject, name); + }, + + getTopmostParent(gameObject) { + if (gameObject === undefined) { + gameObject = this; + } + return GetTopmostParent(gameObject); + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Position.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Position.js new file mode 100644 index 000000000..917d2cb29 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Position.js @@ -0,0 +1,77 @@ +import GetLocalState from './utils/GetLocalState.js'; +import GetScale from './utils/GetScale.js'; + +export default { + updateChildPosition(child) { + if (child.isRexContainerLite) { + child.syncChildrenEnable = false; + } + var state = GetLocalState(child); + var parent = state.parent; + + if (state.syncPosition) { + child.x = state.x; + child.y = state.y; + parent.localToWorld(child); + } + + if (state.syncRotation) { + child.rotation = state.rotation + parent.rotation; + } + + if (state.syncScale) { + child.scaleX = state.scaleX * parent.scaleX; + child.scaleY = state.scaleY * parent.scaleY; + } + + if (child.isRexContainerLite) { + child.syncChildrenEnable = true; + child.syncPosition(); + } + return this; + }, + + syncPosition() { + if (this.syncChildrenEnable) { + this.children.forEach(this.updateChildPosition, this); + } + return this; + }, + + resetChildPositionState(child) { + var state = GetLocalState(child); + var parent = state.parent; + state.x = child.x; + state.y = child.y; + parent.worldToLocal(state); + + state.scaleX = GetScale(child.scaleX, parent.scaleX); + state.scaleY = GetScale(child.scaleY, parent.scaleY); + + state.rotation = child.rotation - parent.rotation; + return this; + }, + + setChildPosition(child, x, y) { + child.x = x; + child.y = y; + this.resetChildPositionState(child); + return this; + }, + + setChildLocalPosition(child, x, y) { + var state = GetLocalState(child); + state.x = x; + state.y = y; + this.updateChildPosition(child); + return this; + }, + + resetLocalPositionState() { + var parent = GetLocalState(this).parent; + if (parent) { + parent.resetChildPositionState(this); + } + return this; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/RemoveChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/RemoveChild.js new file mode 100644 index 000000000..b209a9d7c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/RemoveChild.js @@ -0,0 +1,51 @@ +import Base from './Base.js'; +import { GetParent } from './GetParent.js'; + +const BaseRemove = Base.prototype.remove; +const BaseClear = Base.prototype.clear; + +export default { + // Can override this method + remove(gameObject, destroyChild) { + if (GetParent(gameObject) !== this) { + return this; + } + this.setParent(gameObject, null); + + if (!destroyChild) { + this.removeFromRenderLayer(gameObject); + } + + BaseRemove.call(this, gameObject, destroyChild); + return this; + }, + + // Don't override this method + unpin(gameObject, destroyChild) { + if (GetParent(gameObject) !== this) { + return this; + } + this.setParent(gameObject, null); + + if (!destroyChild) { + this.removeFromRenderLayer(gameObject); + } + + BaseRemove.call(this, gameObject, destroyChild); + return this; + }, + + clear(destroyChild) { + var children = this.children; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + this.setParent(child, null); + + if (!destroyChild) { + this.removeFromRenderLayer(child); + } + } + BaseClear.call(this, destroyChild); + return this; + }, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/RenderTexture.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/RenderTexture.js new file mode 100644 index 000000000..d8a711e39 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/RenderTexture.js @@ -0,0 +1,36 @@ +import Snapshot from '../../../utils/rendertexture/Snapshot.js'; + +export default { + snapshot(config) { + // Save scale + var scaleXSave = this.scaleX; + var scaleYSave = this.scaleY; + var scale1 = (scaleXSave === 1) && (scaleYSave === 1); + if (!scale1) { + this.setScale(1); + } + + // Snapshot with scale = 1 + if (config === undefined) { + config = {}; + } + config.gameObjects = this.getAllVisibleChildren(); + config.x = this.x; + config.y = this.y; + config.originX = this.originX; + config.originY = this.originY; + var rt = Snapshot(config); + var isValidRT = !!rt.scene; + + // Restore scale + if (!scale1) { + this.setScale(scaleXSave, scaleYSave); + + if (isValidRT) { + rt.setScale(scaleXSave, scaleYSave); + } + } + + return (isValidRT) ? rt : this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Rotation.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Rotation.js new file mode 100644 index 000000000..29eaf2809 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Rotation.js @@ -0,0 +1,62 @@ +import GetLocalState from './utils/GetLocalState.js'; + +const DegToRad = Phaser.Math.DegToRad; + +export default { + updateChildRotation(child) { + var state = GetLocalState(child); + var parent = state.parent; + if (state.syncRotation) { + child.rotation = parent.rotation + state.rotation; + } + return this; + }, + + syncRotation() { + if (this.syncChildrenEnable) { + this.children.forEach(this.updateChildRotation, this); + } + return this; + }, + + resetChildRotationState(child) { + var state = GetLocalState(child); + var parent = state.parent; + state.rotation = child.rotation - parent.rotation; + return this; + }, + + setChildRotation(child, rotation) { + child.rotation = rotation; + this.resetChildRotationState(child); + return this; + }, + + setChildAngle(child, angle) { + child.angle = angle; + this.resetChildRotationState(child); + return this; + }, + + setChildLocalRotation(child, rotation) { + var state = GetLocalState(child); + state.rotation = rotation; + this.updateChildRotation(child); + return this; + }, + + setChildLocalAngle(child, angle) { + var state = GetLocalState(child); + state.rotation = DegToRad(angle); + this.updateChildRotation(child); + return this; + }, + + resetLocalRotationState() { + var parent = GetLocalState(this).parent; + if (parent) { + parent.resetChildRotationState(this); + } + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Scale.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Scale.js new file mode 100644 index 000000000..83e472062 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Scale.js @@ -0,0 +1,64 @@ +import GetLocalState from './utils/GetLocalState.js'; +import GetScale from './utils/GetScale.js'; + +export default { + updateChildScale(child) { + var state = GetLocalState(child); + var parent = state.parent; + if (state.syncScale) { + child.scaleX = parent.scaleX * state.scaleX; + child.scaleY = parent.scaleY * state.scaleY; + } + return this; + }, + + syncScale() { + if (this.syncChildrenEnable) { + this.children.forEach(this.updateChildScale, this); + } + return this; + }, + + resetChildScaleState(child) { + var state = GetLocalState(child); + var parent = state.parent; + state.scaleX = GetScale(child.scaleX, parent.scaleX); + state.scaleY = GetScale(child.scaleY, parent.scaleY); + return this; + }, + + setChildScale(child, scaleX, scaleY) { + if (scaleY === undefined) { + scaleY = scaleX; + } + child.scaleX = scaleX; + child.scaleY = scaleY; + this.resetChildScaleState(child); + return this; + }, + + setChildLocalScale(child, scaleX, scaleY) { + if (scaleY === undefined) { + scaleY = scaleX; + } + var state = GetLocalState(child); + state.scaleX = scaleX; + state.scaleY = scaleY; + this.updateChildScale(child); + return this; + }, + + setChildDisplaySize(child, width, height) { + child.setDisplaySize(width, height); + this.resetChildScaleState(child); + return this; + }, + + resetLocalScaleState() { + var parent = GetLocalState(this).parent; + if (parent) { + parent.resetChildScaleState(this); + } + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ScrollFactor.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ScrollFactor.js new file mode 100644 index 000000000..6cf57330f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/ScrollFactor.js @@ -0,0 +1,22 @@ +import GetLocalState from './utils/GetLocalState.js'; + +export default { + updateChildScrollFactor(child) { + var state = GetLocalState(child); + var parent = state.parent; + + if (state.syncScrollFactor) { + child.setScrollFactor(parent.scrollFactorX, parent.scrollFactorY); + } + + return this; + }, + + syncScrollFactor() { + if (this.syncChildrenEnable) { + this.children.forEach(this.updateChildScrollFactor, this); + } + return this; + }, + +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Transform.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Transform.js new file mode 100644 index 000000000..467e5f502 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Transform.js @@ -0,0 +1,27 @@ +const RotateAround = Phaser.Math.RotateAround; + +export default { + worldToLocal(point) { + // Transform + point.x -= this.x; + point.y -= this.y; + // Rotate + RotateAround(point, 0, 0, -this.rotation); + // Scale + point.x /= this.scaleX; + point.y /= this.scaleY; + return point; + }, + + localToWorld(point) { + // Scale + point.x *= this.scaleX; + point.y *= this.scaleY; + // Rotate + RotateAround(point, 0, 0, this.rotation); + // Transform + point.x += this.x; + point.y += this.y; + return point; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Tween.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Tween.js new file mode 100644 index 000000000..e1055972a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Tween.js @@ -0,0 +1,118 @@ +var GetLocalStates = function (gameObjects) { + var localStates = [] + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + var gameObject = gameObjects[i]; + if (!gameObject.hasOwnProperty('rexContainer')) { + continue; + } + localStates.push(gameObject.rexContainer); + } + return localStates; +} + +var GetScene = function (gameObjects) { + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + var scene = gameObjects[i].scene; + if (scene) { + return scene; + } + } + return null; +} + +var UpdateChild = function (tween, key, target) { + if (!target.parent) { + // target object was removed, so remove this tween too + tween.remove(); + return; + } + + var parent = target.parent; + var child = target.self; + switch (key) { + case 'x': + case 'y': + parent.updateChildPosition(child); + break; + + case 'angle': + case 'rotation': + parent.updateChildRotation(child); + break; + + case 'scaleX': + case 'scaleY': + case 'displayWidth': + case 'displayHeight': + parent.updateChildScale(child); + break; + + case 'alpha': + parent.updateChildAlpha(child); + break; + + default: + parent.updateChildPosition(child); + parent.updateChildRotation(child); + parent.updateChildScale(child); + parent.updateChildAlpha(child); + break; + } +}; + +export default { + tweenChild(tweenConfig) { + var targets = tweenConfig.targets; + if (!Array.isArray(targets)) { + targets = [targets]; + } + + var scene = this.scene || GetScene(targets); + if (!scene) { + return; + } + + // Map child game objects to local states + tweenConfig.targets = GetLocalStates(targets); + var tween = scene.tweens.add(tweenConfig); + + // Update child game object in 'update' event + tween.on('update', UpdateChild); + + return tween; + }, + + tweenSelf(tweenConfig) { + tweenConfig.targets = [this]; + return this.tweenChild(tweenConfig); + }, + + createTweenChildConfig(tweenConfig) { + var targets = tweenConfig.targets; + if (targets) { + if (!Array.isArray(targets)) { + targets = [targets]; + } + // Map child game objects to local states + tweenConfig.targets = GetLocalStates(targets); + } + + var onUpdate = tweenConfig.onUpdate; + tweenConfig.onUpdate = function (tween, target) { + if (onUpdate) { + onUpdate(tween, target); + } + UpdateChild(tween, undefined, target); + } + + return tweenConfig; + }, + + tween(tweenConfig) { + var scene = this.scene; + if (!tweenConfig.targets) { + tweenConfig.targets = this; + } + return scene.tweens.add(tweenConfig); + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Visible.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Visible.js new file mode 100644 index 000000000..3be4e85d3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/Visible.js @@ -0,0 +1,77 @@ +/* + +Visible in localState: + + - visible: original visible of child + - maskVisible: invisible by parent mask, see MaskChildren.js + - undefined (not in masking) : Equal to mask visible + - true (mask visible) : Inside, or across parent's visible area + - false (maske invisible) : Out of parent's visible area + +Visible result of child = (parent visible) && (child visible) && (mask visible) +*/ + +import GetLocalState from './utils/GetLocalState.js'; + +export default { + updateChildVisible(child) { + var localState = GetLocalState(child); + var parent = localState.parent; + var maskVisible = (localState.hasOwnProperty('maskVisible')) ? localState.maskVisible : true; + child.visible = parent.visible && localState.visible && maskVisible; + return this; + }, + + syncVisible() { + if (this.syncChildrenEnable) { + this.children.forEach(this.updateChildVisible, this); + } + return this; + }, + + resetChildVisibleState(child) { + var localState = GetLocalState(child); + // Delete maskVisible property + if (localState.hasOwnProperty('maskVisible')) { + delete localState.maskVisible; + } + localState.visible = child.visible; + return this; + }, + + setChildVisible(child, visible) { + // Visible of child will be affect by parent's visible, and mask visible + this.setChildLocalVisible(child, visible); + return this; + }, + + // Internal method + setChildLocalVisible(child, visible) { + if (visible === undefined) { + visible = true; + } + var localState = GetLocalState(child); + localState.visible = visible; + this.updateChildVisible(child); + return this; + }, + + // Internal method + setChildMaskVisible(child, visible) { + if (visible === undefined) { + visible = true; + } + var localState = GetLocalState(child); + localState.maskVisible = visible; + this.updateChildVisible(child); + return this; + }, + + resetLocalVisibleState() { + var parent = GetLocalState(this).parent; + if (parent) { + parent.resetChildVisibleState(this); + } + return this; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/mask/AddChildMask.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/mask/AddChildMask.js new file mode 100644 index 000000000..87545d333 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/mask/AddChildMask.js @@ -0,0 +1,17 @@ +import DefaultMaskGraphics from '../../../../utils/mask/defaultmaskgraphics/DefaultMaskGraphics.js'; + +var AddChildMask = function (maskTarget, sizeTarget, shape, padding) { + var maskGameObject = new DefaultMaskGraphics(sizeTarget, shape, padding); // A Graphics game object + if (maskTarget && !maskTarget.isRexSizer) { // Sizer game object can't apply mask + var mask = maskGameObject.createGeometryMask(); + maskTarget.setMask(mask); + this.once('destroy', function () { + maskTarget.setMask(); + mask.destroy(); + }) + } + this.pin(maskGameObject); + return maskGameObject; +} + +export default AddChildMask; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/mask/ChildrenMaskMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/mask/ChildrenMaskMethods.js new file mode 100644 index 000000000..6d3546cb8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/mask/ChildrenMaskMethods.js @@ -0,0 +1,112 @@ +import MaskChildren from './MaskChildren.js'; +import AddChildMask from './AddChildMask.js'; +import MaskToGameObject from '../../../../utils/mask/MaskToGameObject.js' + +const GetValue = Phaser.Utils.Objects.GetValue; + +const MASKUPDATEMODE = { + update: 0, + everyTick: 1 +}; + +export default { + setupChildrenMask(config) { + if (config === false) { + // No children mask + return this; + } + + this.setMaskUpdateMode(GetValue(config, 'updateMode', 0)); + this.enableChildrenMask(GetValue(config, 'padding', 0)); + this.setMaskLayer(GetValue(config, 'layer', undefined)); + this.startMaskUpdate(); + + return this; + }, + + destroyChildrenMask() { + if (!this.childrenMask) { + return this; + } + + this.stopMaskUpdate(); + this.childrenMask.destroy(); + this.childrenMask = undefined; + + return this; + }, + + setMaskUpdateMode(mode) { + if (typeof (mode) === 'string') { + mode = MASKUPDATEMODE[mode]; + } + this.maskUpdateMode = mode; + return this; + }, + + startMaskUpdate() { + this.scene.game.events.on('poststep', this.maskChildren, this); + }, + + stopMaskUpdate() { + this.scene.game.events.off('poststep', this.maskChildren, this); + }, + + enableChildrenMask(maskPadding) { + var maskGameObject = AddChildMask.call(this, null, this, 0, maskPadding); + this.childrenMask = maskGameObject.createGeometryMask(); + // this.childrenMask is a mask object, not a (Graphics) game object + return this; + }, + + setMaskChildrenFlag(value) { + if (value === undefined) { + value = true; + } + this.maskChildrenFlag = value; + return this; + }, + + setMaskLayer(layer) { + // To reduce amount of masked game object + this.maskLayer = layer; + return this; + }, + + maskChildren() { + if ( + (!this.childrenMask) || // No childrenMask + (!this.maskChildrenFlag) || // No maskChildrenFlag set + (this.alpha === 0) || (!this.visible) // Parent is not visible + ) { + return this; + } + + if (this.privateRenderLayer) { + this.privateRenderLayer.setMask(this.childrenMask); + } else if (this.maskLayer) { + // 1. Add parent and children into layer + this.addToLayer(this.maskLayer); + // 2. Mask this layer + this.maskLayer.setMask(this.childrenMask); + } else { + MaskChildren(this, this.childrenMask); + } + + if (this.maskUpdateMode === 0) { + this.maskChildrenFlag = false; + } + return this; + }, + + layoutChildrenMask() { + if (!this.childrenMask) { + return this; + } + + var maskGameObject = MaskToGameObject(this.childrenMask); + maskGameObject.setPosition().resize(); + this.resetChildPositionState(maskGameObject); + return this; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/mask/MaskChildren.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/mask/MaskChildren.js new file mode 100644 index 000000000..677f54137 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/mask/MaskChildren.js @@ -0,0 +1,113 @@ +import MaskToGameObject from '../../../../utils/mask/MaskToGameObject.js'; + +const Intersects = Phaser.Geom.Intersects.RectangleToRectangle; +const Overlaps = Phaser.Geom.Rectangle.Overlaps; + +var MaskChildren = function (parent, mask, children) { + if (!mask) { + return; + } + + if (children === undefined) { + children = parent.getAllChildren(); + } + + var parentBounds = parent.getBounds(); + var maskGameObject = MaskToGameObject(mask); + + var child, childBounds, visiblePointsNumber; + for (var i = 0, cnt = children.length; i < cnt; i++) { + child = children[i]; + if (child.hasOwnProperty('isRexContainerLite')) { + continue; + } + if (child === maskGameObject) { + continue; + } + if (!IsVisible(child)) { // Child is invisible before masking + continue; + } + + if (child.getBounds) { + childBounds = child.getBounds(childBounds); + visiblePointsNumber = ContainsPoints(parentBounds, childBounds); + switch (visiblePointsNumber) { + case 4: // 4 points are all inside visible window, set visible + ShowAll(parent, child, mask); + break; + case 0: // No point is inside visible window + // Parent intersects with child, or parent is inside child, set visible, and apply mask + if (Intersects(parentBounds, childBounds) || Overlaps(parentBounds, childBounds)) { + ShowSome(parent, child, mask); + } else { // Set invisible + ShowNone(parent, child, mask); + } + break; + default: // Part of points are inside visible window, set visible, and apply mask + ShowSome(parent, child, mask); + break; + } + } else { + ShowSome(parent, child, mask); + } + } +} + +var IsVisible = function (gameObject) { + if (!gameObject.displayList) { + return false; + } + + while (1) { + var localState = gameObject.rexContainer; + if (!localState) { // Top game object + return gameObject.visible; + } else if (localState.visible) { + var parent = localState.parent; + if (parent) { // Test parent's visible + gameObject = parent; + continue; + } else { // Top visible game object + return true; + } + } else { // Current game object is invisible + return false; + } + } +} + +var ContainsPoints = function (rectA, rectB) { + var result = 0; + var top = rectB.top, + bottom = rectB.bottom, + left = rectB.left, + right = rectB.right; + result += rectA.contains(left, top) ? 1 : 0; + result += rectA.contains(left, bottom) ? 1 : 0; + result += rectA.contains(right, top) ? 1 : 0; + result += rectA.contains(right, bottom) ? 1 : 0; + return result; +}; + +var ShowAll = function (parent, child, mask) { + parent.setChildMaskVisible(child, true); + if (child.clearMask) { + child.clearMask(); + } +} + +var ShowSome = function (parent, child, mask) { + parent.setChildMaskVisible(child, true); + if (child.setMask) { + child.setMask(mask); + } +} + +var ShowNone = function (parent, child, mask) { + parent.setChildMaskVisible(child, false); + if (child.clearMask) { + child.clearMask(); + } +} + +export default MaskChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/Enter.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/Enter.js new file mode 100644 index 000000000..0b067d0b2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/Enter.js @@ -0,0 +1,43 @@ +import Exit from './Exit.js'; +import Snapshot from '../../../../utils/rendertexture/Snapshot.js'; + +var Enter = function (parentContainer, rtOwner) { + if (!parentContainer) { + return false; + } + + Exit(parentContainer, rtOwner); + + // Get and paste all visible children, which dose not include this render texture + var useParentBounds = rtOwner.useParentBounds; + Snapshot({ + gameObjects: parentContainer.getAllVisibleChildren(), + renderTexture: rtOwner.rt, + x: rtOwner.x, + y: rtOwner.y, + width: ((useParentBounds) ? parentContainer.displayWidth : undefined), + height: ((useParentBounds) ? parentContainer.displayHeighth : undefined), + originX: ((useParentBounds) ? parentContainer.originX : undefined), + originY: ((useParentBounds) ? parentContainer.originY : undefined), + }); + + // Set rtOwner to be visible + parentContainer.setChildVisible(rtOwner, true); + + // Set visible sibling to be invisible + var visibleSibling = rtOwner.visibleSibling; + var children = parentContainer.children; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if ((child.visible) && (child !== rtOwner)) { + parentContainer.setChildVisible(child, false); + visibleSibling.push(child); + } + } + + rtOwner.isRunning = true; + + return true; +} + +export default Enter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/Exit.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/Exit.js new file mode 100644 index 000000000..1bde7e46b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/Exit.js @@ -0,0 +1,21 @@ +var Exit = function (parentContainer, rtOwner) { + if (!parentContainer) { + return false; + } + + var visibleSibling = rtOwner.visibleSibling; + // Set all visible children back + for (var i = 0, cnt = visibleSibling.length; i < cnt; i++) { + parentContainer.setChildVisible(visibleSibling[i], true); + } + visibleSibling.length = 0; + + // Set rtOwner to be invisible + parentContainer.setChildVisible(rtOwner, false); + + rtOwner.isRunning = false; + + return true; +} + +export default Exit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/Init.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/Init.js new file mode 100644 index 000000000..6da49484a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/Init.js @@ -0,0 +1,14 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +var Init = function (parentContainer, rtOwner, config) { + rtOwner.visibleSibling = []; + rtOwner.isRunning = false; + rtOwner.useParentBounds = GetValue(config, 'useParentBounds', false); + + rtOwner + .setPosition(parentContainer.x, parentContainer.y) + .setVisible(false) + parentContainer.pin(rtOwner); +} + +export default Init; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/MeshRenderTextureBase.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/MeshRenderTextureBase.js new file mode 100644 index 000000000..c3007d8e4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/rendertexture/MeshRenderTextureBase.js @@ -0,0 +1,40 @@ +import Init from './Init.js'; +import Enter from './Enter.js'; +import Exit from './Exit.js'; + +var MeshRenderTextureBase = function (RenderTextureOwnerClass) { + return class Base extends RenderTextureOwnerClass { + constructor(parentContainer, config) { + var scene = parentContainer.scene; + super(scene, 0, 0, 1, 1, config); + scene.add.existing(this); + + Init(parentContainer, this, config); + } + + destroy(fromScene) { + if (!this.scene || this.ignoreDestroy) { + return; + } + + this.exit(); + super.destroy(fromScene); + } + + enter() { + var result = Enter(this.rexContainer.parent, this); + if (result) { + this.syncSize(); + } + + return this; + } + + exit() { + Exit(this.rexContainer.parent, this); + return this; + } + } +} + +export default MeshRenderTextureBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/utils/GetLocalState.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/utils/GetLocalState.js new file mode 100644 index 000000000..7311f4bc2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/utils/GetLocalState.js @@ -0,0 +1,46 @@ +const DegToRad = Phaser.Math.DegToRad; +const RadToDeg = Phaser.Math.RadToDeg; + +var GetLocalState = function (gameObject) { + if (!gameObject.hasOwnProperty('rexContainer')) { + var rexContainer = { + parent: null, self: null, layer: null, + x: 0, y: 0, syncPosition: true, + rotation: 0, syncRotation: true, + scaleX: 0, scaleY: 0, syncScale: true, + alpha: 0, syncAlpha: true, + visible: true, + active: true, + }; + + Object.defineProperty(rexContainer, 'angle', { + get: function () { + return RadToDeg(this.rotation); + }, + set: function (value) { + this.rotation = DegToRad(value); + } + }); + Object.defineProperty(rexContainer, 'displayWidth', { + get: function () { + return gameObject.width * this.scaleX; + }, + set: function (width) { + this.scaleX = width / gameObject.width; + } + }); + Object.defineProperty(rexContainer, 'displayHeight', { + get: function () { + return gameObject.height * this.scaleY; + }, + set: function (height) { + this.scaleY = height / gameObject.height; + } + }); + + gameObject.rexContainer = rexContainer; + } + return gameObject.rexContainer; +} + +export default GetLocalState; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/utils/GetScale.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/utils/GetScale.js new file mode 100644 index 000000000..e14e6b376 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/utils/GetScale.js @@ -0,0 +1,9 @@ +var GetScale = function (a, b) { + if (a === b) { + return 1; + } else { + return a / b; + } +} + +export default GetScale; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/utils/Traversal.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/utils/Traversal.js new file mode 100644 index 000000000..fc1c8b446 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/containerlite/utils/Traversal.js @@ -0,0 +1,26 @@ +var DepthFirstSearch = function (root, callback) { + var skip = callback(root); + if ((!skip) && root.isRexContainerLite) { + var children = root.children; + for (var i = 0, cnt = children.length; i < cnt; i++) { + DepthFirstSearch(children[i], callback); + } + } +} + +var BreadthFirstSearch = function (root, callback) { + var queue = [root]; + while (queue.length > 0) { + var current = queue.shift(); + var skip = callback(current); + + if ((!skip) && current.isRexContainerLite) { + queue.push(...current.children); + } + } +} + +export { + DepthFirstSearch, + BreadthFirstSearch +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/Creator.js new file mode 100644 index 000000000..68a711cce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/Creator.js @@ -0,0 +1,23 @@ +import GridTable from './GridTable.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetValue(config, 'width', 256); + var height = GetValue(config, 'height', 256); + var gameObject = new GridTable(this.scene, 0, 0, width, height, config); + + // set properties wo modify children + gameObject.syncChildrenEnable = false; + BuildGameObject(this.scene, gameObject, config); + // sync properties of children + gameObject.syncChildrenEnable = true; + gameObject.syncPosition().syncVisible().syncAlpha(); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/Factory.js new file mode 100644 index 000000000..3fcd8d80e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/Factory.js @@ -0,0 +1,7 @@ +import GridTable from './GridTable.js'; + +export default function (x, y, width, height, config) { + var gameObject = new GridTable(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/GridTable.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/GridTable.d.ts new file mode 100644 index 000000000..998db3cc3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/GridTable.d.ts @@ -0,0 +1,148 @@ +import ContainerLite from '../../container/containerlite/ContainerLite'; + +export default GridTable; + +declare namespace GridTable { + + type ScrollModeType = 0 | 1 | 'v' | 'vertical' | 'h' | 'horizontal'; + + interface CellData { + scene: Phaser.Scene, + width: number, + height: number, + deltaWidth: number, + deltaHeight: number, + item: unknown, + index: number, + + setHeight(value: number): void, + setDeltaHeight(value: number): void, + setWidth(value: number): void, + setDeltaWidth(value: number): void, + + setContainer(cellContainer?: Phaser.GameObjects.GameObject | null): void, + getContainer(): Phaser.GameObjects.GameObject | null, + popContainer(): Phaser.GameObjects.GameObject | null, + destroyContainer(): this, + } + + type CellVisibleCallbackType = ( + cell: CellData, + cellContainer: Phaser.GameObjects.GameObject | null, + table: GridTable + ) => void; + + type CellInvisibleCallbackType = ( + cell: CellData + ) => void; + + type MaskUpdateModeType = 0 | 1 | 'update' | 'everyTick'; + type MaskConfig = { + padding?: number, + updateMode?: MaskUpdateModeType + } | + boolean; + + + interface IConfig { + cellsCount?: number, + columns?: number, + cellHeight?: number, + cellWidth?: number, + + cellVisibleCallback: CellVisibleCallbackType, + cellVisibleCallbackScope?: Object, + reuseCellContainer?: boolean, + + cellInvisibleCallback: CellInvisibleCallbackType, + cellInvisibleCallbackScope: undefined, + + clamplTableOXY?: boolean, + scrollMode?: ScrollModeType, + mask?: MaskConfig, + enableLayer?: boolean, + } + + namespace Events { + type CellvisibleCallbackType = ( + cell: CellData, + cellContainer: Phaser.GameObjects.GameObject | null, + table: GridTable + ) => void; + + type CellInvisibleCallbackType = (cell: CellData) => void; + + type CellHeightchange = ( + cell: CellData, + cellContainer: Phaser.GameObjects.GameObject | null, + table: GridTable + ) => void; + + type CellWidthchange = ( + cell: CellData, + cellContainer: Phaser.GameObjects.GameObject | null, + table: GridTable + ) => void; + } +} + +declare class GridTable extends ContainerLite { + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + config: GridTable.IConfig + ); + + resize(width: number, height: number): this; + + setTableOY(oy: number): this; + addTableOY(dy: number): this; + setTableOX(ox: number): this; + addTableOX(dx: number): this; + setTableOXY(ox: number, oy: number): this; + addTableOXY(dx: number, dy: number): this; + tableOY: number; + tableOX: number; + + setTableOYByPercentage(t: number): this; + t: number; + getTableOYPercentage(): number; + scrollToBottom(): this; + + scrollToRow(rowIndex: number): this; + scrollToNextRow(rowCount?: number): this; + startRowIndex: number; + + updateTable(refresh?: boolean): this; + updateVisibleCell(cellIdx: number): this; + + setGridSize(colCount: number, rowCount: number): this; + setCellsCount(count: number): this; + readonly cellsCount: number; + readonly columnCount: number; + + readonly tableHeight: number; + readonly tableWidth: number; + + readonly topTableOY: number; + readonly bottomTableOY: number; + readonly leftTableOX: number; + readonly rightTableOX: number; + + getCell(cellIndex: number): GridTable.CellData; + + pointToCellIndex(x: number, y: number): number; + + setCellHeight(cellIndex: number, cellHeight: number): this; + setCellWidth(cellIndex: number, cellWidth: number): this; + + iterateVisibleCell( + callback: (cell: GridTable.CellData) => void + ): this; + + eachVisibleCell( + callback: (cell: GridTable.CellData) => void + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/GridTable.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/GridTable.js new file mode 100644 index 000000000..0a65b9d2c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/GridTable.js @@ -0,0 +1,369 @@ +import ContainerLite from '../../container/containerlite/ContainerLite.js'; +import Table from './table/Table.js'; +import Methods from './methods/Methods.js'; + +const Group = Phaser.GameObjects.Group; +const Set = Phaser.Structs.Set; +const GetValue = Phaser.Utils.Objects.GetValue; + +class GridTable extends ContainerLite { + constructor(scene, x, y, width, height, config) { + if (config === undefined) { + config = {}; + } + super(scene, x, y, width, height); + this.type = 'rexGridTable'; + this._tableOX = 0; + this._tableOY = 0; + this.visibleCells = new Set(); + this.preVisibleCells = new Set(); + this.execeedTopState = false; + this.execeedBottomState = false; + this.execeedLeftState = false; + this.execeedRightState = false; + + var reuseCellContainer = GetValue(config, 'reuseCellContainer', false); + if (reuseCellContainer) { + this.cellContainersPool = new Group(scene); // Don't add Group into update list, I will destroy it manually + } + + var callback = GetValue(config, 'cellVisibleCallback', null); + if (callback !== null) { + var scope = GetValue(config, 'cellVisibleCallbackScope', undefined); + this.on('cellvisible', callback, scope); + } + callback = GetValue(config, 'cellInvisibleCallback', null); + if (callback !== null) { + var scope = GetValue(config, 'cellInvisibleCallbackScope', undefined); + this.on('cellinvisible', callback, scope); + } + + if (GetValue(config, 'enableLayer', false)) { + this.enableLayer(); + } + + this.setupChildrenMask(GetValue(config, 'mask', undefined)); + + this.setScrollMode(GetValue(config, 'scrollMode', 0)); + this.setClampMode(GetValue(config, 'clamplTableOXY', true)); + + // Pre-process cell size + var cellWidth, cellHeight, columns; + var scrollY = (this.scrollMode === 0); + if (scrollY) { // scroll y + cellWidth = config.cellWidth; + cellHeight = config.cellHeight; + columns = config.columns; + } else { // scroll x + cellWidth = config.cellHeight; + cellHeight = config.cellWidth; + columns = GetValue(config, 'rows', config.columns); + } + if (!columns) { + columns = 1; // Default columns + } + this.expandCellSize = (cellWidth === undefined); + if (this.expandCellSize) { + var width = (scrollY) ? this.width : this.height; + cellWidth = width / columns; + } + + config.cellWidth = cellWidth; + config.cellHeight = cellHeight; + config.columns = columns; + + this.table = new Table(this, config); + + this.updateTable(); + } + + destroy(fromScene) { // preDestroy method does not have fromScene parameter + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + this.destroyChildrenMask(); + + this.table.destroy(fromScene); + this.table = undefined; + if (this.cellContainersPool) { + this.cellContainersPool.destroy(true); + this.cellContainersPool = undefined; + } + + super.destroy(fromScene); + } + + setScrollMode(mode) { + if (typeof (mode) === 'string') { + mode = SCROLLMODE[mode.toLowerCase()]; + } + this.scrollMode = mode; + return this; + } + + setClampMode(mode) { + if (mode === undefined) { + mode = true; + } + this.clampTableOXY = mode; + return this; + } + + get tableOY() { + return this._tableOY; + } + + get tableOX() { + return this._tableOX; + } + + set tableOY(oy) { + this.setTableOY(oy).updateTable(); + } + + set tableOX(ox) { + this.setTableOX(ox).updateTable(); + } + + setTableOXY(ox, oy) { + this.setTableOY(oy).setTableOX(ox); + return this; + } + + addTableOY(dy) { + this.setTableOY(this.tableOY + dy); + return this; + } + + addTableOX(dx) { + this.setTableOX(this.tableOX + dx); + return this; + } + + addTableOXY(dx, dy) { + this.addTableOY(dy).addTableOX(dx); + return this; + } + + setTableOYByPercentage(percentage) { + this.setTableOY(-this.tableVisibleHeight * percentage); + return this; + } + + getTableOYPercentage() { + var tableVisibleHeight = this.tableVisibleHeight; + if (tableVisibleHeight === 0) { + return 0; + } + return (this.tableOY / -tableVisibleHeight); + } + + set t(value) { + this.setTableOYByPercentage(value).updateTable(); + } + + get t() { + return this.getTableOYPercentage(); + } + + scrollToBottom() { + this.t = 1; + // t will be 0 if table does not exceed visible area + if (this.t === 0) { + return this; + } + + // Table height might be expanded while cells are visible + do { + this.t = 1; + } while (this.t !== 1) + + return this; + } + + scrollToRow(rowIndex) { + // To get all height of cells + this.scrollToBottom(); + + var height = this.table.rowIndexToHeight(0, rowIndex - 1) + this.setTableOY(-height).updateTable(); + return this; + } + + scrollToNextRow(rowCount) { + if (rowCount === undefined) { + rowCount = 1; + } + this.scrollToRow(this.startRowIndex + rowCount); + return this; + } + + getCell(cellIdx) { + return this.table.getCell(cellIdx, true); + } + + getCellContainer(cellIdx) { + var cell = this.table.getCell(cellIdx, false); + var container; + if (cell) { + container = cell.getContainer(); + } + return container; + } + + get cellsCount() { + return this.table.cellsCount; + } + + get columnCount() { + return this.table.colCount; + } + + setCellHeight(cellIdx, height) { + var cell; + if (typeof (cellIdx) === 'number') { + cell = this.table.getCell(cellIdx, true); + } else { + cell = cellIdx; + } + cell.height = height; // Only worked when scrollMode is 0 + return this; + } + + setCellWidth(cellIdx, width) { + var cell; + if (typeof (cellIdx) === 'number') { + cell = this.table.getCell(cellIdx, true); + } else { + cell = cellIdx; + } + cell.width = width; // Only worked when scrollMode is 1 + return this; + } + + get instHeight() { + return (this.scrollMode === 0) ? this.height : this.width; + } + + get instWidth() { + return (this.scrollMode === 0) ? this.width : this.height; + } + + get tableHeight() { + return this.table.totalRowsHeight; + } + + get tableWidth() { + return this.table.totalColumnWidth; + } + + get topTableOY() { + return 0; + } + + get bottomTableOY() { + return -this.tableVisibleHeight; + } + + get leftTableOX() { + return 0; + } + + get rightTableOX() { + return -this.tableVisibleWidth; + } + + get tableVisibleHeight() { + var h = this.tableHeight - this.instHeight; + if (h < 0) { + h = 0; + } + return h; + } + + get tableVisibleWidth() { + var w; + var tableWidth = this.tableWidth; + var instWidth = this.instWidth; + if (tableWidth > instWidth) { + w = tableWidth - instWidth; + } else { + w = 0; + } + return w; + }; + + get bottomLeftY() { + return -(this.displayHeight * this.originY) + this.displayHeight; + } + + get topRightX() { + return -(this.displayWidth * this.originX) + this.displayWidth; + } + + get topLeftX() { + return -(this.displayWidth * this.originX); + } + + get topLeftY() { + return -(this.displayHeight * this.originY) + } + + get bottomBound() { + if (this.scrollMode === 0) { + return this.bottomLeftY; + } else { + return this.topRightX; + } + } + + get rightBound() { + if (this.scrollMode === 0) { + return this.topRightX; + } else { + return this.bottomLeftY; + } + } + + resize(width, height) { + if ((this.width === width) && (this.height === height)) { + return this; + } + + super.resize(width, height); + + if (this.expandCellSize) { + this.table.setDefaultCellWidth(this.instWidth / this.table.colCount); + } + this.updateTable(true); + + // Layout children-mask + this.layoutChildrenMask(); + // Re-mask children + this.maskChildren(); + + return this; + } +}; + +// mixin +Object.assign( + GridTable.prototype, + Methods +); + +const SCROLLMODE = { + v: 0, + vertical: 0, + h: 1, + horizontal: 1 +}; + +const MASKUPDATEMODE = { + update: 0, + everyTick: 1 +}; + +export default GridTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/EachCell.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/EachCell.js new file mode 100644 index 000000000..78be8fc83 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/EachCell.js @@ -0,0 +1,23 @@ +// For when you know this Set will be modified during the iteration +var EachVisibleCell = function (callback, scope) { + this.visibleCells.each(callback, scope); + return this; +} + +// For when you absolutely know this Set won't be modified during the iteration +var IterateVisibleCell = function (callback, scope) { + this.visibleCells.iterate(callback, scope); + return this; +} + +var EachCell = function (callback, scope) { + this.table.cells.slice().forEach(callback, scope); + return this; +} + +var IterateCell = function (callback, scope) { + this.table.cells.forEach(callback, scope); + return this; +} + +export { EachVisibleCell, IterateVisibleCell, EachCell, IterateCell }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/InsertNewCells.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/InsertNewCells.js new file mode 100644 index 000000000..30173ad7f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/InsertNewCells.js @@ -0,0 +1,18 @@ +const Clamp = Phaser.Math.Clamp; + +var InsertNewCells = function (cellIdx, count) { + if (typeof (cellIdx) === 'object') { + cellIdx = cellIdx.index; + } + if (count === undefined) { + count = 1; + } + if (count <= 0) { + return this; + } + cellIdx = Clamp(cellIdx, 0, this.cellsCount); + this.table.insertNewCells(cellIdx, count); + return this; +} + +export default InsertNewCells; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/IsCellVisible.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/IsCellVisible.js new file mode 100644 index 000000000..2d26aa2d9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/IsCellVisible.js @@ -0,0 +1,6 @@ +var IsCellVisible = function (cellIdx) { + var cell = this.table.getCell(cellIdx, false); + return cell && this.visibleCells.contains(cell); +} + +export default IsCellVisible; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/Methods.js new file mode 100644 index 000000000..f4f98638d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/Methods.js @@ -0,0 +1,57 @@ +import SetTableOY from './SetTableOY.js'; +import SetTableOX from './SetTableOX.js'; +import ChildrenMaskMethods from '../../../container/containerlite/mask/ChildrenMaskMethods.js'; + +import ShowCells from './updatetable/ShowCells.js'; +import ShowCell from './updatetable/ShowCell.js'; +import GetCellTLX from './updatetable/GetCellTLX.js'; +import GetCellTLY from './updatetable/GetCellTLY.js'; +import HideCells from './updatetable/HideCells.js'; +import HideCell from './updatetable/HideCell.js'; +import UpdateTable from './updatetable/UpdateTable.js'; + +import IsCellVisible from './IsCellVisible.js'; +import { PointToCellIndex, PointToCellContainer } from './PointToCell.js'; +import { EachVisibleCell, IterateVisibleCell, EachCell, IterateCell } from './EachCell.js'; + +import SetCellsCount from './SetCellsCount.js'; +import InsertNewCells from './InsertNewCells.js'; +import RemoveCells from './RemoveCells.js'; +import SetColumnCount from './SetColumnCount.js'; +import SetGridSize from './SetGridSize.js'; +import UpdateVisibleCell from './UpdateVisibleCell'; + +var methods = { + setTableOY: SetTableOY, + setTableOX: SetTableOX, + + showCells: ShowCells, + showCell: ShowCell, + getCellTLX: GetCellTLX, + getCellTLY: GetCellTLY, + hideCells: HideCells, + hideCell: HideCell, + updateTable: UpdateTable, + + isCellVisible: IsCellVisible, + pointToCellIndex: PointToCellIndex, + pointToCellContainer: PointToCellContainer, + eachVisibleCell: EachVisibleCell, + iterateVisibleCell: IterateVisibleCell, + eachCell: EachCell, + iterateCell: IterateCell, + + setCellsCount: SetCellsCount, + insertNewCells: InsertNewCells, + removeCells: RemoveCells, + setColumnCount: SetColumnCount, + setGridSize: SetGridSize, + updateVisibleCell: UpdateVisibleCell +} + +Object.assign( + methods, + ChildrenMaskMethods +); + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/PointToCell.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/PointToCell.js new file mode 100644 index 000000000..9de289b5f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/PointToCell.js @@ -0,0 +1,28 @@ +var PointToCellIndex = function (x, y) { + y -= (this.y + this.topLeftY); + x -= (this.x + this.topLeftX); + var offsetTableOY = this.tableOY - ((this.scrollMode === 0) ? y : x); + var offsetTableOX = this.tableOX - ((this.scrollMode === 0) ? x : y); + + var table = this.table; + var rowIdx = table.heightToRowIndex(-offsetTableOY, 0); + var colIdx = table.widthToColIndex(-offsetTableOX); + var cellIdx = table.colRowToCellIndex(colIdx, rowIdx); + if (cellIdx === null) { + return null; + } + if (!this.isCellVisible(cellIdx)) { + return null; + } + return cellIdx; +} + +var PointToCellContainer = function (x, y) { + var cellIdx = PointToCellIndex.call(this, x, y); + if (cellIdx === null) { + return undefined; + } + return this.getCellContainer(cellIdx); +} + +export { PointToCellIndex, PointToCellContainer }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/RemoveCells.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/RemoveCells.js new file mode 100644 index 000000000..1df03e95c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/RemoveCells.js @@ -0,0 +1,38 @@ +import HideCell from './updatetable/HideCell.js'; + +var RemoveCells = function (cellIdx, count) { + if (typeof (cellIdx) === 'object') { + cellIdx = cellIdx.index; + } + if (count === undefined) { + count = 1; + } + if (cellIdx < 0) { + count += cellIdx; + cellIdx = 0; + } + if (count <= 0) { + return this; + } + // out-of-range + if (cellIdx > this.cellsCount) { + return this; + } + + var cell; + for (var i = cellIdx, endIdx = cellIdx + count; i < endIdx; i++) { + cell = this.getCell(i, false); + if (cell) { + if (this.visibleCells.contains(cell)) { + HideCell.call(this, cell); + this.visibleCells.delete(cell); + } + this.preVisibleCells.delete(cell); + } + } + + this.table.removeCells(cellIdx, count); + return this; +} + +export default RemoveCells; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetCellsCount.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetCellsCount.js new file mode 100644 index 000000000..7bf46c207 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetCellsCount.js @@ -0,0 +1,15 @@ +var SetCellsCount = function (count) { + var cellsCount = this.cellsCount; + if (cellsCount === count) { + return this; + } + + if (cellsCount > count) { + this.removeCells(count, cellsCount - count); + } else { // cellsCount < count + this.insertNewCells(cellsCount, count - cellsCount); + } + return this; +} + +export default SetCellsCount; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetColumnCount.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetColumnCount.js new file mode 100644 index 000000000..a7e100f9a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetColumnCount.js @@ -0,0 +1,9 @@ +var SetColumnCount = function (count) { + if (this.table.colCount === count) { + return this; + } + this.table.setColumnCount(count); + return this; +} + +export default SetColumnCount; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetGridSize.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetGridSize.js new file mode 100644 index 000000000..d3dcc979d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetGridSize.js @@ -0,0 +1,7 @@ +var SetGridSize = function (colCount, rowCount) { + this.setCellsCount(colCount * rowCount); + this.table.setColumnCount(colCount); + return this; +} + +export default SetGridSize; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetTableOX.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetTableOX.js new file mode 100644 index 000000000..cdedc5002 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetTableOX.js @@ -0,0 +1,43 @@ +var SetTableOX = function (ox) { + var table = this.table; + var leftTableOX = this.leftTableOX; + var rightTableOX = this.rightTableOX; + var tableOXExeceedLeft = (ox > this.leftTableOX); + var tableOXExeceedRight = (ox < this.rightTableOX); + if (this.clampTableOXY) { + var colCount = table.colCount; + var visibleColCount = table.widthToColIndex(this.instWidth, true); + + // less then 1 page + if (colCount < visibleColCount) { + ox = 0; + } else if (tableOXExeceedLeft) { + ox = leftTableOX + } else { + // var tableVisibleWidth = this.tableVisibleWidth; + if (tableOXExeceedRight) + ox = rightTableOX; + } + } + + if (this._tableOX !== ox) { + this._tableOX = ox; + } + + if (tableOXExeceedLeft) { + if (!this.execeedLeftState) { + this.emit('execeedleft', this, ox, leftTableOX); + } + } + this.execeedLeftState = tableOXExeceedLeft; + + if (tableOXExeceedRight) { + if (!this.execeedRightState) { + this.emit('execeedright', this, ox, rightTableOX); + } + } + this.execeedRightState = tableOXExeceedRight; + return this; +} + +export default SetTableOX; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetTableOY.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetTableOY.js new file mode 100644 index 000000000..372889792 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/SetTableOY.js @@ -0,0 +1,42 @@ +var SetTableOY = function (oy) { + var table = this.table; + var topTableOY = this.topTableOY; + var bottomTableOY = this.bottomTableOY; + var tableOYExceedTop = (oy > this.topTableOY); + var tableOYExeceedBottom = (oy < this.bottomTableOY); + if (this.clampTableOXY) { + var rowCount = table.rowCount; + var visibleRowCount = table.heightToRowIndex(this.instHeight, 1); + + // less then 1 page + if (rowCount < visibleRowCount) { + oy = 0; + } else if (tableOYExceedTop) { + oy = topTableOY + } else if (tableOYExeceedBottom) { + oy = bottomTableOY; + } + } + + if (this._tableOY !== oy) { + this._tableOY = oy; + } + + + if (tableOYExceedTop) { + if (!this.execeedTopState) { + this.emit('execeedtop', this, oy, topTableOY); + } + } + this.execeedTopState = tableOYExceedTop; + + if (tableOYExeceedBottom) { + if (!this.execeedBottomState) { + this.emit('execeedbottom', this, oy, bottomTableOY); + } + } + this.execeedBottomState = tableOYExeceedBottom; + return this; +} + +export default SetTableOY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/UpdateVisibleCell.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/UpdateVisibleCell.js new file mode 100644 index 000000000..45bafb0fc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/UpdateVisibleCell.js @@ -0,0 +1,14 @@ +import ShowCell from './updatetable/ShowCell'; + +var UpdateVisibleCell = function (cellIdx) { + var cell = this.table.getCell(cellIdx, false); + if (!cell || !cell.container) { + return this; + } + + ShowCell.call(this, cell); + + return this; +} + +export default UpdateVisibleCell; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/GetCellTLX.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/GetCellTLX.js new file mode 100644 index 000000000..c9102b1ee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/GetCellTLX.js @@ -0,0 +1,7 @@ +var GetCellTLX = function (colIdx) { + var ox = (this.scrollMode === 0) ? this.topLeftX : this.topLeftY; + var x = this.tableOX + this.table.colIndexToWidth(0, colIdx - 1) + ox; + return x; +} + +export default GetCellTLX; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/GetCellTLY.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/GetCellTLY.js new file mode 100644 index 000000000..30723b5c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/GetCellTLY.js @@ -0,0 +1,7 @@ +var GetCellTLY = function (rowIdx) { + var oy = (this.scrollMode === 0) ? this.topLeftY : this.topLeftX; + var y = this.tableOY + this.table.rowIndexToHeight(0, rowIdx - 1) + oy; + return y; +} + +export default GetCellTLY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/HideCell.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/HideCell.js new file mode 100644 index 000000000..70be4a48d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/HideCell.js @@ -0,0 +1,15 @@ +var HideCell = function (cell) { + // Option: pop container of cell by cell.popContainer() under this event + this.emit('cellinvisible', cell); + + if (this.cellContainersPool) { + var cellContainer = cell.popContainer(); // null if already been removed + if (cellContainer) { + this.cellContainersPool.killAndHide(cellContainer); + } + } + + cell.destroyContainer(); // Destroy container of cell +} + +export default HideCell; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/HideCells.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/HideCells.js new file mode 100644 index 000000000..d8a4f6f27 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/HideCells.js @@ -0,0 +1,11 @@ +var HideCells = function () { + var preList = this.preVisibleCells; + var curList = this.visibleCells; + preList.iterate(function (cell) { + if (!curList.contains(cell)) { + this.hideCell(cell); + } + }, this); +} + +export default HideCells; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/ShowCell.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/ShowCell.js new file mode 100644 index 000000000..610bac2ef --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/ShowCell.js @@ -0,0 +1,35 @@ +var ShowCell = function (cell) { + // Attach container to cell by cell.setContainer(container) under this event + var reusedCellContainer = null; + var cellContainer = cell.getContainer(); + if (cellContainer) { + reusedCellContainer = cellContainer; + cell.popContainer(); + } else if (this.cellContainersPool) { + reusedCellContainer = this.cellContainersPool.getFirstDead(); + if (reusedCellContainer !== null) { // Reuse this game object + reusedCellContainer.setActive(true).setVisible(true); + } + } + + this.emit('cellvisible', cell, reusedCellContainer, this); + + if (this.cellContainersPool) { + var cellContainer = cell.getContainer(); + if (cellContainer) { + if (reusedCellContainer === null) { + this.cellContainersPool.add(cellContainer); // New cell container, add to pool + } else if (reusedCellContainer !== cellContainer) { + // Why reusedCellContainer is not equal to cellContainer? + this.cellContainersPool.add(cellContainer); // New cell container, add to pool + this.cellContainersPool.killAndHide(reusedCellContainer); // Unused cell container, put back to pool + } + } else { // No cell container added + if (reusedCellContainer !== null) { + this.cellContainersPool.killAndHide(reusedCellContainer); // Unused cell container, put back to pool + } + } + } +} + +export default ShowCell; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/ShowCells.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/ShowCells.js new file mode 100644 index 000000000..8cb7b1ab5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/ShowCells.js @@ -0,0 +1,64 @@ +import AlignIn from '../../../../../utils/actions/AlignIn.js'; + +var ShowCells = function () { + if (this.cellsCount === 0) { + return; + } + var table = this.table; + + this.startRowIndex = Math.max(table.heightToRowIndex(-this.tableOY, 2), 0); + var rowIndex = this.startRowIndex; + + var startColumnIndex = Math.max(table.widthToColIndex(-this.tableOX), 0); + var columnIndex = startColumnIndex; + + var cellIdx = table.colRowToCellIndex(columnIndex, rowIndex); + var bottomBound = this.bottomBound; + var rightBound = this.rightBound; + var lastIdx = table.cellsCount - 1; + var lastColIdx = table.colCount - 1; + + var startCellTLX = this.getCellTLX(columnIndex), + cellTLX = startCellTLX; + var cellTLY = this.getCellTLY(rowIndex); + while ((cellTLY < bottomBound) && (cellIdx <= lastIdx)) { + if (this.table.isValidCellIdx(cellIdx)) { + var cell = table.getCell(cellIdx, true); + this.visibleCells.set(cell); + if (!this.preVisibleCells.contains(cell)) { + this.showCell(cell); + } + + var x, y; + if (this.scrollMode === 0) { + x = cellTLX; + y = cellTLY; + } else { + x = cellTLY; + y = cellTLX; + } + if (cell.cellContainerAlign == null) { + cell.setXY(x, y); + } else { + var cellContainer = cell.getContainer(); + AlignIn(cellContainer, x, y, cell.width, cell.height, cell.cellContainerAlign); + cell.setXY(cellContainer.x, cellContainer.y); + } + } + + if ((cellTLX < rightBound) && (columnIndex < lastColIdx)) { + cellTLX += table.getColWidth(columnIndex); + columnIndex += 1; + } else { + cellTLX = startCellTLX; + cellTLY += table.getRowHeight(rowIndex); + + columnIndex = startColumnIndex; + rowIndex += 1; + } + + cellIdx = table.colRowToCellIndex(columnIndex, rowIndex); + } +} + +export default ShowCells; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/UpdateTable.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/UpdateTable.js new file mode 100644 index 000000000..77b05a6ba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/methods/updatetable/UpdateTable.js @@ -0,0 +1,24 @@ +var UpdateTable = function (refresh) { + if (refresh === undefined) { + refresh = false; + } + if (refresh) { + ClearVisibleCellIndexes.call(this); + this.hideCells(); + } + ClearVisibleCellIndexes.call(this); + this.showCells(); + this.hideCells(); + + this.setMaskChildrenFlag(); + return this; +} + +var ClearVisibleCellIndexes = function () { + var tmp = this.preVisibleCells; + this.preVisibleCells = this.visibleCells; + this.visibleCells = tmp; + this.visibleCells.clear(); +} + +export default UpdateTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/table/Cell.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/table/Cell.js new file mode 100644 index 000000000..1a87fa006 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/table/Cell.js @@ -0,0 +1,204 @@ +import DataMethods from '../../../../utils/data/DataMethods.js'; +import AlignConst from '../../../../utils/actions/AlignConst.js'; + +class Cell { + constructor(parent, config) { + this.container = null; + this._deltaHeight = 0; + this.setParent(parent); + // this.resetFromJSON(config); + } + + setParent(parent) { + this.parent = parent; // parent: table + this.parentContainer = parent.getParentContainer(); + } + + // resetFromJSON(o) { + // return this; + // } + + destroy(fromScene) { + if (fromScene === undefined) { + fromScene = false; + } + + if (!fromScene) { + this.destroyContainer(); + } + + this.deltaHeight = 0; + this.data = undefined; + this.container = null; + this.parent = undefined; + this.parentContainer = undefined; + } + + get table() { + return this.parent; + } + + get scrollMode() { + return this.parentContainer.scrollMode; + } + + get colIndx() { + return this.parent.cellIndxeToColIndex(this.index); + } + + get rowIndx() { + return this.parent.cellIndxeToRowIndex(this.index); + } + + getContainer() { + return this.container; + } + + setContainer(container) { + if (!container) { + this.destroyContainer(); + return this; + } + + if (this.container) { + this.container.destroy(); + } + this.container = container; + this.parentContainer.add(container); + return this; + } + + destroyContainer() { + if (this.container) { + this.container.destroy(); + this.container = null; + } + return this; + } + + popContainer() { + if (this.container) { + var container = this.container; + this.container = null; + this.parentContainer.remove(container); + return container; + } else { + return null; + } + } + + setXY(x, y) { + if (this.container) { + this.parentContainer.setChildLocalPosition(this.container, x, y); + } + return this; + } + + setCellContainerAlign(align) { + if (typeof (align) === 'string') { + align = AlignConst[align]; + } + this.cellContainerAlign = align; + return this; + } + + get deltaHeight() { + return this._deltaHeight; + } + + set deltaHeight(deltaHeight) { + if (deltaHeight == null) { + deltaHeight = 0; + } + var table = this.parent; + if ((this._deltaHeight === 0) && (deltaHeight !== 0)) { + table.nonZeroDeltaHeightCount++; + } else if ((this._deltaHeight !== 0) && (deltaHeight === 0)) { + table.nonZeroDeltaHeightCount--; + } + + var isTableHeightChanged = (this._deltaHeight !== deltaHeight); + + this._deltaHeight = deltaHeight; + + if (isTableHeightChanged) { + table.resetTotalRowsHeight(); + var eventName = (this.scrollMode === 0) ? 'cellheightchange' : 'cellwidthchange'; + this.parentContainer.emit(eventName, this, this.container, this.parentContainer); + } + } + + get deltaWidth() { + return this.deltaHeight; + } + + set deltaWidth(deltaWidth) { + this.deltaHeight = deltaWidth; + } + + setDeltaHeight(deltaHeight) { + this.deltaHeight = deltaHeight; + return this; + } + + setDeltaWidth(deltaWidth) { + this.deltaHeight = deltaWidth; + return this; + } + + get height() { + if (this.scrollMode === 0) { + return this.deltaHeight + this.parent.defaultCellHeight; + } else { + return this.parent.defaultCellWidth; + } + } + + set height(height) { + // Only worked when scrollMode is 0 + if (this.scrollMode === 1) { + return; + } + this.setDeltaHeight(height - this.parent.defaultCellHeight); + } + + setHeight(height) { + // Only worked when scrollMode is 0 + this.height = height; + return this; + } + + get width() { + if (this.scrollMode === 0) { + return this.parent.defaultCellWidth; + } else { + return this.deltaHeight + this.parent.defaultCellHeight; + } + } + + set width(width) { + // Only worked when scrollMode is 1 + if (this.scrollMode === 0) { + return; + } + this.setDeltaHeight(width - this.parent.defaultCellHeight); + } + + setWidth(width) { + this.width = width; + return this; + } + + get scene() { + return this.parentContainer.scene; + } +}; + + +Object.assign( + Cell.prototype, + DataMethods +); + + +export default Cell; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/table/Table.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/table/Table.js new file mode 100644 index 000000000..7f2760192 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/gridtable/table/Table.js @@ -0,0 +1,372 @@ +import Cell from './Cell.js'; +import Pool from '../../../../pool.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const SpliceOne = Phaser.Utils.Array.SpliceOne; + +class Table { + constructor(parent, config) { + this.parent = parent; // parent: GridTable game object (Container) + this.cells = []; + this.cellPool = new Pool(); + this.resetFromJSON(config); + } + + resetFromJSON(o) { + if (o === undefined) { + o = {}; + } + this.colCount = undefined; + this.nonZeroDeltaHeightCount = 0; + this.resetTotalRowsHeight(); + + var cellHeight = o.cellHeight; + if (cellHeight === undefined) { + cellHeight = 30; + } + var cellWidth = o.cellWidth; + if (cellWidth === undefined) { + cellWidth = 30; + } + + this.setDefaultCellHeight(cellHeight); + this.setDefaultCellWidth(cellWidth); + this.initCells(GetValue(o, 'cellsCount', 0)); + this.setColumnCount(GetValue(o, 'columns', 1)); + return this; + } + + destroy(fromScene) { + // GridTable is destroyed, all cell containers will also be destroyed too + // Don't have to freeCell + this.cellPool.destroy(); + this.cells = undefined; + this.parent = undefined; + } + + get defaultCellHeightMode() { + return (this.nonZeroDeltaHeightCount === 0); + } + + setDefaultCellHeight(height) { + this.defaultCellHeight = height; + return this; + } + + setDefaultCellWidth(width) { + this.defaultCellWidth = width; + return this; + } + + initCells(size) { + var cells = this.cells; + cells.length = size; + for (var i = 0; i < size; i++) { + cells[i] = null; + } + return this; + } + + insertNewCells(cellIdx, count) { + var cells = this.cells; + if (cellIdx === cells.length) { + // append at end of array + var endIdx = cellIdx + count; + cells.legth = endIdx; + for (var i = cellIdx; i < endIdx; i++) { + cells[i] = null; + } + } else { + var newCells = []; + newCells.length = count; + for (var i = 0; i < count; i++) { + newCells[i] = null; + } + this.cells.splice(cellIdx, 0, ...newCells); + } + + this.resetTotalRowsHeight(); + return this; + } + + removeCells(cellIdx, count) { + var endIdx = cellIdx + count; + for (var i = cellIdx; i < endIdx; i++) { + this.freeCell(i); + } + + if (endIdx === this.cells.length) { + // remove until end of array + this.cells.length = cellIdx; + } else { + if (count === 1) { + SpliceOne(this.cells, cellIdx); + } else { + this.cells.splice(cellIdx, count); + } + this.buildCellIndex(cellIdx); + } + + this.resetTotalRowsHeight(); + return this; + } + + setColumnCount(cnt) { + this.colCount = cnt; + this.resetTotalRowsHeight(); + return this; + } + + get rowCount() { + return Math.ceil(this.cells.length / this.colCount); + } + + get cellsCount() { + return this.cells.length; + } + + isValidCellIdx(idx) { + return ((idx >= 0) && (idx < this.cells.length)); + } + + heightToRowIndex(height, roundMode) { + if (roundMode === undefined) { + roundMode = 0; + } + /* + roundMode: + - 0 : floor + - 1 : ceil + - 2 : plus one if rowIdx is an integer, else floor + */ + + if (height === 0) { + return 0; + } + + // defaultCellHeightMode + if (this.defaultCellHeightMode) { + var rowIdx = height / this.defaultCellHeight; + switch (roundMode) { + case 0: + rowIdx = Math.floor(rowIdx); + break; + + case 1: + rowIdx = Math.ceil(rowIdx); + break; + + default: // 2 + if (Number.isInteger(rowIdx)) { + rowIdx += 1; + } else { + rowIdx = Math.floor(rowIdx); + } + break; + } + + return rowIdx; + } + + // count cell height one by one + var rowCount = this.rowCount; + var remainder = height, + isValidIdx; + var cell, rowHeight, rowIdx = 0; + + while (1) { + rowHeight = this.getRowHeight(rowIdx); + remainder -= rowHeight; + + isValidIdx = (rowIdx >= 0) && (rowIdx < rowCount); + if ((remainder > 0) && isValidIdx) { + rowIdx += 1; + } else if (remainder === 0) { + if (roundMode === 2) { + rowIdx += 1; + } + return rowIdx; + } else { + if (roundMode === 1) { + var preRowIdx = rowIdx; + rowIdx += 1; + isValidIdx = (rowIdx >= 0) && (rowIdx < rowCount); + + if (!isValidIdx) { + rowIdx = preRowIdx; + } + } + + return rowIdx; + } + } + + } + + widthToColIndex(width, isCeil) { + if (width === 0) { + return 0; + } + + var colIdx = width / this.defaultCellWidth; + if (isCeil) { + colIdx = Math.ceil(colIdx); + } else { + colIdx = Math.floor(colIdx); + } + + return colIdx; + } + + colRowToCellIndex(colIdx, rowIdx) { + if (colIdx >= this.colCount) { + return null; + } + return (rowIdx * this.colCount) + colIdx; + } + + rowIndexToHeight(start, end) { + // defaultCellHeightMode + if (this.defaultCellHeightMode) { + return (end - start + 1) * this.defaultCellHeight; + } + + var h, sum = 0; + for (var i = start; i <= end; i++) { + h = this.getRowHeight(i); + sum += h; + } + + return sum; + } + + colIndexToWidth(start, end) { + return (end - start + 1) * this.defaultCellWidth; + }; + + getRowHeight(rowIdx) { + var cnt = this.colCount; + // single column + if (cnt <= 1) { + return this.getCellHeight(this.colRowToCellIndex(0, rowIdx)); + } + + // multiple columns, get the maximum height + var maxHeight = 0, + cellHeight; + for (var i = 0; i < cnt; i++) { + cellHeight = this.getCellHeight(this.colRowToCellIndex(i, rowIdx)); + if (maxHeight < cellHeight) + maxHeight = cellHeight; + } + return maxHeight; + } + + getColWidth(idx) { + return this.defaultCellWidth; + } + + getCellHeight(cellIdx) { + if (!this.isValidCellIdx(cellIdx)) { + return 0; + } + + var cellHeight; + if (this.defaultCellHeightMode) + cellHeight = this.defaultCellHeight; + else { + var cell = this.getCell(cellIdx, false); + var deltaHeight = (cell) ? cell.deltaHeight : 0; + cellHeight = this.defaultCellHeight + deltaHeight; + } + + return cellHeight; + } + + resetTotalRowsHeight() { + this._totalRowsHeight = null; + } + + get totalRowsHeight() { + if (this._totalRowsHeight === null) { + this._totalRowsHeight = this.rowIndexToHeight(0, this.rowCount - 1); + } + + return this._totalRowsHeight; + } + + get totalColumnWidth() { + return this.colCount * this.defaultCellWidth; + } + + cellIndxeToColIndex(cellIdx) { + return cellIdx % this.colCount; + } + + cellIndxeToRowIndex(cellIdx) { + return Math.floor(cellIdx / this.colCount); + } + + getCell(cellIdx, createNewCell) { + if (!this.isValidCellIdx(cellIdx)) { + return null; + } + + if (createNewCell === undefined) { + createNewCell = true; + } + if ((this.cells[cellIdx] === null) && createNewCell) { + var cell = this.newCell(cellIdx); + this.cells[cellIdx] = cell; + } + + return this.cells[cellIdx]; + } + + newCell(cellIdx) { + var cell = this.cellPool.pop(); + if (cell === null) { + cell = new Cell(this); + } else { + cell.setParent(this); + } + cell.index = cellIdx; + + return cell; + } + + buildCellIndex(startIdx) { + if (startIdx === undefined) { + startIdx = 0; + } + var cells = this.cells, + cell; + for (var i = startIdx, len = cells.length; i < len; i++) { + cell = cells[i]; + if (cell) { + cell.index = i; + } + } + return this; + } + + getParentContainer() { + return this.parent; + } + + freeCell(cell) { + if (typeof (cell) === 'number') { + cell = this.cells[cell]; + } + + if (!cell) { + return this; + } + + cell.destroy(); + this.cellPool.push(cell); + return this; + } +} + +export default Table; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/Creator.js new file mode 100644 index 000000000..4a9afba27 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/Creator.js @@ -0,0 +1,17 @@ +import ImageBox from './ImageBox.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var key = GetAdvancedValue(config, 'key', null); + var frame = GetAdvancedValue(config, 'frame', null); + + var gameObject = new ImageBox(this.scene, 0, 0, key, frame, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/Factory.js new file mode 100644 index 000000000..be77b61fe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/Factory.js @@ -0,0 +1,7 @@ +import ImageBox from './ImageBox.js'; + +export default function (x, y, texture, frame, config) { + var gameObject = new ImageBox(this.scene, x, y, texture, frame, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/ImageBox.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/ImageBox.d.ts new file mode 100644 index 000000000..490eeb0d0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/ImageBox.d.ts @@ -0,0 +1,49 @@ +import ContainerLite from '../containerlite/ContainerLite'; + +export default ImageBox; + +declare namespace ImageBox { + + interface IConfig { + x?: number, y?: number, + texture?: string, frame?: string, + + width?: number, height?: number, + + image?: Phaser.GameObjects.GameObject, + } +} + +declare class ImageBox extends ContainerLite { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + texture?: string, frame?: string, + config?: ImageBox.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + config?: ImageBox.IConfig + ); + + constructor( + scene: Phaser.Scene, + config?: ImageBox.IConfig + ); + + image: Phaser.GameObjects.GameObject; + + setTexture(texture?: string, frame?: string): this; + readonly texture: Phaser.Textures.Texture | Phaser.Textures.CanvasTexture; + readonly frame: Phaser.Textures.Frame; + + setFlipX(value: boolean): this; + setFlipY(value: boolean): this; + toggleFlipX(): this; + toggleFlipY(): this; + setFlip(x: boolean, y: boolean): this; + flipX: boolean; + flipY: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/ImageBox.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/ImageBox.js new file mode 100644 index 000000000..e42edce0a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/imagebox/ImageBox.js @@ -0,0 +1,114 @@ +import Container from '../containerlite/ContainerLite.js'; +import FitToSize from '../../../utils/size/FitTo.js'; +import FlipMethods from '../utils/FlipMethods.js'; +import HasTexture from '../../../utils/texture/HasTexture.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class ImageBox extends Container { + constructor(scene, x, y, texture, frame, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + texture = GetValue(config, 'key', undefined); + frame = GetValue(config, 'frame', undefined); + } else if (IsPlainObject(frame)) { + config = frame; + frame = undefined; + } + + var image = GetValue(config, 'image'); + if (!image) { + image = scene.add.image(x, y, texture, frame); + if (texture === undefined) { + image.setVisible(false); + } + } else { + image.setPosition(x, y).setOrigin(0.5); + } + + super(scene, x, y, 1, 1); + this.type = 'rexImageBox'; + + this.add(image); + this.image = image; + + var width = GetValue(config, 'width', image.width); + var height = GetValue(config, 'height', image.height); + this.resize(width, height); + + } + + get texture() { + return this.image.texture; + } + + get frame() { + return this.image.frame; + } + + get flipX() { + return this._flipX; + } + + set flipX(value) { + if (this._flipX === value) { + return; + } + + this._flipX = value; + this.image.setFlipX(value); + } + + get flipY() { + return this._flipY; + } + + set flipY(value) { + if (this._flipY === value) { + return; + } + this._flipY = value; + this.image.setFlipY(value); + } + + scaleImage() { + var image = this.image; + + var result = FitToSize(image, { width: this.width, height: this.height }, true); + image.setDisplaySize(result.width, result.height); + this.resetChildScaleState(image); + return this; + } + + resize(width, height) { + super.resize(width, height); + + this.scaleImage(); + return this; + } + + setTexture(texture, frame) { + var image = this.image; + image.setTexture(texture, frame); + + if (texture !== null) { + this.setChildVisible(image, true); + this.scaleImage(); + + } else { + this.setChildVisible(image, false); + + } + return this; + } +} + +Object.assign( + ImageBox.prototype, + FlipMethods, +) + +export default ImageBox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/Creator.js new file mode 100644 index 000000000..4d9e99e2d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/Creator.js @@ -0,0 +1,17 @@ +import TransitionImage from './TransitionImage.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var key = GetAdvancedValue(config, 'key', null); + var frame = GetAdvancedValue(config, 'frame', null); + + var gameObject = new TransitionImage(this.scene, 0, 0, key, frame, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/Factory.js new file mode 100644 index 000000000..ad55c7c61 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/Factory.js @@ -0,0 +1,7 @@ +import TransitionImage from './TransitionImage.js'; + +export default function (x, y, texture, frame, config) { + var gameObject = new TransitionImage(this.scene, x, y, texture, frame, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/TransitionImage.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/TransitionImage.d.ts new file mode 100644 index 000000000..fc461b369 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/TransitionImage.d.ts @@ -0,0 +1,104 @@ +import ContainerLite from '../containerlite/ContainerLite'; + +export default TransitionImage; + +declare namespace TransitionImage { + + type TransitionDirectionType = 0 | 1 | 'out' | 'in'; + + type TransitionCallbackType = ( + parent: TransitionImage, + currentImage: Phaser.GameObjects.Image, + nextImage: Phaser.GameObjects.Image, + t: number + ) => void; + + interface ITransitConfig { + texture?: string, frame?: string, + + dir?: TransitionDirectionType, + + onStart?: TransitionCallbackType, + onStartScope?: unknown, + + onProgress?: TransitionCallbackType, + onProgressScope?: unknown, + + onComplete?: TransitionCallbackType, + onCompleteScope?: unknown, + + duration?: number, + ease?: string, + mask?: boolean, + } + + interface IConfig extends ITransitConfig { + x?: number, y?: number, + } +} + +declare class TransitionImage extends ContainerLite { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + texture?: string, frame?: string, + config?: TransitionImage.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + config?: TransitionImage.IConfig + ); + + constructor( + scene: Phaser.Scene, + config?: TransitionImage.IConfig + ); + + texture: Phaser.Textures.Texture; + frame: Phaser.Textures.Frame; + + setTransitionDirection( + dir: TransitionImage.TransitionDirectionType + ): this; + + setTransitionStartCallback( + callback: TransitionImage.TransitionCallbackType, + scope?: object + ): this; + + setTransitionProgressCallback( + callback: TransitionImage.TransitionCallbackType, + scope?: object + ): this; + + setTransitionCompleteCallback( + callback: TransitionImage.TransitionCallbackType, + scope?: object + ): this; + + setDuration(duration: number): this; + + setDuration(ease: string): this; + + setMaskEnable(enable?: boolean): this; + + transit(texture: string, frame?: string): this; + + transit( + config: TransitionImage.ITransitConfig + ): this; + + pause(): this; + resume(): this; + stop(): this; + + setFlipX(value: boolean): this; + setFlipY(value: boolean): this; + toggleFlipX(): this; + toggleFlipY(): this; + setFlip(x: boolean, y: boolean): this; + flipX: boolean; + flipY: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/TransitionImage.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/TransitionImage.js new file mode 100644 index 000000000..22cf80b9d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/TransitionImage.js @@ -0,0 +1,261 @@ +import Container from '../containerlite/ContainerLite.js'; +import Methods from './methods/Methods.js'; +import { + OnStart as DefaultOnStart, + OnProgress as DefaultOnProgress, + OnComplete as DefaultOnComplete +} from './methods/CrossFadeTransition.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const Clamp = Phaser.Math.Clamp; + +class TransitionImage extends Container { + constructor(scene, x, y, texture, frame, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + texture = GetValue(config, 'key', undefined); + frame = GetValue(config, 'frame', undefined); + } else if (IsPlainObject(frame)) { + config = frame; + frame = undefined; + } + + var backImage = GetValue(config, 'back', undefined); + var frontImage = GetValue(config, 'front', undefined); + if (!backImage) { + backImage = scene.add.image(x, y, texture, frame); + } + if (!frontImage) { + frontImage = scene.add.image(x, y, texture, frame); + } + var width = GetValue(config, 'width', frontImage.width); + var height = GetValue(config, 'height', frontImage.height); + + super(scene, x, y, width, height); + this.type = 'rexTransitionImage'; + + backImage.setVisible(false); + this.addMultiple([backImage, frontImage]) + + this.backImage = backImage; + this.frontImage = frontImage; + this.maskGameObject = undefined; + this.cellImages = []; + this.imagesPool = []; + + // Transition parameters + var onStart = GetValue(config, 'onStart', undefined); + var onProgress = GetValue(config, 'onProgress', undefined); + var onComplete = GetValue(config, 'onComplete', undefined); + var dir = GetValue(config, 'dir', 0); + if ((onStart === undefined) && (onProgress === undefined) && (onComplete === undefined)) { + onStart = DefaultOnStart; + onProgress = DefaultOnProgress; + onComplete = DefaultOnComplete; + dir = 0; + } + + this + .setTransitionStartCallback( + onStart, + GetValue(config, 'onStartScope', undefined) + ) + .setTransitionProgressCallback( + onProgress, + GetValue(config, 'onProgressScope', undefined) + ) + .setTransitionCompleteCallback( + onComplete, + GetValue(config, 'onCompleteScope', undefined) + ) + .setTransitionDirection(dir) + .setDuration(GetValue(config, 'duration', 1000)) + .setEaseFunction(GetValue(config, 'ease', 'Linear')) + + var maskGameObject = GetValue(config, 'mask', undefined); + if (maskGameObject) { + this.setMaskGameObject(maskGameObject); + } + this.setMaskEnable(false); + + this.ignoreCompleteEvent = false; + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + if (this.childrenMask) { + this.childrenMask.destroy(); + this.childrenMask = undefined; + } + this.backImage = undefined; + this.frontImage = undefined; + this.maskGameObject = undefined; + this.cellImages.length = 0; + this.imagesPool.length = 0; + + super.destroy(fromScene); + + this.onStartCallback = undefined; + this.onStartCallbackScope = undefined; + this.onProgressCallback = undefined; + this.onProgressCallbackScope = undefined; + this.onCompleteCallback = undefined; + this.onCompleteCallbackScope = undefined; + this.easeValueTask = undefined; + } + + get currentImage() { + return (this.dir === 0) ? this.frontImage : this.backImage; + } + + get nextImage() { + return (this.dir === 0) ? this.backImage : this.frontImage; + } + + get texture() { + return this.nextImage.texture; + } + + get frame() { + return this.nextImage.frame; + } + + get flipX() { + return this._flipX; + } + + set flipX(value) { + if (this._flipX === value) { + return; + } + + this._flipX = value; + this.backImage.setFlipX(value); + this.frontImage.setFlipX(value); + } + + get flipY() { + return this._flipY; + } + + set flipY(value) { + if (this._flipY === value) { + return; + } + this._flipY = value; + this.backImage.setFlipY(value); + this.frontImage.setFlipY(value); + } + + get t() { + return this._t; + } + + set t(value) { + value = Clamp(value, 0, 1); + if (this._t === value) { + return; + } + this._t = value; + + var currentImage = this.currentImage; + var nextImage = this.nextImage; + + // Start + if (value === 0) { + this + .setChildVisible(this.frontImage, true) + .setChildVisible(this.backImage, true) + + RunCallback( + this.onStartCallback, this.onStartCallbackScope, + this, currentImage, nextImage, value + ); + } + + // Progress + RunCallback( + this.onProgressCallback, this.onProgressCallbackScope, + this, currentImage, nextImage, value + ); + + // Complete + if (value === 1) { + RunCallback( + this.onCompleteCallback, this.onCompleteCallbackScope, + this, currentImage, nextImage, value + ); + + var key = nextImage.texture.key, + frame = nextImage.frame.name; + this.frontImage.setTexture(key, frame); + this.backImage.setTexture(key, frame); + + this + .setChildVisible(this.frontImage, true) + .setChildVisible(this.backImage, false) + .setMaskEnable(false) + .freeCellImages() + } + + if ((value === 1) && (!this.ignoreCompleteEvent)) { + this.emit('complete'); + } + } + + setT(value) { + this.t = value; + return this; + } + + get isRunning() { + return (this.easeValueTask) ? this.easeValueTask.isRunning : false; + } + + setOrigin(originX, originY) { + super.setOrigin(originX, originY); + + this.backImage.setOrigin(originX, originY); + this.frontImage.setOrigin(originX, originY); + + if (this.maskGameObject) { + this.maskGameObject.setOrigin(originX, originY); + } + + return this; + } + + setTexture(texture, frame) { + // Without transition + this.frontImage.setTexture(texture, frame); + this.backImage.setTexture(texture, frame).setVisible(false); + return this; + } +} + +var RunCallback = function (callback, scope, parent, currentImage, nextImage, t) { + if (!callback) { + return; + } + + if (scope) { + callback.call(scope, parent, currentImage, nextImage, t); + } else { + callback(parent, currentImage, nextImage, t); + } +} + +// mixin +Object.assign( + TransitionImage.prototype, + Methods +); + +export default TransitionImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/CrossFadeTransition.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/CrossFadeTransition.js new file mode 100644 index 000000000..260afe15b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/CrossFadeTransition.js @@ -0,0 +1,16 @@ +var OnStart = function (parent, currentImage, nextImage, t) { +} + +var OnProgress = function (parent, currentImage, nextImage, t) { + parent + .setChildLocalAlpha(currentImage, 1 - t) + .setChildLocalAlpha(nextImage, t) +} + +var OnComplete = function (parent, currentImage, nextImage, t) { + parent.setChildLocalAlpha(currentImage, 1) +} + +export { + OnStart, OnProgress, OnComplete +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/GridCutMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/GridCutMethods.js new file mode 100644 index 000000000..2cd82aae7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/GridCutMethods.js @@ -0,0 +1,64 @@ +import GridCutImage from '../../../../actions/GridCutImage.js' + +export default { + gridCutImage(gameObject, columns, rows, config) { + if (config === undefined) { + config = {}; + } + config.objectPool = this.imagesPool; + var cellImages = GridCutImage(gameObject, columns, rows, config), + cellImage; + for (var i = 0, cnt = cellImages.length; i < cnt; i++) { + cellImage = cellImages[i]; + cellImage.setVisible(true); + this.add(cellImage); + } + + this.cellImages = cellImages; + this.setChildLocalVisible(gameObject, false); // Set cut target to invisible + return cellImages; + }, + + gridCutCurrentImage(columns, rows, config) { + return this.gridCutImage(this.currentImage, columns, rows, config); + }, + + gridCutNextImage(columns, rows, config) { + return this.gridCutImage(this.nextImage, columns, rows, config); + }, + + getCellImages() { + return this.cellImages; + }, + + freeCellImages() { + if (this.cellImages.length === 0) { + return this; + } + + var texture = this.cellImages[0].texture; + var cellImages = this.cellImages, + cellImage, frameName; + for (var i = 0, cnt = cellImages.length; i < cnt; i++) { + cellImage = cellImages[i]; + + // Reset property of cell image + this + .setChildLocalAlpha(cellImage, 1) + .setChildLocalScale(cellImage, 1) + .setChildLocalVisible(cellImage, false) + + cellImage.clearMask(); + + // Remove frame object + frameName = cellImage.frame.name; + cellImage.setTexture(); + texture.remove(frameName); + } + + this.imagesPool.push(...cellImages); + cellImages.length = 0; + + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/MaskMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/MaskMethods.js new file mode 100644 index 000000000..1638181e7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/MaskMethods.js @@ -0,0 +1,95 @@ +import DefaultMaskGraphics from '../../../../utils/mask/defaultmaskgraphics/DefaultMaskGraphics.js'; + +export default { + setMaskGameObject(gameObject) { + if (!gameObject) { + this.removeMaskGameObject(); + return this; + } + + if (this.maskGameObject) { + if ((gameObject === true) && (this.maskGameObject instanceof DefaultMaskGraphics)) { + return this; + } + if (this.maskGameObject === gameObject) { + return this; + } + + // Remove previous Mask Game Object + this.removeMaskGameObject(); + } + + // Add new Mask Game Object + if (gameObject === true) { + gameObject = new DefaultMaskGraphics(this); + } + + this.maskGameObject = gameObject; + this.maskGameObject + .resize(this.width, this.height) + .setOrigin(this.originX, this.originY) + .setPosition(0, 0) + .setScale(1) + .setVisible(false) + this.addLocal(this.maskGameObject); + + this.childrenMask = this.maskGameObject.createGeometryMask(); + return this; + }, + + removeMaskGameObject() { + this.backImage.clearMask(); + this.frontImage.clearMask(); + this.childrenMask = undefined; + this.remove(this.maskGameObject, true); + this.maskGameObject = undefined; + return this; + }, + + setImageMaskEnable(gameObject, enable, invertAlpha) { + if (enable === undefined) { + enable = true; + } + + // Use DefaultMaskGraphics if not given + if (!this.childrenMask) { + this.setMaskGameObject(true); + } + + if (enable) { + gameObject.setMask(this.childrenMask); + if (invertAlpha) { + this.childrenMask.setInvertAlpha(); + } + } else { + gameObject.clearMask(); + } + + return this; + }, + + setCurrentImageMaskEnable(enable, invertAlpha) { + this.setImageMaskEnable(this.currentImage, enable, invertAlpha); + return this; + }, + + setNextImageMaskEnable(enable, invertAlpha) { + this.setImageMaskEnable(this.nextImage, enable, invertAlpha); + return this; + }, + + setCellImagesMaskEnable(enable, invertAlpha) { + var cellImages = this.getCellImages(); + for (var i = 0, cnt = cellImages.length; i < cnt; i++) { + this.setImageMaskEnable(cellImages[i], enable, invertAlpha); + } + return this; + }, + + setMaskEnable(enable, invertAlpha) { + this.setImageMaskEnable(this.backImage, enable, invertAlpha); + this.setImageMaskEnable(this.frontImage, enable, invertAlpha); + this.setCellImagesMaskEnable(enable, invertAlpha); + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/Methods.js new file mode 100644 index 000000000..146c50394 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/Methods.js @@ -0,0 +1,19 @@ +import SetTransitionCallbackMethods from './SetTransitionCallbackMethods.js'; +import TransitionMethods from './TransitionMethods.js'; +import MaskMethods from './MaskMethods.js'; +import GridCutMethods from './GridCutMethods.js'; +import FlipMethods from '../../utils/FlipMethods.js'; + +var methods = { +} + +Object.assign( + methods, + SetTransitionCallbackMethods, + TransitionMethods, + MaskMethods, + GridCutMethods, + FlipMethods +) + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/SetTransitionCallbackMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/SetTransitionCallbackMethods.js new file mode 100644 index 000000000..d13f3be9e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/SetTransitionCallbackMethods.js @@ -0,0 +1,19 @@ +export default { + setTransitionStartCallback(callback, scope) { + this.onStartCallback = callback; + this.onStartCallbackScope = scope; + return this; + }, + + setTransitionProgressCallback(callback, scope) { + this.onProgressCallback = callback; + this.onProgressCallbackScope = scope; + return this; + }, + + setTransitionCompleteCallback(callback, scope) { + this.onCompleteCallback = callback; + this.onCompleteCallbackScope = scope; + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/TransitionMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/TransitionMethods.js new file mode 100644 index 000000000..b4b1ee6b3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/transitionimage/methods/TransitionMethods.js @@ -0,0 +1,117 @@ +import EaseValueTask from '../../../../utils/ease/EaseValueTask.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +var DirMode = { + out: 0, + in: 1 +} + +export default { + setTransitionDirection(dir) { + if (typeof (dir) === 'string') { + dir = DirMode[dir]; + } + this.dir = dir; + return this; + }, + + setDuration(duration) { + this.duration = duration; + return this; + }, + + setEaseFunction(ease) { + this.easeFunction = ease; + return this; + }, + + setNextTexture(texture, frame) { + this.nextImage.setTexture(texture, frame); + return this; + }, + + transit(texture, frame) { + if (this.isRunning) { + this.ignoreCompleteEvent = true; + this.stop(); + this.ignoreCompleteEvent = false; + } + + if (IsPlainObject(texture)) { + var config = texture; + texture = GetValue(config, 'key', undefined); + frame = GetValue(config, 'frame', undefined); + + this + .setDuration(GetValue(config, 'duration', this.duration)) + .setEaseFunction(GetValue(config, 'ease', this.easeFunction)) + .setTransitionDirection(GetValue(config, 'dir', this.dir)) + + var maskGameObject = GetValue(config, 'mask', undefined); + if (maskGameObject) { + this.setMaskGameObject(maskGameObject); + } + this.setMaskEnable(maskGameObject === true); + + var onStart = GetValue(config, 'onStart', undefined); + var onProgress = GetValue(config, 'onProgress', undefined); + var onComplete = GetValue(config, 'onComplete', undefined); + if ((onStart !== undefined) || (onProgress !== undefined) || (onComplete !== undefined)) { + this + .setTransitionStartCallback( + onStart, + GetValue(config, 'onStartScope', undefined) + ) + .setTransitionProgressCallback( + onProgress, + GetValue(config, 'onProgressScope', undefined) + ) + .setTransitionCompleteCallback( + onComplete, + GetValue(config, 'onCompleteScope', undefined) + ) + } + } + + this.setNextTexture(texture, frame); + + this.start(); + return this; + }, + + start() { + if (this.easeValueTask === undefined) { + this.easeValueTask = new EaseValueTask(this, { eventEmitter: null }) + } + this.easeValueTask.restart({ + key: 't', from: 0, to: 1, + duration: this.duration, + ease: this.easeFunction + }); + return this; + }, + + pause() { + if (this.easeValueTask) { + this.easeValueTask.pause(); + } + return this; + }, + + resume() { + if (this.easeValueTask) { + this.easeValueTask.resume(); + } + return this; + }, + + stop() { + if (this.easeValueTask) { + this.easeValueTask.stop(); + } + this.setT(1); + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/utils/FlipMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/utils/FlipMethods.js new file mode 100644 index 000000000..56bc1e926 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/container/utils/FlipMethods.js @@ -0,0 +1,28 @@ +export default { + setFlipX(value) { + this.flipX = value; + return this; + }, + setFlipY(value) { + this.flipY = value; + return this; + }, + toggleFlipX() { + this.flipX = !this.flipX; + return this; + }, + toggleFlipY() { + this.flipY = !this.flipY; + return this; + }, + setFlip(x, y) { + this.flipX = x; + this.flipY = y; + return this; + }, + resetFlip() { + this.flipX = false; + this.flipY = false; + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/ClickPromise.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/ClickPromise.js new file mode 100644 index 000000000..c0c77e6e0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/ClickPromise.js @@ -0,0 +1,19 @@ +import GetGame from '../../../utils/system/GetGame.js'; +import { WaitEvent } from '../../../utils/promise/WaitEvent.js' +import Delay from '../../../utils/promise/Delay.js'; + +var ClickPromise = function ({ game, fileInput, closeDelay }) { + return WaitEvent(GetGame(game).events, 'focus') + .then(function () { + return Delay(closeDelay); + }) + .then(function () { + var result = { + files: fileInput.files + } + + return Promise.resolve(result); + }) +} + +export default ClickPromise; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/Creator.js new file mode 100644 index 000000000..8f178dc44 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/Creator.js @@ -0,0 +1,16 @@ +import FileChooser from './FileChooser.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', undefined); + var gameObject = new FileChooser(this.scene, 0, 0, width, height, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/Factory.js new file mode 100644 index 000000000..4217de41d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/Factory.js @@ -0,0 +1,7 @@ +import FileChooser from './FileChooser.js'; + +export default function (x, y, width, height, config) { + var gameObject = new FileChooser(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/FileChooser.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/FileChooser.d.ts new file mode 100644 index 000000000..7773fa063 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/FileChooser.d.ts @@ -0,0 +1,68 @@ +export default FileChooser; + +declare namespace FileChooser { + + interface IConfig { + x?: number, + y?: number, + width?: number, + height?: number, + + accept?: string, + multiple?: boolean + } + + namespace Events { + type ValueChangeCallbackType = (fileChooser: FileChooser) => void; + } +} + +declare class FileChooser extends Phaser.GameObjects.DOMElement { + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + config?: FileChooser.IConfig + ); + + constructor( + scene: Phaser.Scene, + x: number, y: number, + config?: FileChooser.IConfig + ); + + constructor( + scene: Phaser.Scene, + config?: FileChooser.IConfig + ); + + syncTo(gameObject: Phaser.GameObjects.GameObject): this; + + readonly files: File[]; + + setAccept(accept: string): this; + + setMultiple(multiple?: boolean): this; + + loadFile( + file: File, + loaderType: string, + key: string, + cacheType?: string + ): this; + + loadFile( + file: File, + loaderType: string, + key: string, + cacheType?: string, + onComplete?: (data: any) => void + ): this; + + loadFilePromise( + file: File, + loaderType: string, + key: string, + cacheType?: string + ): Promise; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/FileChooser.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/FileChooser.js new file mode 100644 index 000000000..6f6d031d6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filechooser/FileChooser.js @@ -0,0 +1,118 @@ +import Resize from '../utils/Resize.js'; +import SyncTo from '../utils/SyncTo.js'; +import LoadFileMethods from '../utils/LoadFileMethods.js'; +import ClickPromose from './ClickPromise.js'; + +const DOMElement = Phaser.GameObjects.DOMElement; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class FileChooser extends DOMElement { + constructor(scene, x, y, width, height, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 0); + height = GetValue(config, 'height', 0); + } else if (IsPlainObject(width)) { + config = width; + width = GetValue(config, 'width', 0); + height = GetValue(config, 'height', 0); + } + + // Create a hidden file input + var inputElement = document.createElement('input'); + inputElement.type = 'file'; + var inputStyle = inputElement.style; + inputStyle.display = 'none'; + + // Create a label parent + var labelElement = document.createElement('label'); + labelElement.appendChild(inputElement); + + var style = GetValue(config, 'style', undefined); + super(scene, x, y, labelElement, style); + this.type = 'rexFileChooser'; + this.resetFromJSON(config); + this.resize(width, height); + + // Register events + var self = this; + inputElement.onchange = function () { + self.emit('change', self); + } + + this.setCloseDelay(GetValue(config, 'closeDelay', 200)); + inputElement.onclick = function () { + ClickPromose({ + game: scene, + fileInput: inputElement, + closeDelay: self.closeDelay + }) + .then(function () { + self.emit('select', self); + }) + } + } + + resetFromJSON(config) { + this.setAccept(GetValue(config, 'accept', '')); + this.setMultiple(GetValue(config, 'multiple', false)); + return this; + } + + setAccept(accept) { + if (accept === undefined) { + accept = ''; + } + this.fileInput.setAttribute('accept', accept); + return this; + } + + setMultiple(enabled) { + if (enabled === undefined) { + enabled = true; + } + if (enabled) { + this.fileInput.setAttribute('multiple', ''); + } else { + this.fileInput.removeAttribute('multiple'); + } + return this; + } + + setCloseDelay(delay) { + if (delay === undefined) { + delay = 200; + } + this.closeDelay = delay; + return this; + } + + get fileInput() { + return this.node.children[0]; + } + + open() { // Only work under any touch event + this.fileInput.click(); + return this; + } + + get files() { + return this.fileInput.files; + } +} + +var methods = { + resize: Resize, + syncTo: SyncTo, +} + +Object.assign( + FileChooser.prototype, + methods, + LoadFileMethods, +); + +export default FileChooser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/Creator.js new file mode 100644 index 000000000..bf0402c44 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/Creator.js @@ -0,0 +1,16 @@ +import FileDropZone from './FileDropZone.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', undefined); + var gameObject = new FileDropZone(this.scene, 0, 0, width, height, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/Factory.js new file mode 100644 index 000000000..5251d17e7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/Factory.js @@ -0,0 +1,7 @@ +import FileDropZone from './FileDropZone.js'; + +export default function (x, y, width, height, config) { + var gameObject = new FileDropZone(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/FileDropZone.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/FileDropZone.d.ts new file mode 100644 index 000000000..8c6fed90f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/FileDropZone.d.ts @@ -0,0 +1,70 @@ +export default FileDropZone; + +declare namespace FileDropZone { + + type FilterCallbackType = ( + file: File, + files: File[] + ) => boolean; + + type FiltersType = { [filterType: string]: FilterCallbackType } + + interface IConfig { + x?: number, + y?: number, + width?: number, + height?: number, + + filters?: FiltersType + + } +} + +declare class FileDropZone extends Phaser.GameObjects.DOMElement { + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + config?: FileDropZone.IConfig + ); + + constructor( + scene: Phaser.Scene, + x: number, y: number, + config?: FileDropZone.IConfig + ); + + constructor( + scene: Phaser.Scene, + config?: FileDropZone.IConfig + ); + + setDropEnable(enable?: boolean): this; + toggleDropEnable(): this; + dropEnable: boolean; + + addFilter( + name: string, + callback: FileDropZone.FilterCallbackType + ): this; + addFilters(filters: FileDropZone.FiltersType): this; + + syncTo(gameObject: Phaser.GameObjects.GameObject): this; + + readonly files: File[]; + + loadFile( + file: File, + loaderType: string, + key: string, + cacheType?: string, + onComplete?: (data: any) => void + ): this; + + loadFilePromise( + file: File, + loaderType: string, + key: string, + cacheType?: string + ): Promise; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/FileDropZone.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/FileDropZone.js new file mode 100644 index 000000000..60e52352b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/FileDropZone.js @@ -0,0 +1,82 @@ +import Methods from './methods/Methods.js'; +import { DragDropEvents } from './FileDropZoneProperties.js'; +import RouteEvents from '../utils/RouteEvents.js'; + +const DOMElement = Phaser.GameObjects.DOMElement; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class FileDropZone extends DOMElement { + constructor(scene, x, y, width, height, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 0); + height = GetValue(config, 'height', 0); + } else if (IsPlainObject(width)) { + config = width; + width = GetValue(config, 'width', 0); + height = GetValue(config, 'height', 0); + } + + if (config === undefined) { + config = {}; + } + + var element = document.createElement('div'); + + var style = GetValue(config, 'style', undefined); + super(scene, x, y, element, style); + this.type = 'rexFileDropZone'; + this.resize(width, height); + + this._files = []; + this.setDropEnable(GetValue(config, 'dropEnable', true)); + + var filters = GetValue(config, 'filters'); + if (filters) { + this.addFilters(filters); + } + + // Apply events + RouteEvents(this, element, DragDropEvents, { + preventDefault: true, + preTest(gameObject) { return gameObject.dropEnable; } + }); + + this + .on('drop', function (gameObject, e) { + this._files = e.dataTransfer.files; + var files = this._files; + if (files && this.filters) { + for (var filterType in this.filters) { + var filterCallback = this.filters[filterType]; + + var filteredFiles = []; + for (var i = 0, cnt = files.length; i < cnt; i++) { + var file = files[i]; + if (filterCallback(file, files)) { + filteredFiles.push(file); + } + } + + if (filteredFiles.length > 0) { + this.emit(`drop.${filterType}`, filteredFiles); + } + } + } + }, this) + } + + get files() { + return this._files; + } +} + +Object.assign( + FileDropZone.prototype, + Methods, +); + +export default FileDropZone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/FileDropZoneProperties.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/FileDropZoneProperties.js new file mode 100644 index 000000000..4a3279b40 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/FileDropZoneProperties.js @@ -0,0 +1,10 @@ +const DragDropEvents = { + dragenter: 'dragenter', + dragleave: 'dragleave', + dragover: 'dragover', + drop: 'drop', +}; + +export { + DragDropEvents, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/methods/DropEnableMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/methods/DropEnableMethods.js new file mode 100644 index 000000000..255a7f167 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/methods/DropEnableMethods.js @@ -0,0 +1,15 @@ +export default { + setDropEnable(enable) { + if (enable === undefined) { + enable = true; + } + + this.dropEnable = enable; + return this; + }, + + toggleDropEnable() { + this.dropEnable = !this.dropEnable; + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/methods/FilterMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/methods/FilterMethods.js new file mode 100644 index 000000000..66929ce9a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/methods/FilterMethods.js @@ -0,0 +1,19 @@ +export default { + addFilter(name, callback) { + if (!this.filters) { + this.filters = {}; + } + this.filters[name] = callback; + return this; + }, + + addFilters(filters) { + if (!this.filters) { + this.filters = {}; + } + for (var name in filters) { + this.filters[name] = filters[name]; + } + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/methods/Methods.js new file mode 100644 index 000000000..e5399dee8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/filedropzone/methods/Methods.js @@ -0,0 +1,19 @@ +import Resize from '../../utils/Resize.js'; +import SyncTo from '../../utils/SyncTo.js'; +import LoadFileMethods from '../../utils/LoadFileMethods.js'; +import DropEnableMethods from './DropEnableMethods.js'; +import FilterMethods from './FilterMethods.js'; + +var Methods = { + resize: Resize, + syncTo: SyncTo, +} + +Object.assign( + Methods, + DropEnableMethods, + FilterMethods, + LoadFileMethods, +) + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/Creator.js new file mode 100644 index 000000000..9e06964db --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/Creator.js @@ -0,0 +1,16 @@ +import InputText from './InputText.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', undefined); + var gameObject = new InputText(this.scene, 0, 0, width, height, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/Factory.js new file mode 100644 index 000000000..e7235508a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/Factory.js @@ -0,0 +1,7 @@ +import InputText from './InputText.js'; + +export default function (x, y, width, height, config) { + var gameObject = new InputText(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputText.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputText.d.ts new file mode 100644 index 000000000..995a73d83 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputText.d.ts @@ -0,0 +1,113 @@ +export default InputText; + +declare namespace InputText { + interface IConfig { + x?: number, + y?: number, + width?: number, + height?: number, + + type?: string, + + // Element properties + id?: string, + text?: string, + maxLength?: number, + minLength?: number, + placeholder?: string, + tooltip?: string, + readOnly?: boolean, + spellCheck?: boolean, + autoComplete?: 'on' | 'off', + + // Style properties + align?: string, + paddingLeft?: string, + paddingRight?: string, + paddingTop?: string, + paddingBottom?: string, + fontFamily?: string, + fontSize?: string, + color?: string, + border?: number, + backgroundColor?: string, + borderColor?: string, + outline?: string, + + selectAll?: boolean, + } + + namespace Events { + type TextChangeCallbackType = (inputText: InputText, e: Event) => void; + type FocusCallbackType = (inputText: InputText, e: Event) => void; + type BlurCallbackType = (inputText: InputText, e: Event) => void; + type ClickCallbackType = (inputText: InputText, e: Event) => void; + type DoubleClickCallbackType = (inputText: InputText, e: Event) => void; + type SelectCallbackType = (inputText: InputText, e: Event) => void; + type PointerDownCallbackType = (inputText: InputText, e: Event) => void; + type PointerMoveCallbackType = (inputText: InputText, e: Event) => void; + type PointerUpCallbackType = (inputText: InputText, e: Event) => void; + type KeyDownCallbackType = (inputText: InputText, e: Event) => void; + type KeyPressCallbackType = (inputText: InputText, e: Event) => void; + type KeyUpCallbackType = (inputText: InputText, e: Event) => void; + } +} + +declare class InputText extends Phaser.GameObjects.DOMElement { + constructor(scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + config?: InputText.IConfig + ); + + constructor(scene: Phaser.Scene, + x: number, y: number, + config?: InputText.IConfig + ); + + constructor(scene: Phaser.Scene, + config?: InputText.IConfig + ); + + setText(text: string): this; + text: string; + + selectText( + selectionStart?: number, + selectionEnd?: number + ): this; + selectAll(): this; + readonly selectionStart: number; + readonly selectionEnd: number; + readonly selectedText: string; + + setCursorPosition(value: number): this; + cursorPosition: number; + + scrollToBottom(): this; + + getStyle(key: string): string; + + setStyle(key: string, value?: number | string): this; + + setFocus(): this; + setBlur(): this; + readonly isFocused: boolean; + + setFontColor(color: string): this; + fontColor: string; + + setMaxLength(value: number): this; + maxLength: number; + + setMinLength(value: number): this; + minLength: number; + + setPlaceholder(value: string): this; + placeholder: string; + + setTooltip(value: string): this; + tooltip: string; + + resize(width: number, height: number): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputText.js new file mode 100644 index 000000000..80aeb58eb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputText.js @@ -0,0 +1,286 @@ +import Resize from '../utils/Resize.js'; +import { + ElementProperties, + StyleProperties, + ElementEvents +} from './InputTextProperties.js'; +import SetPrpoerties from '../utils/SetProperties.js'; +import RouteEvents from '../utils/RouteEvents.js'; +import StopPropagationTouchEvents from '../utils/StopPropagationTouchEvents.js'; + +const DOMElement = Phaser.GameObjects.DOMElement; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class InputText extends DOMElement { + constructor(scene, x, y, width, height, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 0); + height = GetValue(config, 'height', 0); + } else if (IsPlainObject(width)) { + config = width; + width = GetValue(config, 'width', 0); + height = GetValue(config, 'height', 0); + } + + if (config === undefined) { + config = {}; + } + + var element; + var textType = GetValue(config, 'type', 'text'); + if (textType === 'textarea') { + element = document.createElement('textarea'); + element.style.resize = 'none'; + } else { + element = document.createElement('input'); + element.type = textType; + } + + SetPrpoerties(ElementProperties, config, element); + + var style = GetValue(config, 'style', undefined); + style = SetPrpoerties(StyleProperties, config, style); + // Apply other style properties + var elementStyle = element.style; + for (var key in config) { + if ((key in ElementProperties) || (key in StyleProperties)) { + continue; + } else if (key in elementStyle) { + style[key] = config[key]; + } + } + style['box-sizing'] = 'border-box'; + super(scene, x, y, element, style); + this.type = 'rexInputText'; + this.resize(width, height); + + // Apply events + RouteEvents(this, element, ElementEvents); + + // Don't propagate touch/mouse events to parent(game canvas) + StopPropagationTouchEvents(element); + + if (GetValue(config, 'selectAll', false)) { + this.selectAll(); + } + + this._isFocused = false; + this + .on('focus', function () { + this._isFocused = true; + }, this) + .on('blur', function () { + this._isFocused = false; + }, this) + + } + + get text() { + return this.node.value; + } + + set text(value) { + this.node.value = value; + } + + setText(value) { // Override + this.text = value; + return this; + } + + get maxLength() { + return this.node.maxLength; + } + + set maxLength(value) { + this.node.maxLength = value; + } + + setMaxLength(value) { + this.maxLength = value; + return this; + } + + get minLength() { + return this.node.minLength; + } + + set minLength(value) { + this.node.minLength = value; + } + + setMinLength(value) { + this.minLength = value; + return this; + } + + get placeholder() { + return this.node.placeholder; + } + + set placeholder(value) { + this.node.placeholder = value; + } + + setPlaceholder(value) { + this.placeholder = value; + return this; + } + + selectText(selectionStart, selectionEnd) { + if (selectionStart === undefined) { + this.node.select(); + } else { + this.node.setSelectionRange(selectionStart, selectionEnd); + } + return this; + } + + selectAll() { + this.selectText(); + return this; + } + + get selectionStart() { + return this.node.selectionStart; + } + + get selectionEnd() { + return this.node.selectionEnd; + } + + get selectedText() { + var node = this.node; + return node.value.substring(node.selectionStart, node.selectionEnd); + } + + get cursorPosition() { + return this.node.selectionStart; + } + + set cursorPosition(value) { + this.node.setSelectionRange(value, value); + } + + setCursorPosition(value) { + if (value === undefined) { + value = this.text.length; + } else if (value < 0) { + value = this.text.length + value; + } + + this.cursorPosition = value; + return this; + } + + get tooltip() { + return this.node.title; + } + + set tooltip(value) { + this.node.title = value; + } + + setTooltip(value) { + this.tooltip = value; + return this; + } + + setTextChangedCallback(callback) { + this.onTextChanged = callback; + return this; + } + + get readOnly() { + return this.node.readOnly; + } + + set readOnly(value) { + this.node.readOnly = value; + } + + setReadOnly(value) { + if (value === undefined) { + value = true; + } + this.readOnly = value; + return this; + } + + get spellCheck() { + return this.node.spellcheck; + } + + set spellCheck(value) { + this.node.spellcheck = value; + } + + setSpellCheck(value) { + this.spellCheck = value; + return this; + } + + get fontColor() { + return this.node.style.color; + } + + set fontColor(value) { + this.node.style.color = value; + } + + setFontColor(value) { + this.fontColor = value; + return this; + } + + setStyle(key, value) { + this.node.style[key] = value; + return this; + } + + getStyle(key) { + return this.node.style[key]; + } + + scrollToBottom() { + this.node.scrollTop = this.node.scrollHeight; + return this; + } + + setEnabled(enabled) { + if (enabled === undefined) { + enabled = true; + } + this.node.disabled = !enabled; + return this; + } + + setBlur() { + this.node.blur(); + return this; + } + + setFocus() { + this.node.focus(); + return this; + } + + get isFocused() { + return this._isFocused; + } +} + +var methods = { + resize: Resize +} + +Object.assign( + InputText.prototype, + methods +); + +export default InputText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputTextProperties.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputTextProperties.js new file mode 100644 index 000000000..03444fe50 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/inputtext/InputTextProperties.js @@ -0,0 +1,62 @@ +const ElementProperties = { + id: ['id', undefined], + text: ['value', undefined], + maxLength: ['maxLength', undefined], + minLength: ['minLength', undefined], + placeholder: ['placeholder', undefined], + tooltip: ['title', undefined], + readOnly: ['readOnly', false], + spellCheck: ['spellcheck', false], + autoComplete: ['autocomplete', 'off'], +}; + +const StyleProperties = { + align: ['textAlign', undefined], + paddingLeft: ['padding-left', undefined], + paddingRight: ['padding-right', undefined], + paddingTop: ['padding-top', undefined], + paddingBottom: ['padding-bottom', undefined], + fontFamily: ['fontFamily', undefined], + fontSize: ['font-size', undefined], + color: ['color', '#ffffff'], + backgroundColor: ['backgroundColor', 'transparent'], + border: ['border', 0], + borderColor: ['borderColor', 'transparent'], + outline: ['outline', 'none'], + direction: ['direction', undefined] +}; + +const ElementEvents = { + input: 'textchange', + + click: 'click', + dblclick: 'dblclick', + + mousedown: 'pointerdown', + mousemove: 'pointermove', + mouseup: 'pointerup', + + touchstart: 'pointerdown', + touchmove: 'pointermove', + touchend: 'pointerup', + + keydown: 'keydown', + keyup: 'keyup', + keypress: 'keypress', + + compositionstart: 'compositionStart', + compositionend: 'compositionEnd', + compositionupdate: 'compositionUpdate', + + focus: 'focus', + blur: 'blur', + + select: 'select', +}; + + +export { + ElementProperties, + StyleProperties, + ElementEvents +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/LoadFileMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/LoadFileMethods.js new file mode 100644 index 000000000..ad79537c8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/LoadFileMethods.js @@ -0,0 +1,23 @@ +import FileObjectToCache from '../../../utils/loader/FileObjectToCache'; + +var LoadFile = function (file, loaderType, key, cacheType, onComplete) { + var scene = this.scene; + FileObjectToCache(scene, file, loaderType, key, cacheType, onComplete); + + return this; +} + +var LoadFilePromise = function (file, loaderType, key, cacheType) { + var scene = this.scene; + return new Promise(function (resolve, reject) { + var onComplete = function (data) { + resolve(data) + } + FileObjectToCache(scene, file, loaderType, key, cacheType, onComplete); + }); +} + +export default { + loadFile: LoadFile, + loadFilePromise: LoadFilePromise, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/Resize.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/Resize.js new file mode 100644 index 000000000..59648aa1c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/Resize.js @@ -0,0 +1,18 @@ +var Resize = function (width, height) { + if (this.scene.sys.scale.autoRound) { + width = Math.floor(width); + height = Math.floor(height); + } + + if ((this.width === width) && (this.height === height)) { + return this; + } + + var style = this.node.style; + style.width = `${width}px`; + style.height = `${height}px`; + this.updateSize(); + return this; +} + +export default Resize; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/RouteEvents.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/RouteEvents.js new file mode 100644 index 000000000..7bb3b268b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/RouteEvents.js @@ -0,0 +1,19 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +var RouteEvents = function (gameObject, element, elementEvents, config) { + var preventDefault = GetValue(config, 'preventDefault', false); + var preTest = GetValue(config, 'preTest'); + for (let elementEventName in elementEvents) { // Note: Don't use `var` here + element.addEventListener(elementEventName, function (e) { + if (!preTest || preTest(gameObject, elementEventName)) { + gameObject.emit(elementEvents[elementEventName], gameObject, e); + } + + if (preventDefault) { + e.preventDefault(); + } + }); + } +} + +export default RouteEvents; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/SetProperties.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/SetProperties.js new file mode 100644 index 000000000..825066518 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/SetProperties.js @@ -0,0 +1,20 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +var SetProperties = function (properties, config, out) { + if (out === undefined) { + out = {}; + } + + var property, value; + for (var key in properties) { + property = properties[key]; // [propName, defaultValue] + value = GetValue(config, key, property[1]); + if (value !== undefined) { + out[property[0]] = value; + } + } + + return out; +} + +export default SetProperties; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/StopPropagationTouchEvents.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/StopPropagationTouchEvents.js new file mode 100644 index 000000000..553478466 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/StopPropagationTouchEvents.js @@ -0,0 +1,14 @@ +var StopPropagationTouchEvents = function (element) { + // Don't propagate touch/mouse events to parent(game canvas) + element.addEventListener('touchstart', callback, false); + element.addEventListener('touchmove', callback, false); + element.addEventListener('touchend', callback, false); + element.addEventListener('mousedown', callback, false); + element.addEventListener('mouseup', callback, false); + element.addEventListener('mousemove', callback, false); +} + +var callback = function (e) { + e.stopPropagation(); +} +export default StopPropagationTouchEvents; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/SyncTo.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/SyncTo.js new file mode 100644 index 000000000..316d79469 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/utils/SyncTo.js @@ -0,0 +1,8 @@ +var SyncTo = function (gameObject) { + this.setOrigin(gameObject.originX, gameObject.originY); + this.setPosition(gameObject.x, gameObject.y); + this.resize(gameObject.displayWidth, gameObject.displayHeight); + return this; +} + +export default SyncTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/Creator.js new file mode 100644 index 000000000..ee3609c21 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/Creator.js @@ -0,0 +1,16 @@ +import YoutubePlayer from './YoutubePlayer.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', undefined); + var gameObject = new YoutubePlayer(this.scene, 0, 0, width, height, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/Factory.js new file mode 100644 index 000000000..6589e886a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/Factory.js @@ -0,0 +1,7 @@ +import YoutubePlayer from './YoutubePlayer.js'; + +export default function (x, y, width, height, config) { + var gameObject = new YoutubePlayer(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/LoadAPI.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/LoadAPI.js new file mode 100644 index 000000000..dcfe55b1d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/LoadAPI.js @@ -0,0 +1,24 @@ +import LoadScript from '../../../utils/loader/LoadScript.js'; + +var IsAPIReady = false; +var LoadAPI = function (onLoaded) { + if (IsAPIReady) { + onLoaded(); + } else { + if (!window.onYouTubeIframeAPIReady) { + window.onYouTubeIframeAPIReady = function () { + IsAPIReady = true; + for(var i=0, cnt = CallbackQueue.length; i void; + type PauseCallbackType = (player: YoutubePlayer) => void; + type EndedCallbackType = (player: YoutubePlayer) => void; + type BufferingCallbackType = (player: YoutubePlayer) => void; + type CuedCallbackType = (player: YoutubePlayer) => void; + type ErrorCallbackType = (player: YoutubePlayer) => void; + } +} + +declare class YoutubePlayer extends Phaser.GameObjects.DOMElement { + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + config?: YoutubePlayer.IConfig + ); + + constructor( + scene: Phaser.Scene, + x: number, y: number, + config?: YoutubePlayer.IConfig + ); + + constructor( + scene: Phaser.Scene, + config?: YoutubePlayer.IConfig + ); + + load( + videoId: string, + autoPlay?: boolean + ): this; + + play(): this; + pause(): this; + + setPlaybackTime(time: number): this; + playbackTime: number; + + setT(t: number): this; + t: number; + + readonly duration: number; + + setVolume(volume: number): this; + volume: number; + + setMute(muted?: boolean): this; + muted: boolean; + + setLoop(loop?: boolean): this; + loop: boolean; + + resize(width: number, height: number): this; + + readonly isPlaying: boolean; + readonly isPaused: boolean; + readonly hasEnded: boolean; + readonly videoState: number; + readonly videoStateString: string; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/YoutubePlayer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/YoutubePlayer.js new file mode 100644 index 000000000..3b3b4bf67 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dom/youtubeplayer/YoutubePlayer.js @@ -0,0 +1,257 @@ +import Resize from '../utils/Resize.js'; +import LoadAPI from './LoadAPI.js'; + +const DOMElement = Phaser.GameObjects.DOMElement; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const Clamp = Phaser.Math.Clamp; +const UUID = Phaser.Utils.String.UUID; + +class YoutubePlayer extends DOMElement { + constructor(scene, x, y, width, height, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 0); + height = GetValue(config, 'height', 0); + } else if (IsPlainObject(width)) { + config = width; + width = GetValue(config, 'width', 0); + height = GetValue(config, 'height', 0); + } + + if (config === undefined) { + config = {}; + } + + super(scene, x, y); + this.type = 'rexYoutubePlayer'; + this.youtubePlayer = undefined; + this.videoState = undefined; + this.videoId = GetValue(config, 'videoId', ''); + this.loop = GetValue(config, 'loop', false); + this.paddingCallbacks = []; + + // Create DIV element and add it + var elementId = `YT${UUID()}`; + var element = document.createElement('div'); + element.id = elementId; + this.setElement(element); + this.resize(width, height); + + // Create youtube player iframe when API ready + var playerVars = { + autoplay: GetValue(config, 'autoPlay', true) ? 1 : 0, + controls: GetValue(config, 'controls', true) ? 1 : 0, + disablekb: !GetValue(config, 'keyboardControl', true) ? 1 : 0, + modestbranding: GetValue(config, 'modestBranding', false) ? 1 : 0, + }; + var onLoad = (function () { + var youtubePlayer = new YT.Player( + elementId, + { + 'videoId': this.videoId, + 'playerVars': playerVars, + 'events': { + 'onStateChange': (function (event) { + this.videoState = event.data; + + this.emit('statechange', this); + this.emit(this.videoStateString, this); + + if ((this.videoState === YT.PlayerState.ENDED) && this.loop) { + this.youtubePlayer.playVideo(); + } + }).bind(this), + 'onReady': (function (event) { + this.youtubePlayer = youtubePlayer; + for (var i = 0, cnt = this.paddingCallbacks.length; i < cnt; i++) { + this.paddingCallbacks[i](); + } + this.paddingCallbacks = undefined; + this.emit('ready', this); + }).bind(this), + 'onError': (function (event) { + this.lastError = event.data; + this.emit('error', this, this.lastError); + }).bind(this), + } + } + ); + this.setElement(document.getElementById(elementId)); // Also remove previous DIV element + }).bind(this); + LoadAPI(onLoad); + } + + _runCallback(callback) { + if (this.youtubePlayer === undefined) { + this.paddingCallbacks.push(callback); + } else { + callback(); + } + } + + get videoStateString() { + if ((this.videoState === undefined) || (!YT)) { + return ''; + } else { + switch (this.videoState) { + case -1: return "unstarted"; + case YT.PlayerState.ENDED: return "ended"; + case YT.PlayerState.PLAYING: return "playing"; + case YT.PlayerState.PAUSED: return "pause"; + case YT.PlayerState.BUFFERING: return "buffering"; + case YT.PlayerState.CUED: return "cued"; + } + } + } + + load(videoId, autoPlay) { + if (autoPlay === undefined) { + autoPlay = true; + } + + var callback = (function () { + this.youtubePlayer.loadVideoById(videoId); + if (autoPlay) { + this.youtubePlayer.playVideo(); + } else { + this.youtubePlayer.pauseVideo(); + } + }).bind(this); + + this._runCallback(callback); + return this; + } + + play() { + var callback = (function () { + this.youtubePlayer.playVideo(); + }).bind(this); + + this._runCallback(callback); + return this; + } + + get isPlaying() { + return (this.videoState === 1); // YT.PlayerState.PLAYING + } + + pause() { + var callback = (function () { + this.youtubePlayer.pauseVideo(); + }).bind(this); + + this._runCallback(callback); + return this; + } + + get isPaused() { + return (this.videoState === 2); // YT.PlayerState.PAUSED + } + + get playbackTime() { + return (this.youtubePlayer) ? this.youtubePlayer.getCurrentTime() : 0; + } + + set playbackTime(value) { + var callback = (function () { + this.youtubePlayer.seekTo(value); + }).bind(this); + + this._runCallback(callback); + } + + setPlaybackTime(time) { + this.playbackTime = time; + return this; + } + + get duration() { + return (this.youtubePlayer) ? this.youtubePlayer.getDuration() : 0; + } + + get t() { + var duration = this.duration; + return (duration === 0) ? 0 : this.playbackTime / duration; + } + + set t(value) { + var callback = (function () { + value = Clamp(value, 0, 1); + this.playbackTime = this.duration * Clamp(value, 0, 1); + }).bind(this); + + this._runCallback(callback); + } + + setT(value) { + this.t = value; + return this; + } + + get hasEnded() { + return (this.videoState === 0); // YT.PlayerState.ENDED + } + + get volume() { + return (this.youtubePlayer) ? (this.youtubePlayer.getVolume() / 100) : 0; + } + + set volume(value) { + var callback = (function () { + this.youtubePlayer.setVolume(Clamp(value * 100, 0, 100)); + }).bind(this); + + this._runCallback(callback); + } + + setVolume(value) { + this.volume = value; + return this; + } + + get muted() { + return (this.youtubePlayer) ? this.youtubePlayer.isMuted() : false; + } + + set muted(value) { + var callback = (function () { + if (value) { + this.youtubePlayer.mute(); + } else { + this.youtubePlayer.unMute(); + } + }).bind(this); + + this._runCallback(callback); + } + + setMute(value) { + if (value === undefined) { + value = true; + } + this.muted = value; + return this; + } + + setLoop(value) { + if (value === undefined) { + value = true; + } + this.loop = value; + return this; + } +} + +var methods = { + resize: Resize +} + +Object.assign( + YoutubePlayer.prototype, + methods +); + +export default YoutubePlayer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/CanvasInput.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/CanvasInput.d.ts new file mode 100644 index 000000000..ef088e990 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/CanvasInput.d.ts @@ -0,0 +1,143 @@ +import DynamicText from '../dynamictext/DynamicText'; +import HiddenTextEdit from './textedit/HiddenTextEdit'; + +export default CanvasInput; + +declare namespace CanvasInput { + type AddCharCallbackType = ( + child: DynamicText.CharBob, + index: number, + canvasInput: CanvasInput + ) => void; + + type MoveCursorCallbackType = ( + currCursorIndex: number, + prevCursorIndex: number, + canvasInput: CanvasInput + ) => void; + + type ParseTextCallbackType = ( + text: string + ) => unknown; + + interface IConfigBackground extends DynamicText.IConfigBackground { + 'focus.color'?: string | number | null, + 'focus.color2'?: string | number | null, + 'focus.horizontalGradient'?: boolean, + + 'focus.stroke'?: string | number | null, + 'focus.strokeThickness'?: number, + + 'focus.cornerRadius'?: number | + ({ x?: number, y?: number }) | + DynamicText.IRadiusConfig, + 'focus.cornerIteration'?: number + } + + interface IConfigTextStyle extends DynamicText.IConfigTextStyle { + 'cursor.bold'?: boolean, + 'cursor.italic'?: boolean, + 'cursor.fontSize'?: string | number, + 'cursor.fontFamily'?: string, + 'cursor.color'?: string | number | null, + 'cursor.stroke'?: string | number | null, + 'cursor.strokeThickness'?: number, + 'cursor.shadowColor'?: string | number | null, + 'cursor.shadowOffsetX'?: number, + 'cursor.shadowOffsetY'?: number, + 'cursor.shadowBlur'?: number, + 'cursor.backgroundColor'?: string | number | null, + 'cursor.offsetX'?: number, + 'cursor.offsetY'?: number, + 'cursor.leftSpace'?: number, + 'cursor.rightSpace'?: number, + } + + interface IConfig extends DynamicText.IConfig { + textArea?: boolean; + + edit?: HiddenTextEdit.IConfig; + + background?: IConfigBackground, + + focusStyle?: DynamicText.IConfigBackground; + + style?: IConfigTextStyle + + cursorStyle?: DynamicText.IConfigTextStyle; + + onOpen?: HiddenTextEdit.OnOpenCallbackType; + onFocus?: HiddenTextEdit.OnOpenCallbackType; + + onClose?: HiddenTextEdit.OnCloseCallbackType; + onBlur?: HiddenTextEdit.OnCloseCallbackType; + + onUpdate?: HiddenTextEdit.OnUpdateCallbackType; + + onAddChar?: AddCharCallbackType; + + onMoveCursor?: MoveCursorCallbackType; + + parseTextCallback?: ParseTextCallbackType; + + readOnly?: boolean, + maxLength?: number, + minLength?: number, + selectAll?: boolean, + } +} + +declare class CanvasInput extends DynamicText { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + fixedWidth?: number, fixedHeight?: number, + config?: CanvasInput.IConfig + ); + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + config?: CanvasInput.IConfig + ); + constructor( + scene: Phaser.Scene, + config?: CanvasInput.IConfig + ); + + setText(text: string): this; + appendText(text: string): this; + + displayText: string; + setDisplayText(value: string): this; + + inputText: string; + setInputText(value: string): this; + + setParseTextCallback(callback?: CanvasInput.ParseTextCallbackType): this; + getValue(): unknown; + setValue(value: unknown): this; + value: unknown; + + setReadOnly(enable?: boolean): this; + readOnly: boolean; + + open(onCloseCallback?: Function): this; + close(): this; + readonly isOpened: boolean; + + setFocusStyle( + style: DynamicText.IConfigBackground + ): this; + + setCursorStyle( + style: DynamicText.IConfigTextStyle + ): this; + + setNumberInput(): this; + + setMaxLength(value: number): this; + maxLength: number; + + setMinLength(value: number): this; + minLength: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/CanvasInput.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/CanvasInput.js new file mode 100644 index 000000000..a33f8ebfc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/CanvasInput.js @@ -0,0 +1,273 @@ +import DynamicText from '../dynamictext/DynamicText.js'; +import CreateHiddenTextEdit from './textedit/CreateHiddenTextEdit.js'; +import InjectDefaultConfig from './methods/InjectDefaultConfig.js'; +import ExtractByPrefix from '../../../utils/object/ExtractByPrefix.js'; +import RegisterCursorStyle from './methods/RegisterCursorStyle.js'; +import RegisterFocusStyle from './methods/RegisterFocusStyle.js'; +import AddLastInsertCursor from './methods/AddLastInsertCursor.js'; +import SetText from './methods/SetText.js'; +import { IsChar } from '../dynamictext/bob/Types.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +class CanvasInput extends DynamicText { + constructor(scene, x, y, fixedWidth, fixedHeight, config) { + if (IsPlainObject(x)) { + config = x; + } else if (IsPlainObject(fixedWidth)) { + config = fixedWidth; + } + + if (config === undefined) { + config = {}; + } + + InjectDefaultConfig(config); + + // Set text later + var text = config.text; + if (text) { + delete config.text; + } + + var focusStyle = ExtractByPrefix(config.background, 'focus'); + var cursorStyle = ExtractByPrefix(config.style, 'cursor'); + + super(scene, x, y, fixedWidth, fixedHeight, config); + this.type = 'rexCanvasInput'; + + this._text = ''; + + this.textEdit = CreateHiddenTextEdit(this, config); + + if (config.focusStyle) { + Object.assign(focusStyle, config.focusStyle); + } + RegisterFocusStyle.call(this, focusStyle); + + if (config.cursorStyle) { + Object.assign(cursorStyle, config.cursorStyle); + } + RegisterCursorStyle.call(this, cursorStyle); + + var addCharCallback = config.onAddChar; + if (addCharCallback) { + this.on('addchar', addCharCallback); + } + + var cursorOutCallback = config.onCursorOut; + if (cursorOutCallback) { + this.on('cursorout', cursorOutCallback); + } + var cursorInCallback = config.onCursorIn; + if (cursorInCallback) { + this.on('cursorin', cursorInCallback); + } + var moveCursorCallback = config.onMoveCursor; + if (moveCursorCallback) { + this.on('movecursor', moveCursorCallback); + } + + this.setParseTextCallback(config.parseTextCallback); + + this.lastInsertCursor = AddLastInsertCursor(this); + if (text) { + this.setText(text); + } else { + // Still need run word wrap for lastInsertCursor child + this.runWordWrap(); + } + } + + addChild(child, index) { + super.addChild(child, index); + + if (Array.isArray(child)) { + var children = child; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if (IsChar(child)) { + this.emit('addchar', child, index + i, this); + } + } + } else { + if (IsChar(child)) { + this.emit('addchar', child, index, this); + } + } + + return this; + } + + get text() { + return this._text; + } + + set text(value) { + if (value == null) { + value = ''; + } else { + value = value.toString(); + } + if (this._text === value) { + return; + } + + SetText(this, value); + + this._text = value; + } + + setText(text) { + this.text = text; + return this; + } + + appendText(text) { + this.setText(this.text + text); + return this; + } + + setSize(width, height) { + if ((this.width === width) && (this.height === height)) { + return this; + } + + super.setSize(width, height); + + // Run wrap again since fixedWidth and fixedHeight are changed + this.runWrap(); + + return this; + } + + get displayText() { + return this.text; + } + + set displayText(value) { + this.text = value; + } + + setDisplayText(value) { + this.displayText = value; + return this; + } + + get inputText() { + return this.textEdit.text; + } + + set inputText(value) { + this.textEdit.text = value; + } + + setInputText(value) { + this.inputText = value; + return this; + } + + setParseTextCallback(callback) { + if (!callback) { + callback = DefaultParseTextCallback; + } + this.parseTextCallback = callback; + return this; + } + + get value() { + return this.parseTextCallback(this.text); + } + + set value(value) { + this.setText(value); + } + + getValue() { + return this.value; + } + + setValue(value) { + this.value = value; + return this; + } + + get readOnly() { + return this.textEdit.readOnly; + } + + set readOnly(value) { + this.textEdit.readOnly = value; + } + + setReadOnly(value) { + this.textEdit.setReadOnly(value); + return this; + } + + open(onCloseCallback) { + if (onCloseCallback) { + this.textEdit.once('close', onCloseCallback) + } + this.textEdit.open(); + return this; + } + + close() { + this.textEdit.close(); + return this; + } + + get isOpened() { + return this.textEdit.isOpened; + } + + setFocusStyle(style) { + this.focusStyle = style; + return this; + } + + setCursorStyle(style) { + this.cursorStyle = style; + return this; + } + + setNumberInput() { + this.textEdit.setNumberInput(); + this.parseTextCallback = Number; + return this; + } + + get maxLength() { + return this.textEdit.maxLength; + } + + set maxLength(value) { + this.textEdit.maxLength = value + } + + setMaxLength(value) { + this.maxLength = value; + return this; + } + + get minLength() { + return this.textEdit.minLength; + } + + set minLength(value) { + this.textEdit.minLength = value; + } + + setMinLength(value) { + this.minLength = value; + return this; + } + +} + +var DefaultParseTextCallback = function (text) { + return text; +} + +export default CanvasInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/Creator.js new file mode 100644 index 000000000..f9cdac8ac --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/Creator.js @@ -0,0 +1,16 @@ +import CanvasInput from './CanvasInput.js' + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', undefined); + var gameObject = new CanvasInput(this.scene, 0, 0, width, height, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/Factory.js new file mode 100644 index 000000000..558593a33 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/Factory.js @@ -0,0 +1,7 @@ +import CanvasInput from './CanvasInput.js' + +export default function (x, y, width, height, config) { + var gameObject = new CanvasInput(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/AddLastInsertCursor.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/AddLastInsertCursor.js new file mode 100644 index 000000000..c977bcfcf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/AddLastInsertCursor.js @@ -0,0 +1,13 @@ +import AddChild from '../../dynamictext/methods/AddChild.js'; + +var AddLastInsertCursor = function (textObject) { + var child = textObject.createCharChild('|'); // Use '|' to update render size + child.text = ''; // Render empty string '' + + // Invoke DynamicText's addChild method directly + AddChild.call(textObject, child); + + return child; +} + +export default AddLastInsertCursor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/InjectDefaultConfig.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/InjectDefaultConfig.js new file mode 100644 index 000000000..479082dad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/InjectDefaultConfig.js @@ -0,0 +1,36 @@ +import HasValue from '../../../../utils/object/HasValue.js'; +import SetValue from '../../../../utils/object/SetValue.js'; + +var InjectDefaultConfig = function (config) { + var isSingleLineMode = !config.textArea; + + if (!HasValue(config, 'wrap.vAlign')) { + var defaultValue = (isSingleLineMode) ? 'center' : 'top'; + SetValue(config, 'wrap.vAlign', defaultValue); + } + + if (!HasValue(config, 'wrap.charWrap')) { + SetValue(config, 'wrap.charWrap', true); + } + + if (!HasValue(config, 'wrap.maxLines')) { + var defaultValue = (isSingleLineMode) ? 1 : undefined; + SetValue(config, 'wrap.maxLines', defaultValue); + } + + if (!HasValue(config, 'wrap.useDefaultTextHeight')) { + SetValue(config, 'wrap.useDefaultTextHeight', true); + } + + if (!config.edit) { + config.edit = {}; + } + if (!HasValue(config.edit, 'inputType')) { + var defaultValue = (isSingleLineMode) ? 'text' : 'textarea'; + SetValue(config.edit, 'inputType', defaultValue); + } + + return config; +} + +export default InjectDefaultConfig; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/RegisterCursorStyle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/RegisterCursorStyle.js new file mode 100644 index 000000000..4f44105b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/RegisterCursorStyle.js @@ -0,0 +1,32 @@ +import IsEmpty from '../../../../utils/object/IsEmpty.js'; +import GetPartialData from '../../../../utils/object/GetPartialData.js'; +import IsKeyValueEqual from '../../../../utils/object/IsKeyValueEqual.js'; + +var RegisterCursorStyle = function (cursorStyle) { + if (IsEmpty(cursorStyle)) { + return; + } + + this + .setCursorStyle(cursorStyle) + .on('cursorin', function (child) { + var cursorStyle = this.cursorStyle; + var styleSave = GetPartialData(child.style, cursorStyle); + if (IsKeyValueEqual(cursorStyle, styleSave)) { + return; + } + + child.styleSave = styleSave; + child.modifyStyle(cursorStyle); + }, this) + .on('cursorout', function (child) { + if (!child.styleSave) { + return; + } + + child.modifyStyle(child.styleSave); + child.styleSave = undefined; + }, this) +} + +export default RegisterCursorStyle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/RegisterFocusStyle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/RegisterFocusStyle.js new file mode 100644 index 000000000..b72f8375a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/RegisterFocusStyle.js @@ -0,0 +1,35 @@ +import IsEmpty from '../../../../utils/object/IsEmpty.js'; +import GetPartialData from '../../../../utils/object/GetPartialData.js'; +import IsKeyValueEqual from '../../../../utils/object/IsKeyValueEqual.js'; + +var RegisterFocusStyle = function (focusStyle) { + if (IsEmpty(focusStyle)) { + return; + } + + this + .setFocusStyle(focusStyle) + .on('open', function () { + var child = this.background; + var focusStyle = this.focusStyle; + var styleSave = GetPartialData(child, focusStyle); + if (IsKeyValueEqual(focusStyle, styleSave)) { + return; + } + + child.styleSave = styleSave; + child.modifyStyle(focusStyle); + }, this) + .on('close', function () { + var child = this.background; + + if (!child.styleSave) { + return; + } + + child.modifyStyle(child.styleSave); + child.styleSave = undefined; + }, this) +} + +export default RegisterFocusStyle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/SetText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/SetText.js new file mode 100644 index 000000000..8c7baaa6e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/methods/SetText.js @@ -0,0 +1,41 @@ +import { diffChars } from '../../../../utils/jsdiff/index.js' + +const RemoveItem = Phaser.Utils.Array.Remove; + +var SetText = function (textObject, newText) { + var text = textObject.text; + if (newText === text) { + return; + } + + // textObject.setText(newText); + + // Remove lastInsertCursor directly + RemoveItem(textObject.children, textObject.lastInsertCursor); + + if (newText === '') { + textObject.removeChildren(); + } else { + var results = diffChars(text, newText); + var charIndex = 0; + for (var i = 0, cnt = results.length; i < cnt; i++) { + var result = results[i]; + if (result.removed) { + // Remove character at charIndex + textObject.removeText(charIndex, result.count); + } else if (result.added) { + textObject.insertText(charIndex, result.value); + charIndex += result.count; + } else { + charIndex += result.count; + } + } + } + + // Push back lastInsertCursor directly + textObject.children.push(textObject.lastInsertCursor); + + textObject.runWordWrap(); +} + +export default SetText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/ClearCursor.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/ClearCursor.js new file mode 100644 index 000000000..2510312fd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/ClearCursor.js @@ -0,0 +1,16 @@ +var ClearCursor = function (hiddenTextEdit) { + var prevCursorPosition = hiddenTextEdit.prevCursorPosition; + if (prevCursorPosition === null) { + return; + } + + var textObject = hiddenTextEdit.parent; + + var child = textObject.getCharChild(prevCursorPosition); + if (child) { + textObject.emit('cursorout', child, prevCursorPosition, textObject); + } + + hiddenTextEdit.prevCursorPosition = null; +} +export default ClearCursor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/ClearSelectRange.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/ClearSelectRange.js new file mode 100644 index 000000000..653445dc5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/ClearSelectRange.js @@ -0,0 +1,21 @@ +var ClearSelectRange = function (hiddenTextEdit) { + var prevSelectionStart = hiddenTextEdit.prevSelectionStart; + if (prevSelectionStart === null) { + return; + } + + var prevSelectionEnd = hiddenTextEdit.prevSelectionEnd; + + var textObject = hiddenTextEdit.parent; + for (var i = prevSelectionStart; i < prevSelectionEnd; i++) { + var child = textObject.getCharChild(i); + if (child) { + textObject.emit('cursorout', child, i, textObject); + } + } + + hiddenTextEdit.prevSelectionStart = null; + hiddenTextEdit.prevSelectionEnd = null; +} + +export default ClearSelectRange; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/CreateHiddenTextEdit.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/CreateHiddenTextEdit.js new file mode 100644 index 000000000..c710c7867 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/CreateHiddenTextEdit.js @@ -0,0 +1,23 @@ +import HiddenTextEdit from './HiddenTextEdit.js'; +import CopyProperty from '../../../../utils/object/CopyProperty.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const PropertiesList = [ + 'inputType', + 'onOpen', 'onFocus', 'onClose', 'onBlur', 'onUpdate', + 'enterClose', + 'readOnly', 'maxLength', 'minLength', 'selectAll' +]; + +var CreateHiddenTextEdit = function (parent, parentConfig) { + var config = GetValue(parentConfig, 'edit'); + if (config === undefined) { + config = {}; + } + + CopyProperty(parentConfig, config, PropertiesList); + + return new HiddenTextEdit(parent, config); +} + +export default CreateHiddenTextEdit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/HiddenTextEdit.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/HiddenTextEdit.d.ts new file mode 100644 index 000000000..dd26fbcda --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/HiddenTextEdit.d.ts @@ -0,0 +1,2 @@ +import HiddenTextEditBase from '../../../../behaviors/hiddentextedit/HiddenTextEditBase'; +export default HiddenTextEditBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/HiddenTextEdit.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/HiddenTextEdit.js new file mode 100644 index 000000000..74f922639 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/HiddenTextEdit.js @@ -0,0 +1,119 @@ +import HiddenTextEditBase from '../../../../behaviors/hiddentextedit/HiddenTextEditBase.js'; +import NumberInputUpdateCallback from '../../../../behaviors/hiddentextedit/defaultcallbacks/NumberInputUpdateCallback.js'; +import SelectRange from './SelectRange.js'; +import MoveCursor from './MoveCursor.js'; +import ClearSelectRange from './ClearSelectRange.js'; +import ClearCursor from './ClearCursor.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class HiddenTextEdit extends HiddenTextEditBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this.setSelectAllWhenFocusEnable(GetValue(config, 'selectAll', false)); + + this.cursorMoveStartIndex = null; + this.prevCursorPosition = null; + this.prevSelectionStart = null; + this.prevSelectionEnd = null; + this.firstClickAfterOpen = false; + + + gameObject + // Open editor by 'pointerdown' event + // Then set cursor position to nearest char + .on('pointerdown', function (pointer, localX, localY, event) { + var child = gameObject.getNearestChild(localX, localY); + var charIndex = gameObject.getCharIndex(child); + + if (!this.selectAllWhenFocus || !this.firstClickAfterOpen) { + this.setCursorPosition(charIndex); + } + + this.cursorMoveStartIndex = charIndex; + this.firstClickAfterOpen = false; + }, this) + .on('pointermove', function (pointer, localX, localY, event) { + if (!pointer.isDown) { + return; + } + var child = gameObject.getNearestChild(localX, localY); + var charIndex = gameObject.getCharIndex(child); + if (this.cursorMoveStartIndex < charIndex) { + this.selectText(this.cursorMoveStartIndex, charIndex + 1); + } else { + this.selectText(charIndex, this.cursorMoveStartIndex + 1); + } + }, this) + + this + .on('open', function () { + if (this.selectAllWhenFocus) { + this.selectAll(); + } + this.firstClickAfterOpen = true; + + gameObject.emit('open'); + }, this) + .on('close', function () { + gameObject.emit('close'); + }, this) + } + + initText() { + var textObject = this.parent; + this.prevCursorPosition = null; + this.setText(textObject.text); + return this; + } + + updateText() { + var textObject = this.parent; + + var text = this.text; + if (this.onUpdateCallback) { + var newText = this.onUpdateCallback(text, textObject, this); + if (newText != null) { + text = newText; + } + } + + if (textObject.text !== text) { + textObject.setText(text); + textObject.emit('textchange', text, textObject, this); + } + + if (this.isOpened) { + if (this.selectionStart !== this.selectionEnd) { + ClearCursor(this); + SelectRange(this); + } else { + ClearSelectRange(this); + MoveCursor(this); + } + } else { + ClearSelectRange(this); + ClearCursor(this); + } + + return this; + } + + setNumberInput() { + this.onUpdateCallback = NumberInputUpdateCallback; + return this; + } + + setSelectAllWhenFocusEnable(enable) { + if (enable === undefined) { + enable = true; + } + + this.selectAllWhenFocus = enable; + return this; + } +} + +export default HiddenTextEdit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/MoveCursor.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/MoveCursor.js new file mode 100644 index 000000000..c7da4fd8c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/MoveCursor.js @@ -0,0 +1,33 @@ +var MoveCursor = function (hiddenTextEdit) { + var textObject = hiddenTextEdit.parent; + var text = textObject.text; + + var cursorPosition = hiddenTextEdit.cursorPosition; + if (hiddenTextEdit.prevCursorPosition === cursorPosition) { + return; + } + + if (hiddenTextEdit.prevCursorPosition !== null) { + if (hiddenTextEdit.prevCursorPosition > text.length) { + hiddenTextEdit.prevCursorPosition = null; + } + } + + if (hiddenTextEdit.prevCursorPosition !== null) { + var child = textObject.getCharChild(hiddenTextEdit.prevCursorPosition); + if (child) { + textObject.emit('cursorout', child, hiddenTextEdit.prevCursorPosition, textObject); + } + } + if (cursorPosition != null) { + var child = textObject.getCharChild(cursorPosition); + if (child) { + textObject.emit('cursorin', child, cursorPosition, textObject); + } + } + textObject.emit('movecursor', cursorPosition, hiddenTextEdit.prevCursorPosition, textObject); + + hiddenTextEdit.prevCursorPosition = cursorPosition; +} + +export default MoveCursor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/SelectRange.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/SelectRange.js new file mode 100644 index 000000000..dc0e6f8d1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/canvasinput/textedit/SelectRange.js @@ -0,0 +1,46 @@ +var OnSelectRange = function (hiddenTextEdit) { + var textObject = hiddenTextEdit.parent; + // var text = textObject.text; + var selectionStart = (hiddenTextEdit.isOpened) ? hiddenTextEdit.selectionStart : null; + var selectionEnd = (hiddenTextEdit.isOpened) ? hiddenTextEdit.selectionEnd : null; + var prevSelectionStart = hiddenTextEdit.prevSelectionStart; + var prevSelectionEnd = hiddenTextEdit.prevSelectionEnd; + + if ((prevSelectionStart === selectionStart) && (prevSelectionEnd === selectionEnd)) { + return; + } + + var min, max; + if (prevSelectionStart === null) { + min = selectionStart; + max = selectionEnd; + } else { + min = Math.min(prevSelectionStart, selectionStart); + max = Math.max(prevSelectionEnd, selectionEnd); + } + + for (var i = min; i < max; i++) { + var inPrevSelectionRange = (prevSelectionStart === null) ? false : + (i >= prevSelectionStart) && (i < prevSelectionEnd); + var inSelectionRange = (i >= selectionStart) && (i < selectionEnd); + + + if (inPrevSelectionRange && inSelectionRange) { + continue; + } + + var child = textObject.getCharChild(i); + if (child) { + if (inPrevSelectionRange) { + textObject.emit('cursorout', child, i, textObject); + } else { + textObject.emit('cursorin', child, i, textObject); + } + } + } + + hiddenTextEdit.prevSelectionStart = selectionStart; + hiddenTextEdit.prevSelectionEnd = selectionEnd; +} + +export default OnSelectRange; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/Creator.js new file mode 100644 index 000000000..73ce534dc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/Creator.js @@ -0,0 +1,16 @@ +import DynamicText from './DynamicText.js' + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', undefined); + var gameObject = new DynamicText(this.scene, 0, 0, width, height, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/DynamicText.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/DynamicText.d.ts new file mode 100644 index 000000000..989da50b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/DynamicText.d.ts @@ -0,0 +1,325 @@ +// import * as Phaser from 'phaser'; +import Canvas from '../../canvas/canvas/Canvas'; +import Background from './bob/background/Background'; +import InnerBounds from './bob/innerbounds/InnerBounds'; +import { IConfigTextStyle as IConfigTextStyleBase } from './bob/char/TextStyle'; +import BobBaseClass from './bob/Base'; +import CharBobClass from './bob/char/CharData'; +import ImageBobClass from './bob/image/ImageData'; +import DrawBobClass from './bob/drawer/Drawer'; +import CommandBobClass from './bob/command/Command'; + + +export default DynamicText; + +declare namespace DynamicText { + + type PaddingTypes = number | + { left?: number, right?: number, top?: number, bottom?: number }; + + interface IRadiusConfig { + tl?: (number | { x?: number, y?: number }), + tr?: (number | { x?: number, y?: number }), + bl?: (number | { x?: number, y?: number }), + br?: (number | { x?: number, y?: number }) + } + + interface IConfigBackground { + color?: string | number | null, + color2?: string | number | null, + horizontalGradient?: boolean, + + stroke?: string | number | null, + strokeThickness?: number, + + cornerRadius?: number | + ({ x?: number, y?: number }) | + IRadiusConfig, + cornerIteration?: number + } + + interface IConfigInnerBounds { + color?: string | number | null, + color2?: string | number | null, + horizontalGradient?: boolean, + + stroke?: string | number | null, + strokeThickness?: number, + } + + interface IConfigTextStyle extends IConfigTextStyleBase { + + } + + interface IConfigImage { + width?: number, + height?: number, + scaleX?: number, + scaleY?: number, + } + + type HAlignTypes = 0 | 1 | 2 | 'left' | 'center' | 'right'; + type VAlignTypes = 0 | 1 | 2 | 'top' | 'center' | 'bottom'; + + interface IConfigWrapBase { + callback?: string | Function, + hAlign?: HAlignTypes, + vAlign?: VAlignTypes, + } + + interface IConfigWordWrap extends IConfigWrapBase { + padding?: { + top?: number, left?: number, right?: number, bottom?: number, + }, + ascent?: number, + lineHeight?: number, + useDefaultTextHeight?: boolean, + maxLines?: number, + wrapWidth?: number, + letterSpacing?: number, + charWrap?: boolean + } + + interface IConfigVerticalWrap extends IConfigWrapBase { + padding: { + top?: number, left?: number, right?: number, bottom?: number, + }, + lineWidth?: number, + maxLines?: number, + fixedChildHeight?: number, + charPerLine?: number, + wrapHeight?: number, + letterSpacing?: number, + rtl?: boolean, + } + + type BobBase = BobBaseClass; + type CharBob = CharBobClass; + type ImageBob = ImageBobClass; + type DrawBob = DrawBobClass; + type CommandBob = CommandBobClass; + type RenderChildTypes = CharBob | ImageBob | DrawBob; + + interface IWrapResult { + children: BobBase[], + lines: ({ + children: BobBase[], + width: number, + height: number + })[], + isLastPage: boolean + } + + interface IConfig { + x?: number, y?: number, + width?: number, height?: number, + + padding: PaddingTypes, + + background?: IConfigBackground, + + innerBounds?: IConfigInnerBounds, + + style?: IConfigTextStyle, + + text?: string, + + wrap?: IConfigWordWrap | IConfigVerticalWrap | IConfigWordWrap, + + testString?: string, + + childrenInteractive?: boolean, + } + +} + +declare class DynamicText extends Canvas { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + fixedWidth?: number, fixedHeight?: number, + config?: DynamicText.IConfig + ); + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + config?: DynamicText.IConfig + ); + constructor( + scene: Phaser.Scene, + config?: DynamicText.IConfig + ); + + background: Background; + innerBounds: InnerBounds; + children: DynamicText.BobBase[]; + lastAppendedChildren: DynamicText.BobBase[]; + + clearContent(): this; + + createCharChild( + text: string, + style?: DynamicText.IConfigTextStyle + ): DynamicText.CharBob; + createCharChildren( + text: string, + style?: DynamicText.IConfigTextStyle + ): DynamicText.CharBob[]; + setText( + text: string, + style?: DynamicText.IConfigTextStyle + ): this; + appendText( + text: string, + style?: DynamicText.IConfigTextStyle + ): this; + insertText( + index: number, + text: string, + style?: DynamicText.IConfigTextStyle + ): this; + getText(activeOnly?: boolean): string; + resetTextStyle(): this; + modifyTextStyle(style: DynamicText.IConfigTextStyle): this; + modifyDefaultTextStyle(style: DynamicText.IConfigTextStyle): this; + text: string; + + setTestString(testString: string): this; + testString: string; + + getCharChild( + charIndex: number, + activeOnly?: boolean + ): DynamicText.CharBob; + getCharChildIndex( + charIndex: number, + activeOnly?: boolean + ): DynamicText.CharBob; + getCharChildren( + activeOnly?: boolean, + out?: DynamicText.CharBob[] + ): DynamicText.CharBob[] + + createImageChild( + key: string, frame?: string | null, + config?: DynamicText.IConfigImage + ): DynamicText.ImageBob; + appendImage( + key: string, frame?: string | null, + config?: DynamicText.IConfigImage + ): this; + + createDrawerChild( + renderCallback: (this: DynamicText.DrawBob) => void, + width?: number, + height?: number + ): DynamicText.DrawBob; + appendDrawer( + renderCallback: (this: DynamicText.DrawBob) => void, + width?: number, + height?: number + ): this; + + createCommandChild( + name: string, + callback: (param: unknown, name: string) => any, + param: unknown, + scope?: Object + ): DynamicText.CommandBob; + appendCommand( + name: string, + callback: (param: unknown, name: string) => any, + param: unknown, + scope?: Object + ): this; + + removeChild(child: DynamicText.BobBase): this; + removeChildren(): this; + removeText(index: number, length?: number): this; + + popChild(child: DynamicText.BobBase): this; + + moveChildToFist(child: DynamicText.BobBase): this; + moveChildToLast(child: DynamicText.BobBase): this; + movechildUp(child: DynamicText.BobBase): this; + movechildDown(child: DynamicText.BobBase): this; + movechildAbove( + child: DynamicText.BobBase, + baseChild: DynamicText.BobBase + ): this; + movechildBelow( + child: DynamicText.BobBase, + baseChild: DynamicText.BobBase + ): this; + + runWordWrap( + config?: DynamicText.IConfigWordWrap + ): DynamicText.IWrapResult; + + runVerticalWrap( + config?: DynamicText.IConfigVerticalWrap + ): DynamicText.IWrapResult; + + runWrap( + config?: DynamicText.IConfigWordWrap | DynamicText.IConfigVerticalWrap | DynamicText.IConfigWrapBase + ): DynamicText.IWrapResult; + + setVAlign(align: DynamicText.VAlignTypes): this; + setHAlign(align: DynamicText.HAlignTypes): this; + + getChildren(): DynamicText.BobBase[]; + getLastAppendedChildren(): DynamicText.BobBase[]; + getActiveChildren(): DynamicText.BobBase[]; + + setBackgroundColor( + color?: string | number | null, + color2?: string | number | null, + horizontalGradient?: boolean + ): this; + setBackgroundStroke( + stroke?: string | number | null, + strokeThickness?: number, + ): this; + setBackgroundCornerRadius( + cornerRadius?: number | + ({ x?: number, y?: number }) | + DynamicText.IRadiusConfig, + cornerIteration?: number + ): this; + + setInnerBoundsColor( + color?: string | number | null, + color2?: string | number | null, + horizontalGradient?: boolean + ): this; + setInnerBoundsStroke( + stroke?: string | number | null, + strokeThickness?: number, + ): this; + + getNearestChild( + localX: number, + localY: number + ): DynamicText.BobBase; + + getCharWorldPosition( + child: DynamicText.BobBase, + offsetX?: number, offsetY?: number, out?: { x?: number, y?: number } + ): { x: number, y: number }; + + getCharWorldPosition( + child: DynamicText.BobBase, + out?: { x?: number, y?: number } + ): { x: number, y: number }; + + getCharWorldPosition( + childIndex: number, + offsetX?: number, offsetY?: number, out?: { x?: number, y?: number } + ): { x: number, y: number }; + + getCharWorldPosition( + childIndex: number, + out?: { x?: number, y?: number } + ): { x: number, y: number }; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/DynamicText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/DynamicText.js new file mode 100644 index 000000000..fe04573c1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/DynamicText.js @@ -0,0 +1,83 @@ +import Canvas from '../../canvas/canvasbase/Canvas.js'; +import { SetPadding } from '../../../utils/padding/PaddingMethods'; +import Background from './bob/background/Background.js'; +import InnerBounds from './bob/innerbounds/InnerBounds.js'; +import TextStyle from './bob/char/TextStyle.js'; +import Methods from './methods/Methods'; +import PoolManager from './poolmanager/PoolManager.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class DynamicText extends Canvas { + constructor(scene, x, y, fixedWidth, fixedHeight, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + fixedWidth = GetValue(config, 'width', 0); + fixedHeight = GetValue(config, 'height', 0); + } else if (IsPlainObject(fixedWidth)) { + config = fixedWidth; + fixedWidth = GetValue(config, 'width', 0); + fixedHeight = GetValue(config, 'height', 0); + } + + var width = (fixedWidth === 0) ? 1 : fixedWidth; + var height = (fixedHeight === 0) ? 1 : fixedHeight; + super(scene, x, y, width, height); + this.type = 'rexDynamicText'; + this.autoRound = true; + this.padding = SetPadding(); + this.wrapPadding = SetPadding(); + + var textStyleConfig = GetValue(config, 'style', undefined); + this.defaultTextStyle = new TextStyle(null, textStyleConfig); + this.textStyle = this.defaultTextStyle.clone(); + this.setTestString(GetValue(config, 'testString', '|MÉqgy')); + + this.background = new Background(this, GetValue(config, 'background', undefined)); + this.innerBounds = new InnerBounds(this, GetValue(config, 'innerBounds', undefined)); + this.children = []; + this.lastAppendedChildren = []; + this.lastOverChild = null; + this.poolManager = new PoolManager(config); + + this.setFixedSize(fixedWidth, fixedHeight); + this.setPadding(GetValue(config, 'padding', 0)); + this.setWrapConfig(GetValue(config, 'wrap', undefined)); + this.setChildrenInteractiveEnable(GetValue(config, 'childrenInteractive', false)); + + var text = GetValue(config, 'text', undefined); + if (text) { + this.setText(text); + } + } + + updateTexture() { + this.renderContent(); + super.updateTexture(); + return this; + } + + get text() { + return this.getText(true); + } + + set text(value) { + this.setText(value); + } + + setSize(width, height) { + this.setFixedSize(width, height); + return this; + } +} + +Object.assign( + DynamicText.prototype, + Methods +); + + +export default DynamicText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/Factory.js new file mode 100644 index 000000000..6b260229e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/Factory.js @@ -0,0 +1,7 @@ +import DynamicText from './DynamicText.js' + +export default function (x, y, width, height, config) { + var gameObject = new DynamicText(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/Base.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/Base.d.ts new file mode 100644 index 000000000..dc9c9beef --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/Base.d.ts @@ -0,0 +1,21 @@ +// import * as Phaser from 'phaser'; +import DataMethods from '../../../../utils/data/DataMethods'; +import DynamicText from '../DynamicText'; + +export default class Base extends DataMethods { + parent: DynamicText; + readonly type: string; + readonly renderable: boolean; + + setActive(active?: boolean): this; + active: boolean; + + setDirty(dirty?: boolean): this; + + scene: Phaser.Scene; + canvas: HTMLCanvasElement; + context: CanvasRenderingContext2D; + + render(): void; + contains(x: number, y: number): boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/Base.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/Base.js new file mode 100644 index 000000000..d83012068 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/Base.js @@ -0,0 +1,85 @@ +import DataMethods from '../../../../utils/data/DataMethods.js' + +class Base { + constructor(parent, type) { + this.setParent(parent); + this.type = type; + this.renderable = false; + + this.reset().setActive(); + } + + destroy() { + this.parent.removeChild(this); + } + + setParent(parent) { + this.parent = parent; + return this; + } + + get scene() { + return this.parent.scene; + } + + get canvas() { + return (this.parent) ? this.parent.canvas : null; + } + + get context() { + return (this.parent) ? this.parent.context : null; + } + + setDirty(dirty) { + if (dirty && this.parent) { + this.parent.dirty = true; + } + return this; + } + + get active() { + return this._active; + } + + set active(value) { + this.setDirty(this._active != value); + this._active = value; + } + + setActive(active) { + if (active === undefined) { + active = true; + } + this.active = active; + return this; + } + + modifyPorperties(o) { + return this; + } + + // Override + onFree() { + this.reset().setParent(); + } + + // Override + reset() { + return this; + } + + // Override + render() { } + + // Override + contains(x, y) { + return false; + } +} + +Object.assign( + Base.prototype, + DataMethods +); + +export default Base; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/Types.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/Types.js new file mode 100644 index 000000000..64a3a925c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/Types.js @@ -0,0 +1,38 @@ +const CharTypeName = 'text'; +const ImageTypeName = 'image'; +const DrawerTypeName = 'drawer'; +const SpaceTypeName = 'space'; +const CmdTypeName = 'command'; + +var IsNewLineChar = function (bob) { + return (bob.type === CharTypeName) && (bob.text === '\n'); +} + +var IsPageBreakChar = function (bob) { + return (bob.type === CharTypeName) && (bob.text === '\f'); +} + +var IsSpaceChar = function (bob) { + return (bob.type === CharTypeName) && (bob.text === ' '); +} + +var IsChar = function (bob) { + return (bob.type === CharTypeName); +} + +var IsCommand = function (bob) { + return bob.type === CmdTypeName; +} + +export { + CharTypeName, + ImageTypeName, + DrawerTypeName, + SpaceTypeName, + CmdTypeName, + IsNewLineChar, + IsPageBreakChar, + IsSpaceChar, + IsChar, + IsCommand +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/background/Background.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/background/Background.d.ts new file mode 100644 index 000000000..59883ff23 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/background/Background.d.ts @@ -0,0 +1,22 @@ +import RenderBase from '../renderbase/RenderBase'; + +export default class Background extends RenderBase { + readonly type: 'background'; + + setColor( + color?: string | number | null, + color2?: string | number | null, + isHorizontalGradient?: boolean + ): this; + + setStroke( + color?: string | number | null, + lineWidth?: number + ): this; + + setCornerRadius( + radius?: number, + iteration?: number | null + ): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/background/Background.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/background/Background.js new file mode 100644 index 000000000..fe9f3ebfa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/background/Background.js @@ -0,0 +1,169 @@ +import RenderBase from '../renderbase/RenderBase.js'; +import GetStyle from '../../../../../utils/canvas/GetStyle.js'; +import GetProperty from '../utils/GetProperty.js'; +import DrawRoundRectangleBackground from '../../../../canvas/utils/DrawRoundRectangleBackground.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Background extends RenderBase { + constructor(parent, config) { + super(parent, 'background'); + + this.setColor( + GetValue(config, 'color', null), + GetValue(config, 'color2', null), + GetValue(config, 'horizontalGradient', true) + ); + + this.setStroke( + GetValue(config, 'stroke', null), + GetValue(config, 'strokeThickness', 2) + ); + + this.setCornerRadius( + GetValue(config, 'cornerRadius', 0), + GetValue(config, 'cornerIteration', null) + ); + } + + set color(value) { + value = GetStyle(value, this.canvas, this.context); + this.setDirty(this._color != value); + this._color = value; + } + + get color() { + return this._color; + } + + set color2(value) { + value = GetStyle(value, this.canvas, this.context); + this.setDirty(this._color2 != value); + this._color2 = value; + } + + get color2() { + return this._color2; + } + + set horizontalGradient(value) { + this.setDirty(this._horizontalGradient != value); + this._horizontalGradient = value; + } + + get horizontalGradient() { + return this._horizontalGradient; + } + + setColor(color, color2, isHorizontalGradient) { + if (isHorizontalGradient === undefined) { + isHorizontalGradient = true; + } + + this.color = color; + this.color2 = color2; + this.horizontalGradient = isHorizontalGradient; + return this; + } + + set stroke(value) { + value = GetStyle(value, this.canvas, this.context); + this.setDirty(this._stroke != value); + this._stroke = value; + } + + get stroke() { + return this._stroke; + } + + set strokeThickness(value) { + this.setDirty(this._strokeThickness != value); + this._strokeThickness = value; + } + + get strokeThickness() { + return this._strokeThickness; + } + + setStroke(color, lineWidth) { + if (color != null) { + if (lineWidth === undefined) { + lineWidth = 2; + } + } + this.stroke = color; + this.strokeThickness = lineWidth; + return this; + } + + set cornerRadius(value) { + this.setDirty(this._cornerRadius != value); + this._cornerRadius = value; + } + + get cornerRadius() { + return this._cornerRadius; + } + + set cornerIteration(value) { + this.setDirty(this._cornerIteration != value); + this._cornerIteration = value; + } + + get cornerIteration() { + return this._cornerIteration; + } + + modifyStyle(o) { + if (o.hasOwnProperty('color')) { + this.setColor( + o.color, + GetProperty('color2', o, this), + GetProperty('horizontalGradient', o, this), + ); + } + if (o.hasOwnProperty('stroke')) { + this.setStroke( + o.stroke, + GetProperty('strokeThickness', o, this), + ); + } + if (o.hasOwnProperty('cornerRadius')) { + this.setCornerRadius( + o.cornerRadius, + GetProperty('cornerIteration', o, this), + ); + } + + return this; + } + + modifyPorperties(o) { + super.modifyPorperties(o); + + this.modifyStyle(o); + + return this; + } + + setCornerRadius(radius, iteration) { + this.cornerRadius = radius; + this.cornerIteration = iteration; + return this; + } + + renderContent() { + DrawRoundRectangleBackground( + this.parent, + this.color, + this.stroke, + this.strokeThickness, + this.cornerRadius, + this.color2, + this.horizontalGradient, + this.cornerIteration + ); + } +} + +export default Background; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/CharData.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/CharData.d.ts new file mode 100644 index 000000000..b63504ee3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/CharData.d.ts @@ -0,0 +1,11 @@ +import RenderBase from '../renderbase/RenderBase'; + +export default class CharData extends RenderBase { + readonly type: 'text'; + + readonly textWidth: number; + readonly textHeight: number; + readonly ascent: number; + readonly descent: number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/CharData.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/CharData.js new file mode 100644 index 000000000..7015623d5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/CharData.js @@ -0,0 +1,221 @@ +import RenderBase from '../renderbase/RenderBase.js'; +import { CharTypeName } from '../Types.js'; +import TextStyle from './TextStyle.js'; + +class CharData extends RenderBase { + constructor( + parent, + text, + style + ) { + super(parent, CharTypeName); + this.updateTextFlag = false; + this.style = new TextStyle(this, style); + this.setText(text); + } + + get autoRound() { + return this.parent.autoRound; + } + + get offsetX() { + return this.style.offsetX; + } + + set offsetX(value) { + if (this.style) { + this.style.offsetX = value; + } + } + + get offsetY() { + return this.style.offsetY; + } + + set offsetY(value) { + if (this.style) { + this.style.offsetY = value; + } + } + + get leftSpace() { + return this.style.leftSpace * this.scaleX; + } + + set leftSpace(value) { + if (this.style) { + this.style.leftSpace = value; + } + super.leftSpace = value; + } + + get rightSpace() { + return this.style.rightSpace * this.scaleX; + } + + set rightSpace(value) { + if (this.style) { + this.style.rightSpace = value; + } + super.rightSpace = value; + } + + get align() { + return this.style.align; + } + + set align(value) { + if (this.style) { + this.style.align = value; + } + } + + modifyStyle(style) { + this.setDirty(true); + this.style.modify(style); + + if (this.updateTextFlag) { + this.updateTextSize(); + } + return this; + } + + modifyPorperties(o) { + if (!o) { + return this; + } + + this.modifyStyle(o); + super.modifyPorperties(o); + return this; + } + + setText(text) { + this.setDirty(this.text != text); + this.text = text; + + this.updateTextSize(); + + return this; + } + + updateTextSize() { + var text = this.text; + // Is new-line, page-break, or empty character + if ((text === '\n') || (text === '\f') || (text === '')) { + this.textWidth = 0; + this.textHeight = 0; + this.ascent = 0; + this.descent = 0; + + } else { + var metrics = this.style.getTextMetrics(this.context, this.text); + this.textWidth = metrics.width; + + var ascent, descent; + if ('actualBoundingBoxAscent' in metrics) { + ascent = metrics.actualBoundingBoxAscent; + descent = metrics.actualBoundingBoxDescent; + } else { + ascent = 0; + descent = 0; + } + + this.textHeight = ascent + descent; + this.ascent = ascent; + this.descent = descent; + } + + this.updateTextFlag = false; + return this; + } + + get width() { + return this.textWidth * this.scaleX; + } + + set width(value) { + if (this.textWidth > 0) { + this.scaleX = value / this.textWidth; + } else { + this.scaleX = 1; + } + } + + get height() { + return this.textHeight * this.scaleY; + } + + set height(value) { + if (this.textHeight > 0) { + this.scaleY = value / this.textHeight; + } else { + this.scaleY = 1; + } + } + + get willRender() { + var text = this.text; + if ((text === '\n') || (text === '\f')) { + return false; + } + + return super.willRender; + } + + renderContent() { + var context = this.context; + var textStyle = this.style; + + if (textStyle.hasBackgroundColor) { + context.fillStyle = textStyle.backgroundColor; + + var x = this.drawTLX; + var width = this.drawTRX - x; + + var bottomY = textStyle.backgroundBottomY; + if (bottomY == null) { + bottomY = this.drawBLY; + } + var height = textStyle.backgroundHeight; + if (height == null) { + height = bottomY - this.drawTLY; + } + var y = bottomY - height; + + context.fillRect(x, y, width, height); + } + + var hasFill = textStyle.hasFill, + hasStroke = textStyle.hasStroke; + + if (!hasFill && !hasStroke) { + return; + } + + textStyle.syncFont(context).syncStyle(context); + // textBaseline = 'alphabetic' + + if (hasStroke) { + textStyle.syncShadow(context); + context.strokeText(this.text, 0, 0); + } + + if (hasFill) { + textStyle.syncShadow(context); + context.fillText(this.text, 0, 0); + } + } + + get drawTLX() { return -this.leftSpace; } + get drawTLY() { return -this.ascent; } + get drawBLX() { return -this.leftSpace; } + get drawBLY() { return this.descent; } + get drawTRX() { return this.textWidth + this.rightSpace; } + get drawTRY() { return -this.ascent; } + get drawBRX() { return this.textWidth + this.rightSpace; } + get drawBRY() { return this.descent; } + +} + +export default CharData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/TextStyle.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/TextStyle.d.ts new file mode 100644 index 000000000..3f9477287 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/TextStyle.d.ts @@ -0,0 +1,74 @@ +export interface IConfigTextStyle { + bold?: boolean, + italic?: boolean, + fontSize?: string | number, + fontFamily?: string, + color?: string | number | null, + stroke?: string | number | null, + strokeThickness?: number, + shadowColor?: string | number | null, + shadowOffsetX?: number, + shadowOffsetY?: number, + shadowBlur?: number, + backgroundColor?: string | number | null, + offsetX?: number, + offsetY?: number, + leftSpace?: number, + rightSpace?: number, +} + +export default class TextStyle { + modify(o?: IConfigTextStyle): this; + + setBold(bold?: boolean): this; + bold: boolean; + setItalic(italic?: boolean): this; + italic: boolean; + + setFontSize(fontSize: string | number): this; + fontSize: string; + + setFontFamily(fontFamily: string): this; + fontFamily: string; + + readonly font: string; + + setColor(color?: number | string | null): this; + color: string | null; + + setStrokeStyle( + stroke?: number | string | null, + strokeThickness?: number + ): this; + stroke: string | null; + strokeThickness: number; + + setShadowColor(color?: number | string | null): this; + shadowColor: string | null; + setShadowOffset(offsetX?: number, offsetY?: number): this; + shadowOffsetX: number; + shadowOffsetY: number; + setShadowBlur(blur?: number): this; + shaodwBlur: number; + setShadow( + color?: number | string | null, + offsetX?: number, + offsetY?: number, + blur?: number + ): this; + + setBackgroundColor(color?: number | string | null): this; + + setOffsetX(offsetX?: number): this; + offsetX: number; + setOffsetY(offsetY?: number): this; + offsetY: number; + setOffset(offsetX?: number, offsetY?: number): this; + + setLeftSpace(space?: number): this; + leftSpace: number; + setRightSpace(space?: number): this; + rightSpace: number; + setSpace(leftSpace?: number, rightSpace?: number): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/TextStyle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/TextStyle.js new file mode 100644 index 000000000..c4da50805 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/char/TextStyle.js @@ -0,0 +1,383 @@ +import GetStyle from '../../../../../utils/canvas/GetStyle.js'; +import GetProperty from '../utils/GetProperty.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class TextStyle { + constructor(parent, config) { + this.parent = parent; + this.set(config); + } + + toJSON() { + return { + bold: this.bold, + italic: this.italic, + fontSize: this.fontSize, + fontFamily: this.fontFamily, + color: this.color, + stroke: this.stroke, + strokeThickness: this.strokeThickness, + shaodwColor: this.shadowColor, + shadowBlur: this.shadowBlur, + shadowOffsetX: this.shadowOffsetX, + shadowOffsetY: this.shadowOffsetY, + offsetX: this.offsetX, + offsetY: this.offsetY, + leftSpace: this.leftSpace, + rightSpace: this.rightSpace, + backgroundHeight: this.backgroundHeight, + backgroundBottomY: this.backgroundBottomY, + align: this.align + } + } + + set(o) { + this.setBold(GetValue(o, 'bold', false)); + this.setItalic(GetValue(o, 'italic', false)); + this.setFontSize(GetValue(o, 'fontSize', '16px')); + this.setFontFamily(GetValue(o, 'fontFamily', 'Courier')); + this.setColor(GetValue(o, 'color', '#fff')); + this.setStrokeStyle( + GetValue(o, 'stroke', null), + GetValue(o, 'strokeThickness', 0) + ); + this.setShadow( + GetValue(o, 'shadowColor', null), + GetValue(o, 'shadowOffsetX', 0), + GetValue(o, 'shadowOffsetY', 0), + GetValue(o, 'shadowBlur', 0) + ); + this.setOffset( + GetValue(o, 'offsetX', 0), + GetValue(o, 'offsetY', 0) + ); + this.setSpace( + GetValue(o, 'leftSpace', 0), + GetValue(o, 'rightSpace', 0) + ); + this.setAlign(GetValue(o, 'align', undefined)); + this.setBackgroundColor(GetValue(o, 'backgroundColor', null)); + this.setBackgroundHeight(GetValue(o, 'backgroundHeight', undefined)); + this.setBackgroundBottomY(GetValue(o, 'backgroundBottomY', undefined)); + + return this; + } + + modify(o) { + if (o.hasOwnProperty('bold')) { + this.setBold(o.bold); + } + if (o.hasOwnProperty('italic')) { + this.setItalic(o.italic); + } + if (o.hasOwnProperty('fontSize')) { + this.setFontSize(o.fontSize); + } + if (o.hasOwnProperty('fontFamily')) { + this.setFontFamily(o.fontFamily); + } + if (o.hasOwnProperty('color')) { + this.setColor(o.color); + } + if (o.hasOwnProperty('stroke') || o.hasOwnProperty('strokeThickness')) { + this.setStrokeStyle( + GetProperty('stroke', o, this), + GetProperty('strokeThickness', o, this) + ); + } + + if (o.hasOwnProperty('shadowColor')) { + this.setShadowColor(o.shadowColor); + } + + if (o.hasOwnProperty('shadowOffsetX') || o.hasOwnProperty('shadowOffsetY')) { + this.setShadowOffset( + GetProperty('shadowOffsetX', o, this), + GetProperty('shadowOffsetY', o, this), + ); + } + + if (o.hasOwnProperty('shadowBlur')) { + this.setShadowBlur(o.shaodwBlur); + } + + if (o.hasOwnProperty('offsetX')) { + this.setOffsetX(o.offsetX); + } + if (o.hasOwnProperty('offsetY')) { + this.setOffsetY(o.offsetY); + } + + if (o.hasOwnProperty('leftSpace')) { + this.setLeftSpace(o.leftSpace); + } + if (o.hasOwnProperty('rightSpace')) { + this.setRightSpace(o.rightSpace); + } + + if (o.hasOwnProperty('align')) { + this.setAlign(o.align); + } + + if (o.hasOwnProperty('backgroundColor')) { + this.setBackgroundColor(o.backgroundColor); + } + + if (o.hasOwnProperty('backgroundHeight')) { + this.setBackgroundHeight(o.backgroundHeight); + } + if (o.hasOwnProperty('backgroundBottomY')) { + this.setBackgroundBottomY(o.backgroundBottomY); + } + + return this; + } + + setUpdateTextFlag() { + if (this.parent) { + this.parent.updateTextFlag = true; + } + return this; + } + + clone() { + return new TextStyle(null, this.toJSON()); + } + + copyFrom(sourceTextStyle) { + this.set(sourceTextStyle.toJSON()); + return this; + } + + copyTo(targetTextStyle) { + targetTextStyle.set(this.toJSON()); + return this; + } + + setBold(value) { + if (value === undefined) { + value = true; + } + this.bold = value; + this.setUpdateTextFlag(); + return this; + } + + setItalic(value) { + if (value === undefined) { + value = true; + } + this.italic = value; + this.setUpdateTextFlag(); + return this; + } + + get fontStyle() { + if (this.bold && this.italic) { + return 'bold italic'; + } else if (this.bold) { + return 'bold'; + } else if (this.italic) { + return 'italic'; + } else { + return ''; + } + } + + setFontSize(fontSize) { + if (typeof (fontSize) === 'number') { + fontSize = `${fontSize}px`; + } + this.fontSize = fontSize; + this.setUpdateTextFlag(); + return this; + } + + setFontFamily(fontFamily) { + this.fontFamily = fontFamily; + this.setUpdateTextFlag(); + return this; + } + + get font() { + return `${this.fontStyle} ${this.fontSize} ${this.fontFamily}`; + } + + setColor(color) { + this.color = GetStyle(color); + return this; + } + + get hasFill() { + return this.color != null; + } + + setStrokeStyle(stroke, strokeThickness) { + this.stroke = GetStyle(stroke); + if (strokeThickness !== undefined) { + this.strokeThickness = strokeThickness; + } + return this; + } + + setStrokeThickness(strokeThickness) { + this.strokeThickness = strokeThickness; + return this; + } + + get hasStroke() { + return (this.stroke != null) && (this.strokeThickness > 0); + } + + setShadowColor(color) { + this.shadowColor = GetStyle(color); + return this; + } + + setShadowOffset(offsetX, offsetY) { + if (offsetX === undefined) { + offsetX = 0 + } + if (offsetY === undefined) { + offsetY = 0 + } + + this.shadowOffsetX = offsetX; + this.shadowOffsetY = offsetY; + return this; + } + + setShadowBlur(blur) { + if (blur === undefined) { + blur = 0 + } + + this.shaodwBlur = blur; + return this; + } + + setShadow(color, offsetX, offsetY, blur) { + this + .setShadowColor(color) + .setShadowOffset(offsetX, offsetY) + .setShadowBlur(blur); + return this; + } + + setBackgroundColor(color) { + this.backgroundColor = GetStyle(color); + return this; + } + + get hasBackgroundColor() { + return this.backgroundColor != null; + } + + setBackgroundHeight(height) { + this.backgroundHeight = height; + return this; + } + + setBackgroundBottomY(y) { + this.backgroundBottomY = y; + return this; + } + + setOffsetX(offsetX) { + if (offsetX === undefined) { + offsetX = 0 + } + + this.offsetX = offsetX; + return this; + } + + setOffsetY(offsetY) { + if (offsetY === undefined) { + offsetY = 0 + } + + this.offsetY = offsetY; + return this; + } + + setOffset(offsetX, offsetY) { + this + .setOffsetX(offsetX) + .setOffsetY(offsetY); + return this; + } + + setLeftSpace(space) { + if (space === undefined) { + space = 0 + } + + this.leftSpace = space; + return this; + } + + setRightSpace(space) { + if (space === undefined) { + space = 0 + } + + this.rightSpace = space; + return this; + } + + setSpace(leftSpace, rightSpace) { + this + .setLeftSpace(leftSpace) + .setRightSpace(rightSpace); + return this; + } + + setAlign(align) { + this.align = align; + return this; + } + + syncFont(context) { + context.font = this.font; + return this; + } + + syncStyle(context) { + context.textBaseline = 'alphabetic'; + + var hasFill = this.hasFill; + var hasStroke = this.hasStroke; + context.fillStyle = (hasFill) ? this.color : '#000'; + + context.strokeStyle = (hasStroke) ? this.stroke : '#000'; + context.lineWidth = (hasStroke) ? this.strokeThickness : 0; + context.lineCap = 'round'; + context.lineJoin = 'round'; + + return this; + } + + syncShadow(context) { + if (context.shadowColor != null) { + context.shadowColor = this.shadowColor; + context.shadowOffsetX = this.shadowOffsetX; + context.shadowOffsetY = this.shadowOffsetY; + context.shadowBlur = this.shadowBlur; + } else { + context.shadowColor = 0; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; + } + } + + getTextMetrics(context, text) { + this.syncFont(context).syncStyle(context); + return context.measureText(text); + } + +} + +export default TextStyle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/command/Command.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/command/Command.d.ts new file mode 100644 index 000000000..c5c95cc5a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/command/Command.d.ts @@ -0,0 +1,8 @@ +import Base from '../Base'; + +export default class Command extends Base { + readonly type: 'command'; + + name: string; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/command/Command.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/command/Command.js new file mode 100644 index 000000000..2bc1fe80c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/command/Command.js @@ -0,0 +1,49 @@ +import Base from '../Base.js'; +import { CmdTypeName } from '../Types.js'; + +class Command extends Base { + constructor(parent, name, callback, param, scope) { + super(parent, CmdTypeName); + + this + .setName(name) + .setParameter(param) + .setCallback(callback, scope); + } + + setName(name) { + this.name = name; + return this; + } + + setParameter(param) { + this.param = param; + return this; + } + + setCallback(callback, scope) { + this.callback = callback; + this.scope = scope; + return this; + } + + exec() { + var result; + if (this.scope) { + result = this.callback.call(this.scope, this.param, this.name); + } else { + result = this.callback(this.param, this.name); + } + return result; + } + + onFree() { + super.onFree(); + this + .setName() + .setCallback() + .setParameter() + } +} + +export default Command; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/drawer/Drawer.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/drawer/Drawer.d.ts new file mode 100644 index 000000000..5b3ee1147 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/drawer/Drawer.d.ts @@ -0,0 +1,13 @@ +import RenderBase from '../renderbase/RenderBase'; + +export default class Drawer extends RenderBase { + readonly type: 'drawer'; + + setRenderCallback(callback?: (this: Drawer) => void): this; + + setDrawerSize(isAllSize: true): this; + setDrawerSize(width?: number, height?: number): this; + readonly drawerWidth: number; + readonly drawerHeight: number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/drawer/Drawer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/drawer/Drawer.js new file mode 100644 index 000000000..3d4fed472 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/drawer/Drawer.js @@ -0,0 +1,85 @@ +import RenderBase from '../renderbase/RenderBase.js'; +import { DrawerTypeName } from '../Types.js'; + +class Drawer extends RenderBase { + constructor(parent, renderCallback, width, height) { + super(parent, DrawerTypeName); + + this.setRenderCallback(renderCallback); + this.setDrawerSize(width, height); + } + + setRenderCallback(callback) { + if (callback) { + this.renderContent = callback.bind(this); + } else { + delete this.renderContent; + } + return this; + } + + setDrawerSize(width, height) { + // Whole canvas + if (width === true) { + this.toLocalPosition = false; + width = undefined; + height = undefined; + } else { + this.toLocalPosition = true; + } + + if (width === undefined) { + width = 0; + } + if (height === undefined) { + height = width; + } + + this.drawerWidth = width; + this.drawerHeight = height; + + return this; + } + + onFree() { + super.onFree(); + this + .setRenderCallback() + } + + get width() { + return this.drawerWidth * this.scaleX; + } + + set width(value) { + this.setDirty(this.width !== value); + this.scaleX = (this.drawerWidth > 0) ? value / this.drawerWidth : 1; + } + + get height() { + return this.drawerHeight * this.scaleY; + } + + set height(value) { + this.setDirty(this.height !== value); + this.scaleY = (this.drawerHeight > 0) ? value / this.drawerHeight : 1; + } + + get offsetY() { + return -this.height; + } + + set offsetY(value) { } + + get drawTLX() { return -this.leftSpace; } + get drawTLY() { return 0; } + get drawBLX() { return -this.leftSpace; } + get drawBLY() { return this.drawerHeight; } + get drawTRX() { return this.drawerWidth + this.rightSpace; } + get drawTRY() { return 0; } + get drawBRX() { return this.drawerWidth + this.rightSpace; } + get drawBRY() { return this.drawerHeight; } + +} + +export default Drawer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/image/ImageData.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/image/ImageData.d.ts new file mode 100644 index 000000000..f16e06add --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/image/ImageData.d.ts @@ -0,0 +1,13 @@ +import RenderBase from '../renderbase/RenderBase'; + +export default class ImageData extends RenderBase { + readonly type: 'image'; + + setTexture(key: string, frame?: string | null): this; + key: string; + frame: string | null; + + readonly frameWidth: number; + readonly frameHeight: number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/image/ImageData.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/image/ImageData.js new file mode 100644 index 000000000..85f705ff1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/image/ImageData.js @@ -0,0 +1,106 @@ +import RenderBase from '../renderbase/RenderBase.js'; +import { ImageTypeName } from '../Types.js'; + +class ImageData extends RenderBase { + constructor( + parent, + key, frame + ) { + super(parent, ImageTypeName); + this.setTexture(key, frame); + } + + get frameWidth() { + return (this.frameObj) ? this.frameObj.cutWidth : 0; + } + + get frameHeight() { + return (this.frameObj) ? this.frameObj.cutHeight : 0; + } + + get offsetY() { + return -this.height; + } + + set offsetY(value) { } + + get key() { + return this._key; + } + + set key(value) { + this.setDirty(this._key != value); + this._key = value; + } + + get frame() { + return this._frame; + } + + set frame(value) { + this.setDirty(this._frame != value); + this._frame = value; + } + + setTexture(key, frame) { + this.key = key; + this.frame = frame; + + this.frameObj = this.scene.sys.textures.getFrame(key, frame); + return this; + } + + get width() { + return this.frameWidth * this.scaleX; + } + + set width(value) { + this.setDirty(this.width !== value); + this.scaleX = value / this.frameWidth; + } + + get height() { + return this.frameHeight * this.scaleY; + } + + set height(value) { + this.setDirty(this.height !== value); + this.scaleY = value / this.frameHeight; + } + + setHeight(height, keepAspectRatio) { + if (keepAspectRatio === undefined) { + keepAspectRatio = false; + } + this.height = height; + + if (keepAspectRatio) { + this.scaleX = this.scaleY; + } + return this; + } + + renderContent() { + var context = this.context; + var frame = this.frameObj; + + var width = this.frameWidth, + height = this.frameHeight; + context.drawImage( + frame.source.image, // image + frame.cutX, frame.cutY, width, height, + 0, 0, width, height, + ); + } + + get drawTLX() { return -this.leftSpace; } + get drawTLY() { return 0; } + get drawBLX() { return -this.leftSpace; } + get drawBLY() { return this.frameHeight; } + get drawTRX() { return this.frameWidth + this.rightSpace; } + get drawTRY() { return 0; } + get drawBRX() { return this.frameWidth + this.rightSpace; } + get drawBRY() { return this.frameHeight; } +} + +export default ImageData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/innerbounds/InnerBounds.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/innerbounds/InnerBounds.d.ts new file mode 100644 index 000000000..f53a51e71 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/innerbounds/InnerBounds.d.ts @@ -0,0 +1,17 @@ +import RenderBase from '../renderbase/RenderBase'; + +export default class InnerBounds extends RenderBase { + readonly type: 'innerbounds'; + + setColor( + color?: string | number | null, + color2?: string | number | null, + isHorizontalGradient?: boolean + ): this; + + setStroke( + color?: string | number | null, + lineWidth?: number + ): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/innerbounds/InnerBounds.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/innerbounds/InnerBounds.js new file mode 100644 index 000000000..2ea48e8e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/innerbounds/InnerBounds.js @@ -0,0 +1,145 @@ +import RenderBase from '../renderbase/RenderBase.js'; +import GetStyle from '../../../../../utils/canvas/GetStyle.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class InnerBounds extends RenderBase { + constructor(parent, config) { + super(parent, 'innerbounds'); + + this.setColor( + GetValue(config, 'color', null), + GetValue(config, 'color2', null), + GetValue(config, 'horizontalGradient', true) + ); + + this.setStroke( + GetValue(config, 'stroke', null), + GetValue(config, 'strokeThickness', 2) + ); + } + + set color(value) { + value = GetStyle(value, this.canvas, this.context); + this.setDirty(this._color != value); + this._color = value; + } + + get color() { + return this._color; + } + + set color2(value) { + value = GetStyle(value, this.canvas, this.context); + this.setDirty(this._color2 != value); + this._color2 = value; + } + + get color2() { + return this._color2; + } + + set horizontalGradient(value) { + this.setDirty(this._horizontalGradient != value); + this._horizontalGradient = value; + } + + get horizontalGradient() { + return this._horizontalGradient; + } + + setColor(color, color2, isHorizontalGradient) { + if (isHorizontalGradient === undefined) { + isHorizontalGradient = true; + } + + this.color = color; + this.color2 = color2; + this.horizontalGradient = isHorizontalGradient; + return this; + } + + set stroke(value) { + value = GetStyle(value, this.canvas, this.context); + this.setDirty(this._stroke != value); + this._stroke = value; + } + + get stroke() { + return this._stroke; + } + + set strokeThickness(value) { + this.setDirty(this._strokeThickness != value); + this._strokeThickness = value; + } + + get strokeThickness() { + return this._strokeThickness; + } + + setStroke(color, lineWidth) { + if (color != null) { + if (lineWidth === undefined) { + lineWidth = 2; + } + } + this.stroke = color; + this.strokeThickness = lineWidth; + return this; + } + + modifyPorperties(o) { + super.modifyPorperties(o); + + if (o.hasOwnProperty('color')) { + this.setColor( + o.color, + GetValue(o, 'color2', null), + GetValue(o, 'horizontalGradient', true) + ); + } + if (o.hasOwnProperty('stroke')) { + this.setStroke( + o.stroke, + GetValue(o, 'strokeThickness', 2) + ); + } + } + + renderContent() { + var padding = this.parent.padding; + var x = padding.left, + y = padding.top, + width = this.parent.width - padding.left - padding.right, + height = this.parent.height - padding.top - padding.bottom; + var context = this.context; + if (this.color != null) { + var fillStyle; + if (this.color2 != null) { + var grd; + if (this.horizontalGradient) { + grd = context.createLinearGradient(0, 0, width, 0); + } else { + grd = context.createLinearGradient(0, 0, 0, height); + } + grd.addColorStop(0, this.color); + grd.addColorStop(1, this.color2); + fillStyle = grd; + } else { + fillStyle = this.color; + } + + context.fillStyle = fillStyle; + context.fillRect(x, y, width, height); + } + + if ((this.stroke != null) && (this.strokeThickness > 0)) { + context.strokeStyle = this.stroke; + context.lineWidth = this.strokeThickness; + context.strokeRect(x, y, width, height); + } + } +} + +export default InnerBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/Contains.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/Contains.js new file mode 100644 index 000000000..fbeb21c62 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/Contains.js @@ -0,0 +1,28 @@ +import CanvasPositionToBobPosition from '../../methods/utils/transform/CanvasPositionToBobPosition.js'; + +const Rectangle = Phaser.Geom.Rectangle; + +var Contains = function (canvasX, canvasY) { + if ((this.width === 0) || (this.height === 0)) { + return false; + } + + var bobPosition = CanvasPositionToBobPosition(canvasX, canvasY, this, true); + return GetBounds(this).contains(bobPosition.x, bobPosition.y); +} + +var GetBounds = function (bob) { + if (globBounds === undefined) { + globBounds = new Rectangle(); + } + + var x = bob.drawTLX, + y = bob.drawTLY; + globBounds.setTo(x, y, (bob.drawTRX - x), (bob.drawBLY - y)); + + return globBounds; +} + +var globBounds; + +export default Contains; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/GetWorldPosition.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/GetWorldPosition.js new file mode 100644 index 000000000..4b628942e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/GetWorldPosition.js @@ -0,0 +1,7 @@ +import GetBobWorldPosition from '../../methods/utils/transform/GetBobWorldPosition.js'; + +var GetWorldPosition = function (offsetX, offsetY, out) { + return GetBobWorldPosition(this.parent, this, offsetX, offsetY, out); +} + +export default GetWorldPosition; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/Methods.js new file mode 100644 index 000000000..7e09ee0b7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/Methods.js @@ -0,0 +1,15 @@ +import RenderMethods from './RenderMethods.js'; +import Contains from './Contains.js'; +import GetWorldPosition from './GetWorldPosition.js'; + +var Methods = { + contains: Contains, + getWorldPosition: GetWorldPosition, +} + +Object.assign( + Methods, + RenderMethods +) + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/RenderBase.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/RenderBase.d.ts new file mode 100644 index 000000000..a3ba97be5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/RenderBase.d.ts @@ -0,0 +1,74 @@ +// import * as Phaser from 'phaser'; +import Base from '../Base'; + +export default RenderBase; + +declare namespace RenderBase { + type DrawCallbackType = ( + bob: RenderBase + ) => void; +} + +declare class RenderBase extends Base { + renderable: boolean; + removed: boolean; + toLocalPosition: boolean; + + setVisible(visible?: boolean): this; + visible: boolean; + + setAlpha(alpha: number): this; + alpha: number; + + setPosition(x: number, y: number): this; + setX(x: number): this; + setY(y: number): this; + x: number; + y: number; + + setAngle(degrees: number): this; + setRotation(radians: number): this; + angle: number; + rotation: number; + + setScale(scaleX: number, scaleY?: number): this; + setScaleX(scaleX: number): this; + setScaleY(scaleY: number): this; + scaleX: number; + scaleY: number; + + setWidth(width: number, keepAspectRatio?: boolean): this; + width: number; + setLeftSpace(value: number): this; + leftSpace: number; + setRightSpace(value: number): this; + rightSpace: number; + readonly outerWidth: number; + + setHeight(height: number, keepAspectRatio?: boolean): this; + height: number; + + setOrigin(x: number): this; + originX: number; + offsetX: number; + offsetY: number; + + readonly willRender: boolean; + + setDrawBelowCallback( + callback?: RenderBase.DrawCallbackType + ): this; + + setDrawAboveCallback( + callback?: RenderBase.DrawCallbackType + ): this; + + readonly drawTLX: number; + readonly drawTLY: number; + readonly drawBLX: number; + readonly drawBLY: number; + readonly drawTRX: number; + readonly drawTRY: number; + readonly drawBRX: number; + readonly drawBRY: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/RenderBase.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/RenderBase.js new file mode 100644 index 000000000..3067d6568 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/RenderBase.js @@ -0,0 +1,337 @@ +import Base from '../Base.js'; +import Methods from './Methods.js'; + +const DegToRad = Phaser.Math.DegToRad; +const RadToDeg = Phaser.Math.RadToDeg; +const GetValue = Phaser.Utils.Objects.GetValue; + +class RenderBase extends Base { + constructor(parent, type) { + super(parent, type); + + this.renderable = true; + this.toLocalPosition = true; + this.originX = 0; + this.offsetX = 0; // Override + this.offsetY = 0; // Override + } + + get visible() { + return this._visible; + } + + set visible(value) { + this.setDirty(this._visible != value); + this._visible = value; + } + + setVisible(visible) { + if (visible === undefined) { + visible = true; + } + + this.visible = visible; + return this; + } + + get alpha() { return this._alpha; } + + set alpha(value) { + this.setDirty(this._alpha != value); + this._alpha = value; + } + + setAlpha(alpha) { + this.alpha = alpha; + return this; + } + + get x() { return this._x; } + + set x(value) { + this.setDirty(this._x != value); + this._x = value; + } + + setX(x) { + this.x = x; + return this; + } + + get y() { return this._y; } + + set y(value) { + this.setDirty(this._y != value); + this._y = value; + } + + setY(y) { + this.y = y; + return this; + } + + setPosition(x, y) { + this.x = x; + this.y = y; + return this; + } + + setInitialPosition(x, y) { + this.x0 = x; + this.y0 = y; + return this; + } + + get rotation() { return this._rotation; } + + set rotation(value) { + this.setDirty(this._rotation != value); + this._rotation = value; + } + + setRotation(rotation) { + this.rotation = rotation; + return this; + } + + get angle() { return RadToDeg(this._rotation); } + + set angle(value) { + this.rotation = DegToRad(value); + } + + setAngle(angle) { + this.angle = angle; + return this; + } + + get scaleX() { return this._scaleX; } + + set scaleX(value) { + this.setDirty(this._scaleX !== value); + this._scaleX = value; + } + + setScaleX(scaleX) { + this.scaleX = scaleX; + return this; + } + + // Override + get width() { return 0; } + + // Override + set width(value) { } + + setWidth(width, keepAspectRatio) { + if (keepAspectRatio === undefined) { + keepAspectRatio = false; + } + this.width = width; + + if (keepAspectRatio) { + this.scaleY = this.scaleX; + } + return this; + } + + get leftSpace() { return this._leftSpace; } + + set leftSpace(value) { + this.setDirty(this._leftSpace !== value); + this._leftSpace = value; + } + + setLeftSpace(value) { + this.leftSpace = value; + return this; + } + + get rightSpace() { return this._rightSpace; } + + set rightSpace(value) { + this.setDirty(this._rightSpace !== value); + this._rightSpace = value; + } + + setRightSpace(value) { + this.rightSpace = value; + return this; + } + + get outerWidth() { + return this.width + this.leftSpace + this.rightSpace; + } + + get scaleY() { return this._scaleY; } + + set scaleY(value) { + this.setDirty(this._scaleY !== value); + this._scaleY = value; + } + + setScaleY(scaleY) { + this.scaleY = scaleY; + return this; + } + + // Override + get height() { return 0; } + + // Override + set height(value) { } + + setHeight(height, keepAspectRatio) { + if (keepAspectRatio === undefined) { + keepAspectRatio = false; + } + this.height = height; + + if (keepAspectRatio) { + this.scaleX = this.scaleY; + } + return this; + } + + setScale(scaleX, scaleY) { + if (scaleY === undefined) { + scaleY = scaleX; + } + + this.scaleX = scaleX; + this.scaleY = scaleY; + return this; + } + + setOrigin(x) { + this.originX = x; + return this; + } + + setAlign(align) { + this.align = align; + return this; + } + + modifyPorperties(o) { + if (!o) { + return this; + } + + if (o.hasOwnProperty('x')) { + this.setX(o.x); + } + if (o.hasOwnProperty('y')) { + this.setY(o.y); + } + + if (o.hasOwnProperty('rotation')) { + this.setRotation(o.rotation); + } else if (o.hasOwnProperty('angle')) { + this.setAngle(o.angle); + } + + if (o.hasOwnProperty('alpha')) { + this.setAlpha(o.alpha); + } + + // ScaleX, ScaleY + var width = GetValue(o, 'width', undefined); + var height = GetValue(o, 'height', undefined); + var scaleX = GetValue(o, 'scaleX', undefined); + var scaleY = GetValue(o, 'scaleY', undefined); + + if (width !== undefined) { + if ((height === undefined) && (scaleY === undefined)) { + this.setWidth(width, true); + } else { + this.setWidth(width); + } + } else if (scaleX !== undefined) { + this.setScaleX(scaleX); + } + if (height !== undefined) { + if ((width === undefined) && (scaleX === undefined)) { + this.setHeight(height, true); + } else { + this.setHeight(height); + } + } else if (scaleY !== undefined) { + this.setScaleY(scaleY); + } + + if (o.hasOwnProperty('leftSpace')) { + this.setLeftSpace(o.leftSpace); + } + if (o.hasOwnProperty('rightSpace')) { + this.setRightSpace(o.rightSpace); + } + + if (o.hasOwnProperty('align')) { + this.setAlign(o.align); + } + + return this; + } + + setDrawBelowCallback(callback) { + this.drawBelowCallback = callback; + return this; + } + + setDrawAboveCallback(callback) { + this.drawAboveCallback = callback; + return this; + } + + reset() { + this + .setVisible() + .setAlpha(1) + .setPosition(0, 0) + .setRotation(0) + .setScale(1, 1) + .setLeftSpace(0).setRightSpace(0) + .setOrigin(0) + .setAlign() + .setDrawBelowCallback() + .setDrawAboveCallback() + return this; + } + + // Override + get willRender() { + return this.visible && (this.alpha > 0); + } + + get drawX() { + return this.x + this.leftSpace + this.offsetX - (this.originX * this.width); + } + get drawY() { + return this.y + this.offsetY; + } + + // Override + get drawTLX() { return 0; } + get drawTLY() { return 0; } + get drawBLX() { return 0; } + get drawBLY() { return 0; } + get drawTRX() { return 0; } + get drawTRY() { return 0; } + get drawBRX() { return 0; } + get drawBRY() { return 0; } + + get drawCenterX() { + return (this.drawTRX + this.drawTLX) / 2; + } + get drawCenterY() { + return (this.drawBLY + this.drawTLY) / 2; + } +} + +Object.assign( + RenderBase.prototype, + Methods, +) + +export default RenderBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/RenderMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/RenderMethods.js new file mode 100644 index 000000000..d7d3c12dd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/renderbase/RenderMethods.js @@ -0,0 +1,43 @@ +export default { + // Override + renderContent() { + + }, + + // Override + render() { + if (!this.willRender) { + return this; + } + + var context = this.context; + context.save(); + context.globalAlpha = this.alpha; + + if (this.toLocalPosition) { + var x = this.drawX, y = this.drawY; + if (this.autoRound) { + x = Math.round(x); + y = Math.round(y); + } + + context.translate(x, y); + context.scale(this.scaleX, this.scaleY); + context.rotate(this.rotation); + } + + if (this.drawBelowCallback) { + this.drawBelowCallback(this); + } + + this.renderContent(); + + if (this.drawAboveCallback) { + this.drawAboveCallback(this); + } + + context.restore(); + + return this; + }, +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/space/Space.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/space/Space.d.ts new file mode 100644 index 000000000..9a083dc02 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/space/Space.d.ts @@ -0,0 +1,8 @@ +import RenderBase from '../renderbase/RenderBase'; + +export default class Space extends RenderBase { + readonly type: 'space'; + + setSpaceWidth(width: number): this; + spaceWidth: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/space/Space.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/space/Space.js new file mode 100644 index 000000000..0f0b8d10d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/space/Space.js @@ -0,0 +1,32 @@ +import RenderBase from '../renderbase/RenderBase.js'; +import { SpaceTypeName } from '../Types.js'; + +class Space extends RenderBase { + constructor( + parent, + width + ) { + super(parent, SpaceTypeName); + this.setSpaceWidth(width); + } + + get width() { + return this.spaceWidth * this.scaleX; + } + + set width(value) { + if (this.spaceWidth > 0) { + this.scaleX = value / this.spaceWidth; + } else { + this.scaleX = 1; + } + } + + setSpaceWidth(width) { + this.spaceWidth = width; + return this; + } + +} + +export default Space; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/utils/GetProperty.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/utils/GetProperty.js new file mode 100644 index 000000000..8c44d9639 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/bob/utils/GetProperty.js @@ -0,0 +1,9 @@ +var GetProperty = function (name, config, defaultConfig) { + if (config.hasOwnProperty(name)) { + return config[name]; + } else { + return defaultConfig[name]; + } +} + +export default GetProperty; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AddChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AddChild.js new file mode 100644 index 000000000..a1e814c8c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AddChild.js @@ -0,0 +1,33 @@ +// const RemoveItem = Phaser.Utils.Array.Remove; + +var AddChild = function (child, index) { + var areChildren = Array.isArray(child); + + // Remove existed child(s) + // RemoveItem(this.children, child); + + if ((index === undefined) || (index === this.children.length)) { + if (areChildren) { + this.children.push(...child); + } else { + this.children.push(child); + } + } else { + if (areChildren) { + this.children.splice(index, 0, ...child) + } else { + this.children.splice(index, 0, child); + } + } + + this.lastAppendedChildren.length = 0; + if (areChildren) { + this.lastAppendedChildren.push(...child); + } else { + this.lastAppendedChildren.push(child); + } + + return this; +} + +export default AddChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendCommand.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendCommand.js new file mode 100644 index 000000000..ff062cde9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendCommand.js @@ -0,0 +1,8 @@ +var AppendCommand = function (name, callback, param, scope) { + var child = this.createCommandChild(name, callback, param, scope); + this.addChild(child); + + return this; +} + +export default AppendCommand; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendDrawer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendDrawer.js new file mode 100644 index 000000000..257004a8f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendDrawer.js @@ -0,0 +1,8 @@ +var AppendDrawer = function (renderCallback, width, height) { + var child = this.createDrawerChild(renderCallback, width, height); + this.addChild(child); + + return this; +}; + +export default AppendDrawer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendImage.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendImage.js new file mode 100644 index 000000000..485f52739 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendImage.js @@ -0,0 +1,8 @@ +var AppendImage = function (key, frame, properties) { + var child = this.createImageChild(key, frame, properties); + this.addChild(child); + + return this; +}; + +export default AppendImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendSpace.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendSpace.js new file mode 100644 index 000000000..73bbb047f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendSpace.js @@ -0,0 +1,8 @@ +var AppendSpace = function (width) { + var child = this.createSpaceChild(width); + this.addChild(child); + + return this; +}; + +export default AppendSpace; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendText.js new file mode 100644 index 000000000..c68aaee86 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/AppendText.js @@ -0,0 +1,7 @@ +var AppendText = function (text, style) { + var children = this.createCharChildren(text, style); + this.addChild(children); + return this; +}; + +export default AppendText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/BackgroundMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/BackgroundMethods.js new file mode 100644 index 000000000..b8a6d0c43 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/BackgroundMethods.js @@ -0,0 +1,16 @@ +export default { + setBackgroundColor(color, color2, isHorizontalGradient) { + this.background.setColor(color, color2, isHorizontalGradient); + return this; + }, + + setBackgroundStroke(color, lineWidth) { + this.background.setStroke(color, lineWidth); + return this; + }, + + setBackgroundCornerRadius(radius, iteration) { + this.background.setCornerRadius(radius, iteration); + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ClearContent.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ClearContent.js new file mode 100644 index 000000000..25a77066f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ClearContent.js @@ -0,0 +1,7 @@ +var ClearContent = function() { + this.setText(); + return this; +} + +export default ClearContent; + diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateCharChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateCharChild.js new file mode 100644 index 000000000..405dcbde7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateCharChild.js @@ -0,0 +1,27 @@ +import CharData from '../bob/char/CharData.js'; +import { CharTypeName } from '../bob/Types.js'; + +var CreateCharChild = function (text, style) { + if (style) { + this.textStyle.modify(style); + } + + var child = this.poolManager.allocate(CharTypeName); + if (child === null) { + child = new CharData( + this, // parent + text, // text + this.textStyle, // style + ); + } else { + child + .setParent(this) + .setActive() + .modifyStyle(this.textStyle) + .setText(text); + } + + return child; +} + +export default CreateCharChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateCharChildren.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateCharChildren.js new file mode 100644 index 000000000..450568b55 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateCharChildren.js @@ -0,0 +1,34 @@ +import CharData from '../bob/char/CharData.js'; +import { CharTypeName } from '../bob/Types.js'; + +var CreateCharChildren = function (text, style) { + if (style) { + this.textStyle.modify(style); + } + + var children = []; + for (var i = 0, cnt = text.length; i < cnt; i++) { + var char = text.charAt(i); + var child = this.poolManager.allocate(CharTypeName); + if (child === null) { + child = new CharData( + this, // parent + char, // text + this.textStyle, // style + ); + } else { + child + .setParent(this) + .setActive() + .modifyStyle(this.textStyle) + .setText(char); + } + // child.modifyPorperties(properties); // Warning: Will modify text-style twice + + children.push(child); + } + + return children; +} + +export default CreateCharChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateCommandChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateCommandChild.js new file mode 100644 index 000000000..525283089 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateCommandChild.js @@ -0,0 +1,26 @@ +import { CmdTypeName } from '../bob/Types.js'; +import Command from '../bob/command/Command.js'; + +var CreateCommandChild = function (name, callback, param, scope) { + var child = this.poolManager.allocate(CmdTypeName); + + if (child === null) { + child = new Command( + this, // parent + name, + callback, param, scope, + ); + } else { + child + .setParent(this) + .setActive() + .setName(name) + .setCallback(callback, scope) + .setParameter(param) + + } + + return child; +} + +export default CreateCommandChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateDrawerChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateDrawerChild.js new file mode 100644 index 000000000..a019a87ce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateDrawerChild.js @@ -0,0 +1,23 @@ +import Drawer from '../bob/drawer/Drawer.js'; +import { DrawerTypeName } from '../bob/Types.js'; + +var CreateDrawerChild = function (renderCallback, width, height) { + var child = this.poolManager.allocate(DrawerTypeName); + + if (child === null) { + child = new Drawer( + this, // parent + renderCallback, + width, height + ); + } else { + child + .setParent(this) + .setActive() + .setRenderCallback(renderCallback) + .setDrawerSize(width, height) + } + + return child; +} +export default CreateDrawerChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateImageChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateImageChild.js new file mode 100644 index 000000000..02cfbc973 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateImageChild.js @@ -0,0 +1,24 @@ +import ImageData from '../bob/image/ImageData.js'; +import { ImageTypeName } from '../bob/Types.js'; + +var CreateImageChild = function(key, frame, properties) { + var child = this.poolManager.allocate(ImageTypeName); + + if (child === null) { + child = new ImageData( + this, // parent + key, + frame + ); + } else { + child + .setParent(this) + .setActive() + .setTexture(key, frame) + } + child.modifyPorperties(properties); + + return child; +} + +export default CreateImageChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateSpaceChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateSpaceChild.js new file mode 100644 index 000000000..d0cd30ad6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/CreateSpaceChild.js @@ -0,0 +1,21 @@ +import Space from '../bob/space/Space.js'; +import { SpaceTypeName } from '../bob/Types.js'; + +var CreateSpaceChild = function (width) { + var child = this.poolManager.allocate(SpaceTypeName); + + if (child === null) { + child = new Space( + this, // parent + width + ); + } else { + child + .setParent(this) + .setActive() + .setSpaceWidth(width) + } + return child; +} + +export default CreateSpaceChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ForEachCharChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ForEachCharChild.js new file mode 100644 index 000000000..767c0f8b8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ForEachCharChild.js @@ -0,0 +1,35 @@ +import { IsChar } from '../bob/Types.js'; + +var ForEachCharChild = function (callback, scope, activeOnly) { + if (activeOnly === undefined) { + activeOnly = true; + } + + var children = this.children; + var charIndex = 0; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + + if (activeOnly && !child.active) { + continue; + } + + if (IsChar(child) && !child.removed) { + var isBreak; + if (scope) { + isBreak = callback.call(this, child, charIndex, children); + } else { + isBreak = callback(child, charIndex, children); + } + charIndex++; + + if (isBreak) { + break; + } + } + } + + return this; +} + +export default ForEachCharChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ForEachChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ForEachChild.js new file mode 100644 index 000000000..281025f11 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ForEachChild.js @@ -0,0 +1,31 @@ +var ForEachChild = function (callback, scope, activeOnly) { + if (activeOnly === undefined) { + activeOnly = true; + } + + var children = this.children; + var childIndex = 0; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + + if (activeOnly && !child.active) { + continue; + } + + var isBreak; + if (scope) { + isBreak = callback.call(this, child, childIndex, children); + } else { + isBreak = callback(child, childIndex, children); + } + childIndex++; + + if (isBreak) { + break; + } + } + + return this; +} + +export default ForEachChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ForEachRenderableChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ForEachRenderableChild.js new file mode 100644 index 000000000..d29072ac5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ForEachRenderableChild.js @@ -0,0 +1,33 @@ +var ForEachRenderableChild = function (callback, scope, activeOnly) { + if (activeOnly === undefined) { + activeOnly = true; + } + + var children = this.children; + var childIndex = 0; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + + if (activeOnly && !child.active) { + continue; + } + + if (child.renderable && !child.removed) { + var isBreak; + if (scope) { + isBreak = callback.call(this, child, childIndex, children); + } else { + isBreak = callback(child, childIndex, children); + } + childIndex++; + + if (isBreak) { + break; + } + } + } + + return this; +} + +export default ForEachRenderableChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetActiveChildren.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetActiveChildren.js new file mode 100644 index 000000000..ca55cd390 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetActiveChildren.js @@ -0,0 +1,7 @@ +const GetAll = Phaser.Utils.Array.GetAll; + +var GetActiveChildren = function () { + return GetAll(this.children, 'active', true); +} + +export default GetActiveChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharChild.js new file mode 100644 index 000000000..d0ef25b68 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharChild.js @@ -0,0 +1,27 @@ +import { IsChar } from '../bob/Types.js'; + +var GetCharChild = function (charIndex, activeOnly) { + if (activeOnly === undefined) { + activeOnly = true; + } + + var children = this.children; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if (activeOnly && !child.active) { + continue; + } + + if (IsChar(child) && !child.removed) { + if (charIndex === 0) { + return child; + } else { + charIndex--; + } + } + } + + return undefined; +} + +export default GetCharChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharChildIndex.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharChildIndex.js new file mode 100644 index 000000000..d58bb48fe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharChildIndex.js @@ -0,0 +1,27 @@ +import { IsChar } from '../bob/Types.js'; + +var GetCharChildIndex = function (charIndex, activeOnly) { + if (activeOnly === undefined) { + activeOnly = true; + } + + var children = this.children; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if (activeOnly && !child.active) { + continue; + } + + if (IsChar(child) && !child.removed) { + if (charIndex === 0) { + return i; + } else { + charIndex--; + } + } + } + + return undefined; +} + +export default GetCharChildIndex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharChildren.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharChildren.js new file mode 100644 index 000000000..6c88a9159 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharChildren.js @@ -0,0 +1,13 @@ +var GetCharChildren = function (activeOnly, out) { + if (out === undefined) { + out = []; + } + + this.forEachCharChild(function (child) { + out.push(child); + }, undefined, activeOnly); + + return out; +} + +export default GetCharChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharIndex.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharIndex.js new file mode 100644 index 000000000..52ebc9ca9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharIndex.js @@ -0,0 +1,34 @@ +import { IsChar } from '../bob/Types.js'; + +var GetCharIndex = function (childIndex, activeOnly) { + if (typeof (childIndex) !== 'number') { + childIndex = this.children.indexOf(childIndex); + if (childIndex < 0) { + return null; + } + } + + if (activeOnly === undefined) { + activeOnly = true; + } + + var children = this.children; + if (childIndex >= children.length) { + childIndex = children.length; + } + var charIndex = 0; + for (var i = 0; i < childIndex; i++) { + var child = children[i]; + if (activeOnly && !child.active) { + continue; + } + + if (IsChar(child) && !child.removed) { + charIndex++; + } + } + + return charIndex; +} + +export default GetCharIndex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharWorldPosition.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharWorldPosition.js new file mode 100644 index 000000000..030b5b634 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetCharWorldPosition.js @@ -0,0 +1,11 @@ +import GetBobWorldPosition from './utils/transform/GetBobWorldPosition.js'; + +var GetCharWorldPosition = function (child, offsetX, offsetY, out) { + if (typeof (child) === 'number') { + child = this.getCharChild(child, true); + } + + return GetBobWorldPosition(this, child, offsetX, offsetY, out); +} + +export default GetCharWorldPosition; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetChildren.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetChildren.js new file mode 100644 index 000000000..2c47837f1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetChildren.js @@ -0,0 +1,5 @@ +var GetChildren = function () { + return this.children; +} + +export default GetChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetLastAppendedChildren.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetLastAppendedChildren.js new file mode 100644 index 000000000..d09335ca2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetLastAppendedChildren.js @@ -0,0 +1,5 @@ +var GetLastAppendedChildren = function () { + return this.lastAppendedChildren; +} + +export default GetLastAppendedChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetNearestChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetNearestChild.js new file mode 100644 index 000000000..47e31e88f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetNearestChild.js @@ -0,0 +1,21 @@ +import GetBobCenterPosition from './utils/transform/GetBobCenterPosition'; + +const GetDistance = Phaser.Math.Distance.BetweenPointsSquared; + +var GetNearestChild = function (canvasX, canvasY) { + var pointA = { x: canvasX, y: canvasY }; + + var minDistance = Infinity; + var nearestChild = null; + this.forEachRenderableChild(function (child) { + var distance = GetDistance(pointA, GetBobCenterPosition(child, true)); + if (minDistance > distance) { + minDistance = distance; + nearestChild = child; + } + }) + + return nearestChild; +} + +export default GetNearestChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetPadding.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetPadding.js new file mode 100644 index 000000000..cbc3cb2d7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetPadding.js @@ -0,0 +1,7 @@ +import { GetPadding as GetPaddingBase } from '../../../../utils/padding/PaddingMethods.js'; + +var GetPadding = function (key) { + return GetPaddingBase(this.padding, key); +} + +export default GetPadding; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetText.js new file mode 100644 index 000000000..4d0bea4f7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/GetText.js @@ -0,0 +1,9 @@ +var GetText = function (activeOnly) { + var text = '' + this.forEachCharChild(function (child) { + text += child.text; + }, undefined, activeOnly); + return text; +} + +export default GetText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/InnerBoundsMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/InnerBoundsMethods.js new file mode 100644 index 000000000..70b3f0e0d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/InnerBoundsMethods.js @@ -0,0 +1,11 @@ +export default { + setInnerBoundsColor(color, color2, isHorizontalGradient) { + this.innerBounds.setColor(color, color2, isHorizontalGradient); + return this; + }, + + setInnerBoundsStroke(color, lineWidth) { + this.innerBounds.setStroke(color, lineWidth); + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/InsertText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/InsertText.js new file mode 100644 index 000000000..7ced5840b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/InsertText.js @@ -0,0 +1,9 @@ +var InsertText = function (index, text, style) { + var children = this.createCharChildren(text, style); + index = this.getCharChildIndex(index, true); + this.addChild(children, index); + + return this; +}; + +export default InsertText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/Methods.js new file mode 100644 index 000000000..6030fd918 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/Methods.js @@ -0,0 +1,124 @@ +import SetFixedSize from './SetFixedSize.js'; +import SetPadding from './SetPadding.js'; +import GetPadding from './GetPadding.js'; +import ModifyTextStyle from './ModifyTextStyle.js'; +import ModifyDefaultTextStyle from './ModifyDefaultTextStyle.js'; +import ResetTextStyle from './ResetTextStyle.js'; +import SetTestString from './SetTestString.js'; + +import RemoveChild from './RemoveChild.js'; +import RemoveChildren from './RemoveChildren.js'; +import PopChild from './PopChild.js'; +import ClearContent from './ClearContent.js'; +import AddChild from './AddChild.js'; +import CreateCharChild from './CreateCharChild.js'; +import CreateCharChildren from './CreateCharChildren.js'; +import SetText from './SetText.js'; +import AppendText from './AppendText.js'; +import InsertText from './InsertText.js'; +import RemoveText from './RemoveText.js'; +import GetText from './GetText.js'; +import CreateImageChild from './CreateImageChild.js'; +import AppendImage from './AppendImage.js'; +import CreateDrawerChild from './CreateDrawerChild.js'; +import AppendDrawer from './AppendDrawer.js'; +import CreateSpaceChild from './CreateSpaceChild.js'; +import AppendSpace from './AppendSpace.js'; +import CreateCommandChild from './CreateCommandChild.js'; +import AppendCommand from './AppendCommand.js'; +import SetWrapConfig from './SetWrapConfig.js'; +import RunWordWrap from './RunWordWrap.js'; +import RunVerticalWrap from './RunVerticalWrap.js'; +import RunWrap from './RunWrap.js'; +import SetAlignMethods from './SetAlignMethods.js'; +import RenderContent from './RenderContent.js'; + +import ForEachChild from './ForEachChild.js'; +import ForEachRenderableChild from './ForEachRenderableChild.js'; +import ForEachCharChild from './ForEachCharChild.js'; +import GetChildren from './GetChildren.js'; +import GetActiveChildren from './GetActiveChildren.js'; +import GetCharChildren from './GetCharChildren.js'; +import GetLastAppendedChildren from './GetLastAppendedChildren.js'; +import GetNearestChild from './GetNearestChild.js'; +import GetCharWorldPosition from './GetCharWorldPosition.js'; +import SetToMinSize from './SetToMinSize.js'; +import GetCharChildIndex from './GetCharChildIndex.js'; +import GetCharChild from './GetCharChild.js'; +import GetCharIndex from './GetCharIndex.js'; + +import SetChildrenInteractiveEnable from './input/SetChildrenInteractiveEnable.js'; +import SetInteractive from './input/SetInteractive.js'; + +import MoveChildMethods from './MoveChildMethods.js'; +import BackgroundMethods from './BackgroundMethods.js'; +import InnerBoundsMethods from './InnerBoundsMethods.js'; + +var Methods = { + setFixedSize: SetFixedSize, + setPadding: SetPadding, + getPadding: GetPadding, + modifyTextStyle: ModifyTextStyle, + modifyDefaultTextStyle: ModifyDefaultTextStyle, + resetTextStyle: ResetTextStyle, + setTestString: SetTestString, + + removeChild: RemoveChild, + removeChildren: RemoveChildren, + popChild: PopChild, + clearContent: ClearContent, + addChild: AddChild, + createCharChild: CreateCharChild, + createCharChildren: CreateCharChildren, + setText: SetText, + appendText: AppendText, + insertText: InsertText, + removeText: RemoveText, + getText: GetText, + createImageChild: CreateImageChild, + appendImage: AppendImage, + createDrawerChild: CreateDrawerChild, + appendDrawer: AppendDrawer, + createSpaceChild: CreateSpaceChild, + appendSpace: AppendSpace, + createCommandChild: CreateCommandChild, + appendCommand: AppendCommand, + + setWrapConfig: SetWrapConfig, + runWordWrap: RunWordWrap, + runVerticalWrap: RunVerticalWrap, + runWrap: RunWrap, + renderContent: RenderContent, + + forEachChild: ForEachChild, + forEachRenderableChild: ForEachRenderableChild, + forEachCharChild: ForEachCharChild, + getChildren: GetChildren, + getActiveChildren: GetActiveChildren, + getCharChildren: GetCharChildren, + getLastAppendedChildren: GetLastAppendedChildren, + getNearestChild: GetNearestChild, + getCharWorldPosition: GetCharWorldPosition, + + setToMinSize: SetToMinSize, + + getCharChildIndex: GetCharChildIndex, + getCharChild: GetCharChild, + getCharIndex: GetCharIndex, + + + setChildrenInteractiveEnable: SetChildrenInteractiveEnable, + setInteractive: SetInteractive, +} + +Object.assign( + Methods, + + MoveChildMethods, + BackgroundMethods, + InnerBoundsMethods, + SetAlignMethods, + +) + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ModifyDefaultTextStyle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ModifyDefaultTextStyle.js new file mode 100644 index 000000000..bafb464aa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ModifyDefaultTextStyle.js @@ -0,0 +1,6 @@ +var ModifyDefaultTextStyle = function (style) { + this.defaultTextStyle.modify(style); + return this; +}; + +export default ModifyDefaultTextStyle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ModifyTextStyle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ModifyTextStyle.js new file mode 100644 index 000000000..3492fefed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ModifyTextStyle.js @@ -0,0 +1,6 @@ +var ModifyTextStyle = function (style) { + this.textStyle.modify(style); + return this; +}; + +export default ModifyTextStyle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/MoveChildMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/MoveChildMethods.js new file mode 100644 index 000000000..68b98754f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/MoveChildMethods.js @@ -0,0 +1,38 @@ +const BringToTop = Phaser.Utils.Array.BringToTop; +const SendToBack = Phaser.Utils.Array.SendToBack; +const MoveUp = Phaser.Utils.Array.MoveUp; +const MoveDown = Phaser.Utils.Array.MoveDown; +const MoveAbove = Phaser.Utils.Array.MoveAbove; +const MoveBelow = Phaser.Utils.Array.MoveBelow; + +export default { + moveChildToFist(child) { + SendToBack(this.children, child); + return this; + }, + + moveChildToLast(child) { + BringToTop(this.children, child); + return this; + }, + movechildUp(child) { + MoveUp(this.children, child); + return this; + }, + + movechildDown(child) { + MoveDown(this.children, child); + return this; + }, + + movechildAbove(child, baseChild) { + MoveAbove(this.children, child, baseChild); + return this; + }, + + movechildBelow(child, baseChild) { + MoveBelow(this.children, child, baseChild); + return this; + }, + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/PopChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/PopChild.js new file mode 100644 index 000000000..3caf55423 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/PopChild.js @@ -0,0 +1,11 @@ +const RemoveItem = Phaser.Utils.Array.Remove; + +var PopChild = function (child) { + RemoveItem(this.children, child); + this.lastAppendedChildren.length = 0; + this.lastOverChild = null; + this.dirty = true; + return this; +} + +export default PopChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RemoveChild.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RemoveChild.js new file mode 100644 index 000000000..b5354ba3a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RemoveChild.js @@ -0,0 +1,12 @@ +const RemoveItem = Phaser.Utils.Array.Remove; + +var RemoveChild = function (child) { + this.poolManager.free(child); + RemoveItem(this.children, child); + this.lastAppendedChildren.length = 0; + this.lastOverChild = null; + this.dirty = true; + return this; +} + +export default RemoveChild; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RemoveChildren.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RemoveChildren.js new file mode 100644 index 000000000..9b50166ec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RemoveChildren.js @@ -0,0 +1,10 @@ +var RemoveChildren = function () { + this.poolManager.freeMultiple(this.children); + this.children.length = 0; + this.lastAppendedChildren.length = 0; + this.lastOverChild = null; + this.dirty = true; + return this; +} + +export default RemoveChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RemoveText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RemoveText.js new file mode 100644 index 000000000..5f6f41a01 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RemoveText.js @@ -0,0 +1,16 @@ +var RemoveText = function (index, length) { + if (length === undefined) { + length = 1; + } + + for (var i = 0; i < length; i++) { + var childIndex = this.getCharChildIndex(index, true); + if (childIndex === undefined) { + break; + } + this.removeChild(this.children[childIndex]); + } + return this; +} + +export default RemoveText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RenderContent.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RenderContent.js new file mode 100644 index 000000000..66df4dbb7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RenderContent.js @@ -0,0 +1,23 @@ +var RenderContent = function () { + this.clear(); + + this.setCanvasSize(this.width, this.height); + + if (this.background.active) { + this.background.render(); + } + + var child; + for (var i = 0, cnt = this.children.length; i < cnt; i++) { + child = this.children[i]; + if (child.active) { + child.render(); + } + } + + if (this.innerBounds.active) { + this.innerBounds.render(); + } +} + +export default RenderContent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ResetTextStyle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ResetTextStyle.js new file mode 100644 index 000000000..a60e96476 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/ResetTextStyle.js @@ -0,0 +1,6 @@ +var ResetTextStyle = function () { + this.textStyle.copyFrom(this.defaultTextStyle); + return this; +}; + +export default ResetTextStyle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RunVerticalWrap.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RunVerticalWrap.js new file mode 100644 index 000000000..5aa108cb7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RunVerticalWrap.js @@ -0,0 +1,13 @@ +import RunVerticalWrapBase from './wrap/runverticalwrap/RunVerticalWrap.js'; + +const Merge = Phaser.Utils.Objects.Merge; + +var RunVerticalWrap = function (config) { + if (config === undefined) { + config = {}; + } + + return RunVerticalWrapBase.call(this, Merge(config, this.wrapConfig)); +}; + +export default RunVerticalWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RunWordWrap.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RunWordWrap.js new file mode 100644 index 000000000..0c843412d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RunWordWrap.js @@ -0,0 +1,13 @@ +import RunWordWrapBase from './wrap/runwordwrap/RunWordWrap.js'; + +const Merge = Phaser.Utils.Objects.Merge; + +var RunWordWrap = function (config) { + if (config === undefined) { + config = {}; + } + + return RunWordWrapBase.call(this, Merge(config, this.wrapConfig)); +}; + +export default RunWordWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RunWrap.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RunWrap.js new file mode 100644 index 000000000..c7316e84d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/RunWrap.js @@ -0,0 +1,15 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +var RunWrap = function (config) { + var wrapCallback = GetValue(this.wrapConfig, 'callback'); + if (!wrapCallback) { + wrapCallback = GetValue(config, 'callback', this.runWordWrap); + } + if (typeof (wrapCallback) === 'string') { + wrapCallback = this[wrapCallback]; + } + + return wrapCallback.call(this, config); +} + +export default RunWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetAlignMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetAlignMethods.js new file mode 100644 index 000000000..5caacc4d9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetAlignMethods.js @@ -0,0 +1,11 @@ +export default { + setVAlign(align) { + this.wrapConfig.vAlign = align; + return this; + }, + + setHAlign(align) { + this.wrapConfig.hAlign = align; + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetFixedSize.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetFixedSize.js new file mode 100644 index 000000000..bd5f2f84a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetFixedSize.js @@ -0,0 +1,26 @@ +var SetFixedSize = function (width, height) { + if (width === undefined) { + width = 0; + } + if (height === undefined) { + height = 0; + } + + var dirty = (this.fixedWidth !== width) || (this.fixedHeight !== height); + if (!dirty) { + return this; + } + + this.fixedWidth = width; + this.fixedHeight = height; + this.dirty = true; + + this.setCanvasSize( + (width > 0) ? width : this.width, + (height > 0) ? height : this.height + ); + + return this; +} + +export default SetFixedSize; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetPadding.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetPadding.js new file mode 100644 index 000000000..696116466 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetPadding.js @@ -0,0 +1,21 @@ +import { SetPadding as SetPaddingBase } from '../../../../utils/padding/PaddingMethods.js'; + +var SetPadding = function (key, value) { + var padding = this.padding; + var paddingLeft = padding.left, + paddingRight = padding.right, + paddingTop = padding.top, + paddingBottom = padding.bottom; + + SetPaddingBase(padding, key, value); + + this.dirty = this.dirty || + (paddingLeft != padding.left) || + (paddingRight != padding.right) || + (paddingTop != padding.top) || + (paddingBottom != padding.bottom) + ; + return this; +}; + +export default SetPadding; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetTestString.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetTestString.js new file mode 100644 index 000000000..10727c783 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetTestString.js @@ -0,0 +1,6 @@ +var SetTestString = function (testString) { + this.testString = testString; + return this; +} + +export default SetTestString; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetText.js new file mode 100644 index 000000000..2908db8a7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetText.js @@ -0,0 +1,15 @@ +import AppendText from './AppendText.js'; + +var SetText = function (text, style) { + if (text === undefined) { + text = ''; + } + + this.removeChildren(); + AppendText.call(this, text, style); // this.appendText might be override + + this.dirty = true; + return this; +}; + +export default SetText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetToMinSize.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetToMinSize.js new file mode 100644 index 000000000..d273add7b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetToMinSize.js @@ -0,0 +1,28 @@ +var SetToMinSize = function () { + var children = this.children; + var maxX = 0, + maxY = 0; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if (!child.renderable || !child.active || !child.visible) { + continue; + } + + var x0 = (child.x0 !== undefined) ? child.x0 : child.x; + var y0 = (child.y0 !== undefined) ? child.y0 : child.y; + maxX = Math.max(maxX, x0); + maxY = Math.max(maxY, y0); + } + + var width = maxX + this.padding.left + this.padding.right + this.wrapPadding.left + this.wrapPadding.right; + var height = maxY + this.padding.top + this.padding.bottom + this.wrapPadding.top + this.wrapPadding.bottom; + + // Ignore fixedWidth, and fixedHeight + if ((this.width !== width) || (this.height !== height)) { + this.dirty = true; + this.setCanvasSize(width, height); + } + return this; +} + +export default SetToMinSize; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetWrapConfig.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetWrapConfig.js new file mode 100644 index 000000000..a0ebc21e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/SetWrapConfig.js @@ -0,0 +1,14 @@ +import DeepClone from '../../../../utils/object/DeepClone.js'; + +var SetWrapConfig = function (config) { + if (config === undefined) { + config = {}; + } else if (typeof (config) === 'object') { + config = DeepClone(config); + } + + this.wrapConfig = config; + return this; +} + +export default SetWrapConfig; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/GetFirstChildContains.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/GetFirstChildContains.js new file mode 100644 index 000000000..8ab0ca739 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/GetFirstChildContains.js @@ -0,0 +1,15 @@ +var GetFirstChildContains = function (children, x, y) { + var children = children; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if (!child.active || !child.renderable) { + continue; + } + if (child.contains(x, y)) { + return child; + } + } + return null; +} + +export default GetFirstChildContains; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/SetChildrenInteractive.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/SetChildrenInteractive.js new file mode 100644 index 000000000..883d8f938 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/SetChildrenInteractive.js @@ -0,0 +1,73 @@ +import GetFirstChildContains from './GetFirstChildContains.js'; + +var SetChildrenInteractive = function () { + this + .on('pointerdown', OnPointerDown, this) + + .on('pointerdown', OnPointerUp, this) + + .on('pointermove', OnPointOverOut, this) + .on('pointerover', OnPointOverOut, this) + .on('pointerout', function (pointer, event) { + OnPointOverOut.call(this, pointer, null, null, event); + }, this) + + return this; +} + +var OnPointerDown = function (pointer, localX, localY, event) { + if (!this.childrenInteractiveEnable) { + return; + } + + var child = GetFirstChildContains(this.children, localX, localY); + if (!child) { + return; + } + + this.emit('child.pointerdown', child, pointer, localX, localY, event); +} + +var OnPointerUp = function (pointer, localX, localY, event) { + if (!this.childrenInteractiveEnable) { + return; + } + + var child = GetFirstChildContains(this.children, localX, localY); + if (!child) { + return; + } + + this.emit('child.pointerup', child, pointer, localX, localY, event); +} + +var OnPointOverOut = function (pointer, localX, localY, event) { + if (!this.childrenInteractiveEnable) { + return; + } + + if (localX === null) { // Case of pointerout + if (this.lastOverChild !== null) { + this.emit('child.pointerout', this.lastOverChild, pointer, localX, localY, event); + this.lastOverChild = null; + } + return; + } + + var child = GetFirstChildContains(this.children, localX, localY); + if (child === this.lastOverChild) { + return; + } + + if (this.lastOverChild !== null) { + this.emit('child.pointerout', this.lastOverChild, pointer, localX, localY, event); + } + + if (child !== null) { + this.emit('child.pointerover', child, pointer, localX, localY, event); + } + + this.lastOverChild = child; +} + +export default SetChildrenInteractive; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/SetChildrenInteractiveEnable.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/SetChildrenInteractiveEnable.js new file mode 100644 index 000000000..592b2a166 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/SetChildrenInteractiveEnable.js @@ -0,0 +1,15 @@ +var SetChildrenInteractiveEnable = function (enable) { + if (enable === undefined) { + enable = true; + } + + if (this.childrenInteractiveEnable !== enable) { + this.lastOverChild = null; + } + + this.childrenInteractiveEnable = enable; + + return this; +} + +export default SetChildrenInteractiveEnable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/SetInteractive.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/SetInteractive.js new file mode 100644 index 000000000..c248732c7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/input/SetInteractive.js @@ -0,0 +1,17 @@ +import SetChildrenInteractive from './SetChildrenInteractive.js'; + +const GameObject = Phaser.GameObjects.GameObject; + +var SetInteractive = function (hitArea, hitAreaCallback, dropZone) { + var isInteractived = !!this.input; + + GameObject.prototype.setInteractive.call(this, hitArea, hitAreaCallback, dropZone); + + if (!isInteractived) { + SetChildrenInteractive.call(this); + } + + return this; +} + +export default SetInteractive; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/BobPositionToCanvasPosition.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/BobPositionToCanvasPosition.js new file mode 100644 index 000000000..c4766c5dc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/BobPositionToCanvasPosition.js @@ -0,0 +1,28 @@ +const RotateAround = Phaser.Math.RotateAround; + +var BobPositionToCanvasPosition = function (bob, bobX, bobY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + if (globPoint === undefined) { + globPoint = {}; + } + out = globPoint; + } + + out.x = bobX; + out.y = bobY; + + if (bob.rotation !== 0) { + RotateAround(out, 0, 0, bob.rotation); + } + + out.x = (out.x * bob.scaleX) + bob.drawX; + out.y = (out.y * bob.scaleY) + bob.drawY; + + return out; +} + +var globPoint; + +export default BobPositionToCanvasPosition \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/BobPositionToWorldPosition.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/BobPositionToWorldPosition.js new file mode 100644 index 000000000..0899ae984 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/BobPositionToWorldPosition.js @@ -0,0 +1,10 @@ +import BobPositionToCanvasPosition from './BobPositionToCanvasPosition.js'; +import GameObjectLocalXYToWorldXY from '../../../../../../utils/position/GameObjectLocalXYToWorldXY.js'; + +var BobPositionToWorldPosition = function (dynamicText, bob, bobX, bobY, out) { + var localXY = BobPositionToCanvasPosition(bob, bobX, bobY, true); + var worldXY = GameObjectLocalXYToWorldXY(dynamicText, localXY.x, localXY.y, out); + return worldXY; +} + +export default BobPositionToWorldPosition; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/CanvasPositionToBobPosition.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/CanvasPositionToBobPosition.js new file mode 100644 index 000000000..0592e66d8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/CanvasPositionToBobPosition.js @@ -0,0 +1,24 @@ +const RotateAround = Phaser.Math.RotateAround; + +var CanvasPositionToBobPosition = function (canvasX, canvasY, bob, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + if (globPoint === undefined) { + globPoint = {}; + } + out = globPoint; + } + + out.x = (canvasX - bob.drawX) / bob.scaleX; + out.y = (canvasY - bob.drawY) / bob.scaleY; + + if (bob.rotation !== 0) { + RotateAround(out, 0, 0, -bob.rotation); + } + return out; +} + +var globPoint; + +export default CanvasPositionToBobPosition; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/GetBobCenterPosition.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/GetBobCenterPosition.js new file mode 100644 index 000000000..331d443cf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/GetBobCenterPosition.js @@ -0,0 +1,14 @@ +import BobPositionToCanvasPosition from './BobPositionToCanvasPosition.js'; + +var GetBobCenterPosition = function (bob, offsetX, offsetY, out) { + if (typeof (offsetX) !== 'number') { + out = offsetX; + offsetX = 0; + offsetY = 0; + } + var bobX = bob.drawCenterX + offsetX; + var bobY = bob.drawCenterY + offsetY; + return BobPositionToCanvasPosition(bob, bobX, bobY, out); +} + +export default GetBobCenterPosition; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/GetBobWorldPosition.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/GetBobWorldPosition.js new file mode 100644 index 000000000..0e4610bbd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/utils/transform/GetBobWorldPosition.js @@ -0,0 +1,14 @@ +import BobPositionToWorldPosition from './BobPositionToWorldPosition.js'; + +var GetBobWorldPosition = function (dynamicText, bob, offsetX, offsetY, out) { + if (typeof (offsetX) !== 'number') { + out = offsetX; + offsetX = 0; + offsetY = 0; + } + var bobX = bob.drawCenterX + offsetX; + var bobY = bob.drawCenterY + offsetY; + return BobPositionToWorldPosition(dynamicText, bob, bobX, bobY, out); +} + +export default GetBobWorldPosition; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/GetChildrenAlign.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/GetChildrenAlign.js new file mode 100644 index 000000000..5c68c2898 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/GetChildrenAlign.js @@ -0,0 +1,12 @@ +var GetChildrenAlign = function (children) { + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if (child.align !== undefined) { + return child.align; + } + } + + return undefined; +} + +export default GetChildrenAlign; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/OffsetChildren.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/OffsetChildren.js new file mode 100644 index 000000000..cdc5fb822 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/OffsetChildren.js @@ -0,0 +1,17 @@ +var OffsetChildren = function (children, offsetX, offsetY) { + if ((offsetX === 0) && (offsetY === 0)) { + return; + } + + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if (!child.renderable) { + continue; + } + + child.x += offsetX; + child.y += offsetY; + } +} + +export default OffsetChildren; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runverticalwrap/AlignLines.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runverticalwrap/AlignLines.js new file mode 100644 index 000000000..f4c9e685c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runverticalwrap/AlignLines.js @@ -0,0 +1,65 @@ +import GetChildrenAlign from '../GetChildrenAlign.js'; +import OffsetChildren from '../OffsetChildren.js'; + +var AlignLines = function (result, width, height) { + var hAlign = result.hAlign, + vAlign = result.vAlign; + + var offsetX, offsetY; + + var rtl = result.rtl; + var lines = result.lines, + lineWidth = result.lineWidth, + linesWidth = result.linesWidth; + switch (hAlign) { + case 1: // center + case 'center': + offsetX = (width - linesWidth) / 2 + break; + + case 2: // right + case 'right': + offsetX = width - linesWidth; + break; + + default: // left + offsetX = 0; + break; + } + if (rtl) { + offsetX += lineWidth; + } + + for (var li = 0, lcnt = lines.length; li < lcnt; li++) { + var line = lines[(rtl) ? (lcnt - li - 1) : li]; + var children = line.children; + var lineHeight = line.height; + + var lineVAlign = GetChildrenAlign(children); + if (lineVAlign === undefined) { + lineVAlign = vAlign; + } + + switch (lineVAlign) { + case 1: // center + case 'center': + offsetY = (height - lineHeight) / 2; + break; + + case 2: // bottom + case 'bottom': + offsetY = height - lineHeight; + break; + + default: // top + offsetY = 0; + break; + } + + OffsetChildren(children, offsetX, offsetY); + + offsetX += lineWidth; + } +} + +export default AlignLines; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runverticalwrap/RunVerticalWrap.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runverticalwrap/RunVerticalWrap.js new file mode 100644 index 000000000..b7cba1a97 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runverticalwrap/RunVerticalWrap.js @@ -0,0 +1,192 @@ +import { SetPadding } from '../../../../../../utils/padding/PaddingMethods.js'; +import AlignLines from './AlignLines.js'; +import { IsNewLineChar, IsPageBreakChar } from '../../../bob/Types.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var RunVerticalWrap = function (config) { + // Parse parameters + var startIndex = GetValue(config, 'start', 0); + + SetPadding(this.wrapPadding, GetValue(config, 'padding', 0)); + var paddingVertical = this.padding.top + this.padding.bottom + this.wrapPadding.top + this.wrapPadding.bottom; + var paddingHorizontal = this.padding.left + this.padding.right + this.wrapPadding.left + this.wrapPadding.right; + + var lineWidth = GetValue(config, 'lineWidth', undefined); + var maxLines; + if (lineWidth === undefined) { + // Calculate lineWidth via maxLines, in fixedWidth mode + maxLines = GetValue(config, 'maxLines', 0); + if (this.fixedWidth > 0) { + var innerWidth = this.fixedWidth - paddingHorizontal; + lineWidth = innerWidth / maxLines; + } else { + lineWidth = 0; + } + } else { + if (this.fixedWidth > 0) { + // Calculate maxLines via lineWidth, in fixedWidth mode + maxLines = GetValue(config, 'maxLines', undefined); + if (maxLines === undefined) { + var innerWidth = this.fixedWidth - paddingHorizontal; + maxLines = Math.floor(innerWidth / lineWidth) + 1; + } + } else { + maxLines = GetValue(config, 'maxLines', 0); // Default is show all lines + } + + } + var showAllLines = (maxLines === 0); + + // Get fixedChildHeight + var fixedChildHeight = GetValue(config, 'fixedChildHeight', undefined); + if (fixedChildHeight === undefined) { + var charPerLine = GetValue(config, 'charPerLine', undefined); + if (charPerLine !== undefined) { + var innerHeight = this.fixedHeight - paddingVertical; + fixedChildHeight = Math.floor(innerHeight / charPerLine); + } else { + // Use child.heigh as fixedChildHeight + } + } + + // Get wrapHeight + var wrapHeight = GetValue(config, 'wrapHeight', undefined); + if (wrapHeight === undefined) { + if (this.fixedHeight > 0) { + wrapHeight = this.fixedHeight - paddingVertical; + } else { + wrapHeight = Infinity; // No word-wrap + } + } + + var letterSpacing = GetValue(config, 'letterSpacing', 0); + + var rtl = GetValue(config, 'rtl', true); + var hAlign = GetValue(config, 'hAlign', rtl ? 2 : 0); + var vAlign = GetValue(config, 'vAlign', 0); + + var result = { + callback: 'runVerticalWrap', + start: startIndex, // Next start index + isLastPage: false, // Is last page + padding: this.wrapPadding, + lineWidth: lineWidth, + maxLines: maxLines, + fixedChildHeight: fixedChildHeight, + wrapHeight: wrapHeight, + letterSpacing: letterSpacing, + hAlign: hAlign, + vAlign: vAlign, + rtl: rtl, + children: [], // Word-wrap result + lines: [], // Word-wrap result in lines + maxLineHeight: 0, + linesWidth: 0 + } + + // Set all children to active + var children = this.children; + for (var i = 0, cnt = children.length; i < cnt; i++) { + children[i].setActive(false); + } + + // Layout children + wrapHeight += letterSpacing; + var startX = this.padding.left + this.wrapPadding.left, // Reset x of each character in AlignLines method + startY = this.padding.top + this.wrapPadding.top, + x = startX, + y = startY; + var remainderHeight = wrapHeight, + childIndex = startIndex, + lastChildIndex = children.length; + var resultChildren = result.children; + var resultLines = result.lines, + lastLine = [], lastLineHeight = 0, maxLineHeight = 0; + while (childIndex < lastChildIndex) { + // Append non-typeable child directly + var child = children[childIndex]; + childIndex++; + if (!child.renderable) { + child.setActive(); + resultChildren.push(child); + lastLine.push(child); + continue; + } + + var childHeight = ((fixedChildHeight !== undefined) ? fixedChildHeight : child.height) + letterSpacing; + // Next line + var isNewLineChar = IsNewLineChar(child); + var isPageBreakChar = IsPageBreakChar(child); + var isControlChar = isNewLineChar || isPageBreakChar; + if ((remainderHeight < childHeight) || isControlChar) { + // Add to result + if (isNewLineChar) { + child.setActive().setPosition(x, y).setOrigin(0.5); + resultChildren.push(child); + lastLine.push(child); + } + + // Move cursor + x = startX; + y = startY; + remainderHeight = wrapHeight; + resultLines.push({ children: lastLine, height: lastLineHeight }); + maxLineHeight = Math.max(maxLineHeight, lastLineHeight); + + lastLineHeight = 0; + lastLine = []; + + var isPageEnd = isPageBreakChar || + (!showAllLines && (resultLines.length === maxLines)); // Exceed maxLines + if (isPageEnd) { + break; + } else if (isControlChar) { // Already add to result + continue; + } + } + remainderHeight -= childHeight; + lastLineHeight += childHeight; + + child.setActive().setPosition(x, y).setOrigin(0.5); + resultChildren.push(child); + lastLine.push(child); + y += childHeight; + } + + if (lastLine.length > 0) { + resultLines.push({ children: lastLine, height: lastLineHeight }); + maxLineHeight = Math.max(maxLineHeight, lastLineHeight); + } + + result.start += resultChildren.length; + result.isLastPage = (result.start === lastChildIndex); + result.maxLineHeight = maxLineHeight; + result.linesWidth = (resultLines.length * lineWidth); + + // Calculate size of game object + var width = (this.fixedWidth > 0) ? this.fixedWidth : (result.linesWidth + paddingHorizontal); + var height = (this.fixedHeight > 0) ? this.fixedHeight : (result.maxLineHeight + paddingVertical); + + // Size might be changed after wrapping + var innerWidth = width - paddingHorizontal; + var innerHeight = height - paddingVertical; + AlignLines(result, innerWidth, innerHeight); + + // Resize + this.setCanvasSize(width, height); + + // Set initial position + for (var i = 0, cnt = resultChildren.length; i < cnt; i++) { + var child = resultChildren[i]; + if (!child.renderable) { + continue; + } + child.x0 = child.x; + child.y0 = child.y; + } + + return result; +} + +export default RunVerticalWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/AlignLines.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/AlignLines.js new file mode 100644 index 000000000..4095a719c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/AlignLines.js @@ -0,0 +1,60 @@ +import GetChildrenAlign from '../GetChildrenAlign.js'; +import OffsetChildren from '../OffsetChildren.js'; + +var AlignLines = function (result, width, height) { + var hAlign = result.hAlign, + vAlign = result.vAlign; + + var offsetX, offsetY; + + var linesHeight = result.linesHeight; + switch (vAlign) { + case 1: // center + case 'center': + offsetY = (height - linesHeight) / 2; + break; + + case 2: // bottom + case 'bottom': + offsetY = height - linesHeight; + break; + + default: + offsetY = 0; + break; + } + + var lines = result.lines; + for (var li = 0, lcnt = lines.length; li < lcnt; li++) { + var line = lines[li]; + var lineWidth = line.width, + children = line.children; + + var lineHAlign = GetChildrenAlign(children); + if (lineHAlign === undefined) { + lineHAlign = hAlign; + } + + switch (lineHAlign) { + case 1: // center + case 'center': + offsetX = (width - lineWidth) / 2 + break; + + case 2: // right + case 'right': + offsetX = width - lineWidth; + break; + + default: + offsetX = 0; + break; + } + + OffsetChildren(children, offsetX, offsetY); + + } + +} + +export default AlignLines; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/GetDefaultTextHeight.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/GetDefaultTextHeight.js new file mode 100644 index 000000000..1f02fed36 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/GetDefaultTextHeight.js @@ -0,0 +1,21 @@ +var GetDefaultTextHeight = function () { + var metrics = this.defaultTextStyle.getTextMetrics(this.context, this.testString); + var ascent, descent; + if ('actualBoundingBoxAscent' in metrics) { + ascent = metrics.actualBoundingBoxAscent; + descent = metrics.actualBoundingBoxDescent; + } else { + ascent = 0; + descent = 0; + } + + Result.ascent = ascent; + Result.descent = descent; + Result.height = ascent + descent; + + return Result; +} + +var Result = {}; + +export default GetDefaultTextHeight; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/GetWord.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/GetWord.js new file mode 100644 index 000000000..8414fc2cf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/GetWord.js @@ -0,0 +1,47 @@ +import { CharTypeName } from '../../../bob/Types.js'; + +var GetWord = function (children, startIndex, charMode, result) { + if (result === undefined) { + result = { word: [], width: 0 }; + } + + result.word.length = 0; + + var endIndex = children.length; + var currentIndex = startIndex; + var word = result.word, wordWidth = 0; + while (currentIndex < endIndex) { + var child = children[currentIndex]; + // Can't render (command child), put into output directly + if (!child.renderable) { + word.push(child); + currentIndex++; + continue; + } + + var text = (child.type === CharTypeName) ? child.text : null; + if ((text !== null) && + (text !== ' ') && (text !== '\n') && (text !== '\f') + ) { + word.push(child); + wordWidth += child.outerWidth; + currentIndex++; + // Continue + } else { // Get image child, a space, a new-line, or page-break + if (currentIndex === startIndex) { // Single child + word.push(child); + wordWidth += child.outerWidth; + } + break; + } + + if (charMode) { // Word only contains 1 character + break; + } + } + + result.width = wordWidth; + return result; +} + +export default GetWord; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/RunWordWrap.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/RunWordWrap.js new file mode 100644 index 000000000..c678098a2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/methods/wrap/runwordwrap/RunWordWrap.js @@ -0,0 +1,209 @@ +import { SetPadding } from '../../../../../../utils/padding/PaddingMethods.js'; +import GetWord from './GetWord.js'; +import AlignLines from './AlignLines.js'; +import { IsNewLineChar, IsPageBreakChar } from '../../../bob/Types.js'; +import GetDefaultTextHeight from './GetDefaultTextHeight.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var RunWordWrap = function (config) { + // Parse parameters + var startIndex = GetValue(config, 'start', 0); + + SetPadding(this.wrapPadding, GetValue(config, 'padding', 0)); + var paddingVertical = this.padding.top + this.padding.bottom + this.wrapPadding.top + this.wrapPadding.bottom; + var paddingHorizontal = this.padding.left + this.padding.right + this.wrapPadding.left + this.wrapPadding.right; + + // Get lineHeight, maxLines + var lineHeight = GetValue(config, 'lineHeight'); + var ascent = GetValue(config, 'ascent', lineHeight); + var maxLines; + if (lineHeight === undefined) { + // Calculate lineHeight + var useDefaultTextHeight = GetValue(config, 'useDefaultTextHeight', false); + maxLines = GetValue(config, 'maxLines', 0); + if ((this.fixedHeight > 0) && (!useDefaultTextHeight)) { + var innerHeight = this.fixedHeight - paddingVertical; + if (maxLines > 0) { + // Calculate lineHeight via maxLines, in fixedHeight mode + lineHeight = innerHeight / maxLines; + } else { + var textHeightResult = GetDefaultTextHeight.call(this); + lineHeight = textHeightResult.height; + ascent = textHeightResult.ascent; + // Calculate maxLines via (ascent, lineHeight), in fixedHeight mode + maxLines = Math.floor((innerHeight - ascent) / lineHeight); + } + } else { + var textHeightResult = GetDefaultTextHeight.call(this); + lineHeight = textHeightResult.height; + ascent = textHeightResult.ascent; + } + + } else { + // Calculate maxLines + if (this.fixedHeight > 0) { + // Calculate maxLines via lineHeight, in fixedHeight mode + maxLines = GetValue(config, 'maxLines'); + if (maxLines === undefined) { + var innerHeight = this.fixedHeight - paddingVertical; + maxLines = Math.floor(innerHeight / lineHeight); + } + } else { + maxLines = GetValue(config, 'maxLines', 0); // Default is show all lines + } + + } + + // If ascent is undefined, assign to lineHeight + if (ascent === undefined) { + ascent = lineHeight; + } + + var showAllLines = (maxLines === 0); + + // Get wrapWidth + var wrapWidth = GetValue(config, 'wrapWidth', undefined); + if (wrapWidth === undefined) { + if (this.fixedWidth > 0) { + wrapWidth = this.fixedWidth - paddingHorizontal; + } else { + wrapWidth = Infinity; // No word-wrap + } + } + + var letterSpacing = GetValue(config, 'letterSpacing', 0); + + var hAlign = GetValue(config, 'hAlign', 0); + var vAlign = GetValue(config, 'vAlign', 0); + + var charWrap = GetValue(config, 'charWrap', false); + + var result = { + callback: 'runWordWrap', + start: startIndex, // Next start index + isLastPage: false, // Is last page + padding: this.wrapPadding, + ascent: ascent, + lineHeight: lineHeight, + maxLines: maxLines, + wrapWidth: wrapWidth, + letterSpacing: letterSpacing, + hAlign: hAlign, + vAlign: vAlign, + charWrap: charWrap, + children: [], // Word-wrap result + lines: [], // Word-wrap result in lines + maxLineWidth: 0, + linesHeight: 0 + } + + // Set all children to inactive + var children = this.children; + for (var i = 0, cnt = children.length; i < cnt; i++) { + children[i].setActive(false); + } + + // Layout children + wrapWidth += letterSpacing; + var startX = this.padding.left + this.wrapPadding.left, + startY = this.padding.top + this.wrapPadding.top + ascent, // Start(baseline) from ascent, not 0 + x = startX, + y = startY; + var remainderWidth = wrapWidth, + childIndex = startIndex, + lastChildIndex = children.length; + var resultChildren = result.children; + var resultLines = result.lines, + lastLine = [], lastLineWidth = 0, maxLineWidth = 0; + var wordResult; + while (childIndex < lastChildIndex) { + wordResult = GetWord(children, childIndex, charWrap, wordResult); + var word = wordResult.word; + var charCnt = word.length; + var wordWidth = wordResult.width + (charCnt * letterSpacing); + + childIndex += charCnt; + // Next line + var isNewLineChar = IsNewLineChar(word[0]); + var isPageBreakChar = IsPageBreakChar(word[0]); + var isControlChar = isNewLineChar || isPageBreakChar; + if ((remainderWidth < wordWidth) || isControlChar) { + // Add to result + if (isControlChar) { + var char = word[0]; + char.setActive().setPosition(x, y); + resultChildren.push(char); + lastLine.push(char); + } + + // Move cursor + x = startX; + y += lineHeight; + remainderWidth = wrapWidth; + resultLines.push({ children: lastLine, width: lastLineWidth }); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + lastLineWidth = 0; + lastLine = []; + + var isPageEnd = isPageBreakChar || + (!showAllLines && (resultLines.length === maxLines)); // Exceed maxLines + if (isPageEnd) { + break; + } else if (isControlChar) { // Already add to result + continue; + } + } + remainderWidth -= wordWidth; + lastLineWidth += wordWidth; + + for (var i = 0, cnt = word.length; i < cnt; i++) { + var child = word[i]; + child.setActive(); + resultChildren.push(child); + lastLine.push(child); + + if (child.renderable) { + child.setPosition(x, y); + x += (child.outerWidth + letterSpacing); + } + } + } + + if (lastLine.length > 0) { + resultLines.push({ children: lastLine, width: lastLineWidth }); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + } + + result.start += resultChildren.length; + result.isLastPage = (result.start === lastChildIndex); + result.maxLineWidth = maxLineWidth; + result.linesHeight = (resultLines.length * lineHeight); + + // Calculate size of game object + var width = (this.fixedWidth > 0) ? this.fixedWidth : (result.maxLineWidth + paddingHorizontal); + var height = (this.fixedHeight > 0) ? this.fixedHeight : (result.linesHeight + paddingVertical); + + // Size might be changed after wrapping + var innerWidth = width - paddingHorizontal; + var innerHeight = height - paddingVertical; + AlignLines(result, innerWidth, innerHeight); + + // Resize + this.setCanvasSize(width, height); + + // Set initial position + for (var i = 0, cnt = resultChildren.length; i < cnt; i++) { + var child = resultChildren[i]; + if (!child.renderable) { + continue; + } + child.x0 = child.x; + child.y0 = child.y; + } + + return result; +}; + +export default RunWordWrap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/poolmanager/PoolManager.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/poolmanager/PoolManager.js new file mode 100644 index 000000000..e09974ccb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/dynamictext/poolmanager/PoolManager.js @@ -0,0 +1,44 @@ +import Pool from '../../../../pool.js'; + +const GetFastValue = Phaser.Utils.Objects.GetFastValue; + +var Pools = {}; +class PoolManager { + constructor(config) { + this.pools = GetFastValue(config, 'pools', Pools); + } + + free(bob) { + if (!this.pools) { + return this; + } + + var bobType = bob.type; + if (!this.pools.hasOwnProperty(bobType)) { + this.pools[bobType] = new Pool(); + } + this.pools[bobType].push(bob); + bob.onFree(); + return this; + } + + freeMultiple(arr) { + if (!this.pools) { + return this; + } + + for (var i = 0, cnt = arr.length; i < cnt; i++) { + this.free(arr[i]); + } + return this; + } + + allocate(bobType) { + if (!this.pools || !this.pools.hasOwnProperty(bobType)) { + return null; + } + return this.pools[bobType].pop(); + } +} + +export default PoolManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/Creator.js new file mode 100644 index 000000000..05c054924 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/Creator.js @@ -0,0 +1,16 @@ +import TextPlayer from './TextPlayer.js' + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', undefined); + var gameObject = new TextPlayer(this.scene, 0, 0, width, height, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/Factory.js new file mode 100644 index 000000000..f4604e70c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/Factory.js @@ -0,0 +1,7 @@ +import TextPlayer from './TextPlayer.js' + +export default function (x, y, width, height, config) { + var gameObject = new TextPlayer(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/TextPlayer.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/TextPlayer.d.ts new file mode 100644 index 000000000..36d82a638 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/TextPlayer.d.ts @@ -0,0 +1,161 @@ +// import * as Phaser from 'phaser'; +import DynamicText from '../dynamictext/DynamicText'; +import Parser from '../../../bracketparser'; +import Managers from '../../../logic/runcommands/managers/Managers'; + +export default TextPlayer; + +declare namespace TextPlayer { + + interface IConfigParser { + delimiters?: string, + comment?: string, + translateTagNameCallback?: (s: string) => string, + } + + interface IConfigTyping { + speed?: number, + onTypingStart?: (children: DynamicText.RenderChildTypes[]) => void, + animation?: { + duration?: number, + yoyo?: boolean, + onStart?: (child: DynamicText.RenderChildTypes) => void, + onProgress: (child: DynamicText.RenderChildTypes, t: number) => void, + onComplete: (child: DynamicText.RenderChildTypes) => void + }, + skipSpace?: boolean, + minSizeEnable?: boolean, + + fadeOutPage?: (children: DynamicText.RenderChildTypes[]) + => void | Phaser.Events.EventEmitter | Promise; + } + + interface IConfigImages { + [name: string]: { + width?: number, + height?: number, + key?: string, + frame?: string + } + } + + interface ISpriteGameObjectConfig { + createGameObject?: 'sprite' | 'image' | Managers.CreateGameObjectCallbackType, + + fade?: number | { + mode?: 0 | 1 | 'tint' | 'alpha', + time?: number + }, + + viewportCoordinate?: boolean | { + enable?: boolean, + viewport?: Phaser.Geom.Rectangle + } + } + + type NextPageInputTypes = string | ((callback: Function) => void) | null; + + type ClickTrgetTypes = Phaser.GameObjects.GameObject | Phaser.Scene; + + interface IConfig extends DynamicText.IConfig { + parser?: IConfigParser, + + typing?: IConfigTyping, + + images?: IConfigImages, + + sounds?: Managers.IConfigSounds, + + sprites?: ISpriteGameObjectConfig | false, + + nextPageInput?: NextPageInputTypes, + + clickTarget?: ClickTrgetTypes, + + text?: string + } + + namespace Events { + type TypingCompleteCallbackType = () => void; + + type TypingChildCallbackType = ( + child: DynamicText.RenderChildTypes + ) => void + + type PageStartCallbackType = () => void; + + type PageCompleteCallbackType = () => void; + + type WaitClickCallbackType = () => void; + + type WaitKeyDownCallbackType = (keyName: string) => void; + + type WaitTimeCallbackType = (time: number) => void; + + type WaitMusicCompleteCallbackType = ( + music: Phaser.Sound.BaseSound + ) => void; + + type WaitCameraEffectCompleteCallbackType = (effectName: string) => void; + + type WaitSpriteActionCompleteCallbackType = (name?: string, prop?: string) => void; + + type WaitCallbackType = ( + callback: () => void + ) => void; + + type ParseCustomTagOnCallbackType = (parser: Parser, ...values: any) => void; + type ExecuteCustomTagOnCallbackType = (...values: any) => void; + type ParseCustomTagOffCallbackType = (parser: Parser) => void; + type ExecuteCustomTagOffCallbackType = () => void; + } +} + +declare class TextPlayer extends DynamicText { + constructor( + scene: Phaser.Scene, + config?: TextPlayer.IConfig + ); + + addGameObjectManager(config: Managers.IGameObjectConfig): this; + + play(content: string): this; + playPromise(content: string): Promise; + + showPage(): this; + typingNextPage(): this; + + pause(): this; + pauseTyping(): this; + resume(): this; + + setTypingSpeed(speed: number): this; + typingSpeed: number; + setTimeScale(timeScale: number): this; + timeScale: number; + + readonly isPlaying: boolean; + readonly isPageTyping: boolean; + + addImage(config: TextPlayer.IConfigImages): this; + + ignoreNextPageInput(enable?: boolean): this; + setClickTarget(clickTarget: TextPlayer.ClickTrgetTypes): this; + readonly clickTarget: TextPlayer.ClickTrgetTypes; + + setTargetCamera(camera: Phaser.Cameras.Scene2D.BaseCamera): this; + readonly targetCamera: Phaser.Cameras.Scene2D.BaseCamera; + + getGameObject( + goType: string, + name: string + ): Phaser.GameObjects.GameObject; + getGameObject( + goType: string, + ): { [name: string]: Phaser.GameObjects.GameObject } + addGameObject( + goType: string, + name: string, + gameObject: Phaser.GameObjects.GameObject + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/TextPlayer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/TextPlayer.js new file mode 100644 index 000000000..25e77e84a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/TextPlayer.js @@ -0,0 +1,134 @@ +import Extend from '../../../utils/managers/Extend.js'; +import DynamicText from '../dynamictext/DynamicText.js'; +import Parser from './parser/Parser.js'; +import TypeWriter from './typewriter/TypeWriter.js'; +import ImageManager from '../../../utils/texture/imagemanager/ImageManager.js'; +import AddSpriteManager from './methods/spritemanager/AddSpriteManager.js'; +import Methods from './methods/Methods.js'; +import ClearEvents from './methods/utils/ClearEvents.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class TextPlayer extends Extend(DynamicText) { + constructor(scene, x, y, fixedWidth, fixedHeight, config) { + if (IsPlainObject(x)) { + config = x; + } else if (IsPlainObject(fixedWidth)) { + config = fixedWidth; + } + if (config === undefined) { + config = {}; + } + + // Don't set text in DynamicText's constructor + var content = config.text; + delete config.text; + + super(scene, x, y, fixedWidth, fixedHeight, config); + this.type = 'rexTextPlayer'; + + this.parser = new Parser(this, GetValue(config, 'parser', undefined)); + + this.typeWriter = new TypeWriter(this, GetValue(config, 'typing', undefined)); + + this._imageManager = undefined; + var imageData = GetValue(config, 'images', undefined); + if (imageData) { + this.addImage(imageData); + } + + this.setTargetCamera(GetValue(config, 'camera', this.scene.sys.cameras.main)); + + this.initManagers(scene, config); + + var spriteManagerConfig = GetValue(config, 'sprites'); + if ((spriteManagerConfig !== false) && (spriteManagerConfig !== null)) { + AddSpriteManager.call(this, spriteManagerConfig); + } + + this.setIgnoreNextPageInput(GetValue(config, 'ignoreNextPageInput', false)); + this.setClickTarget(GetValue(config, 'clickTarget', this)); // this.clickEE + this.setNextPageInput(GetValue(config, 'nextPageInput', null)); + + this.isPlaying = false; + + if (content) { + this.play(content); + } + } + + get imageManager() { + if (this._imageManager === undefined) { + this._imageManager = new ImageManager(this.scene); + } + return this._imageManager; + } + + get spriteManager() { + return this.getGameObjectManager('sprite'); + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + ClearEvents(this); + + this.parser.destroy(); + this.parser = undefined; + + this.typeWriter.destroy(fromScene); + this.typeWriter = undefined; + + if (this._imageManager) { + this._imageManager.destroy(fromScene); + } + this._imageManager = undefined; + + this.targetCamera = undefined; + + this.clickEE = undefined; + + this.destroyManagers(fromScene); + + super.destroy(fromScene); + } + + get isPageTyping() { + return this.typeWriter.isPageTyping; + } + + set defaultTypingSpeed(speed) { + this.typeWriter.setDefaultTypingSpeed(speed); + } + + get defaultTypingSpeed() { + return this.typeWriter.defaultTypingSpeed; + } + + set typingSpeed(speed) { + this.typeWriter.setTypingSpeed(speed); + } + + get typingSpeed() { + return this.typeWriter.speed; + } + + set timeScale(value) { + this.setTimeScale(value); + } + + get timeScale() { + return this.getTimeScale(); + } +} + +Object.assign( + TextPlayer.prototype, + Methods +); + +export default TextPlayer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/AddImage.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/AddImage.js new file mode 100644 index 000000000..94743ce85 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/AddImage.js @@ -0,0 +1,6 @@ +var AddImage = function (key, config) { + this.imageManager.add(key, config); + return this; +} + +export default AddImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/ContentMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/ContentMethods.js new file mode 100644 index 000000000..a0e6e0c05 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/ContentMethods.js @@ -0,0 +1,12 @@ +export default { + setContentOutputEnable(enable) { + this.parser.setContentOutputEnable(enable); + return this; + }, + + setContentCallback(callback, scope) { + this.contentCallback = callback; + this.contentCallbackScope = scope; + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/Methods.js new file mode 100644 index 000000000..7647e8052 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/Methods.js @@ -0,0 +1,41 @@ +import GameObjectManagerMethods from './gameobjectmanager/GameObjectManagerMethods.js'; +import SetClickTarget from './SetClickTarget.js'; +import SetTargetCamera from './SetTargetCamera.js'; +import SetNextPageInput from './SetNextPageInput.js'; +import AddImage from './AddImage.js'; +import PlayMethods from './PlayMethods.js'; +import TypingNextPage from './TypingNextPage.js'; +import PauseMethods from './PauseMethods.js'; +import ResumeMethods from './ResumeMethods.js'; +import Wait from './Wait.js'; +import TypingSpeedMethods from './TypingSpeedMethods.js'; +import SetIgnoreWait from './SetIgnoreWait.js'; +import SetIgnoreNextPageInput from './SetIgnoreNextPageInput.js'; +import ShowPage from './ShowPage.js'; +import SpriteMethods from './spritemanager/SpriteMethods.js'; +import ContentMethods from './ContentMethods.js'; + +var Methods = { + setClickTarget: SetClickTarget, + setTargetCamera: SetTargetCamera, + setNextPageInput: SetNextPageInput, + addImage: AddImage, + typingNextPage: TypingNextPage, + wait: Wait, + setIgnoreWait: SetIgnoreWait, + setIgnoreNextPageInput: SetIgnoreNextPageInput, + showPage: ShowPage, +} + +Object.assign( + Methods, + GameObjectManagerMethods, + PlayMethods, + PauseMethods, + ResumeMethods, + TypingSpeedMethods, + SpriteMethods, + ContentMethods, +); + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/PauseMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/PauseMethods.js new file mode 100644 index 000000000..eb0543111 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/PauseMethods.js @@ -0,0 +1,15 @@ +export default { + pause() { + // Pause typing, typing timer and animation progresses + this.timeline.pause(); + + return this; + }, + + pauseTyping() { + // Pause typing + this.typeWriter.pauseTyping(); + + return this; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/PlayMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/PlayMethods.js new file mode 100644 index 000000000..34f2123ab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/PlayMethods.js @@ -0,0 +1,27 @@ +import { WaitComplete } from '../../../../utils/promise/WaitEvent.js'; + +export default { + play(content) { + if (this.isPlaying) { + return this; + } + + this.removeChildren(); + this.parser.start(content); // Parse bbcode-content + + this.isPlaying = true; + this.once('complete', function () { + this.isPlaying = false; + }, this); + + this.lastWrapResult = undefined; + this.typingNextPage(); + return this; + }, + + playPromise(content) { + var promise = WaitComplete(this); + this.play(content); + return promise; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/ResumeMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/ResumeMethods.js new file mode 100644 index 000000000..d9c761018 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/ResumeMethods.js @@ -0,0 +1,15 @@ +export default { + resume() { + // Resume typing timer, animation progresses and typing + this.timeline.resume(); + + return this; + }, + + resumeTyping(offsetTime) { + // Resume typing + this.typeWriter.resumeTyping(offsetTime); + + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetClickTarget.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetClickTarget.js new file mode 100644 index 000000000..e99d46976 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetClickTarget.js @@ -0,0 +1,17 @@ +import IsSceneObject from '../../../../utils/system/IsSceneObject.js'; + +var SetClickTarget = function (target) { + this.clickTarget = target; + + if (!target) { + this.clickEE = null; + } else if (IsSceneObject(target)) { + this.clickEE = target.input; + } else { // Assume that target is a gameObject + this.clickEE = target.setInteractive(); + } + + return this; +} + +export default SetClickTarget; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetIgnoreNextPageInput.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetIgnoreNextPageInput.js new file mode 100644 index 000000000..61b0fc7e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetIgnoreNextPageInput.js @@ -0,0 +1,9 @@ +var SetIgnoreNextPageInput = function (enable) { + if (enable === undefined) { + enable = true; + } + this.ignoreNextPageInput = enable; + return this; +} + +export default SetIgnoreNextPageInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetIgnoreWait.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetIgnoreWait.js new file mode 100644 index 000000000..3dbf521b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetIgnoreWait.js @@ -0,0 +1,6 @@ +var SetIgnoreWait = function (value) { + this.typeWriter.setIgnoreWait(value); + return this; +} + +export default SetIgnoreWait; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetNextPageInput.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetNextPageInput.js new file mode 100644 index 000000000..8d782f3aa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetNextPageInput.js @@ -0,0 +1,22 @@ +import GetWrapCallback from './utils/wait/GetWrapCallback.js'; +import WaitMultiple from './utils/wait/WaitMultiple.js'; + +var SetNextPageInput = function (input) { + var textPlayer = this; + if (!input) { + this.nextPageInput = null; + + } else if (typeof (input) === 'function') { + this.nextPageInput = function (callback, args, scope) { + var wrapCallback = GetWrapCallback(textPlayer, callback, args, scope); + input.call(textPlayer, wrapCallback); + } + + } else { + this.nextPageInput = function (callback, args, scope) { + WaitMultiple(textPlayer, input, callback, args, scope); + } + } +} + +export default SetNextPageInput; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetTargetCamera.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetTargetCamera.js new file mode 100644 index 000000000..b56ad47f2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SetTargetCamera.js @@ -0,0 +1,6 @@ +var SetTargetCamera = function (camera) { + this.targetCamera = camera; + return this; +} + +export default SetTargetCamera; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/ShowPage.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/ShowPage.js new file mode 100644 index 000000000..9ff2d2e7f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/ShowPage.js @@ -0,0 +1,33 @@ +var ShowPage = function () { + // Only can work after playing, and before processing last child + if (!this.isPlaying || !this.isPageTyping) { + return this; + } + + // Save parameters + var typingSpeedSave = this.typeWriter.speed; + var ignoreWaitSave = this.typeWriter.ignoreWait; + var skipTypingAnimationSave = this.typeWriter.skipTypingAnimation; + var skipSoundEffectSave = this.typeWriter.skipSoundEffect; + + this.typeWriter + .once('complete', function () { + // Recover parameters + this.typeWriter + .setTypingSpeed(typingSpeedSave) + .setIgnoreWait(ignoreWaitSave) + .setSkipTypingAnimation(skipTypingAnimationSave) + .setSkipSoundEffect(skipSoundEffectSave) + + }, this) + + .setTypingSpeed(0) + .skipCurrentTypingDelay() + .setIgnoreWait(true) + .setSkipTypingAnimation(true) + .setSkipSoundEffect(true) + + return this; +} + +export default ShowPage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SpriteMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SpriteMethods.js new file mode 100644 index 000000000..bdd6f4f2e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/SpriteMethods.js @@ -0,0 +1,11 @@ +export default { + getSprite(name) { + return this.spriteManager.getGO(name); + }, + + addSprite(name, gameObject) { + this.spriteManager.addGO(name, gameObject); + return this; + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/TypingNextPage.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/TypingNextPage.js new file mode 100644 index 000000000..8d5bec84b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/TypingNextPage.js @@ -0,0 +1,50 @@ +import { StopPlayEvent } from './utils/Events.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var TypingNextPage = function () { + if (!this.isPlaying || this.isPageTyping) { + return this; + } + + this.typeWriter + .once('page.fadeout', _TypingNextPage, this) + .fadeOutPage(); + return this; +} + +var _TypingNextPage = function () { + var result = this.runWrap(this.lastWrapResult); + this.lastWrapResult = result; + + this.emit('page.start'); + + var OnTypingPageComplete = function () { + this.emit(StopPlayEvent); // Clear registed StopPlayEvent + if (result.isLastPage) { + this.emit('complete'); + } else { + this.emit('page.complete'); + + if (this.ignoreNextPageInput) { + TypingNextPage.call(this); + } else if (this.nextPageInput) { + this.nextPageInput(TypingNextPage, [], this); + } else { + // Stop here, don't typing next page. + } + + } + } + + // Remove event when typing pages has been canceled + this.once(StopPlayEvent, function () { + this.typeWriter.off('complete', OnTypingPageComplete, this); + }) + + this.typeWriter + .once('complete', OnTypingPageComplete, this) + .start(result.children); +} + +export default TypingNextPage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/TypingSpeedMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/TypingSpeedMethods.js new file mode 100644 index 000000000..fb90d6f49 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/TypingSpeedMethods.js @@ -0,0 +1,11 @@ +export default { + setDefaultTypingSpeed(speed) { + this.defaultTypingSpeed = speed; + return this; + }, + + setTypingSpeed(speed) { + this.typingSpeed = speed; + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/Wait.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/Wait.js new file mode 100644 index 000000000..38d10895a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/Wait.js @@ -0,0 +1,6 @@ +var Wait = function (name) { + this.typeWriter.wait(name); + return this; +} + +export default Wait; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/GameObjectManagerMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/GameObjectManagerMethods.js new file mode 100644 index 000000000..2824dcea3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/GameObjectManagerMethods.js @@ -0,0 +1,43 @@ +import GameObjectManagerMethods from '../../../../../utils/managers/GameObjectManagerMethods.js'; +import OnParseAddGameObjectTag from './OnParseAddGameObjectTag.js'; +import OnParseRemoveAllGameObjectsTag from './OnParseRemoveAllGameObjectsTag.js'; +import OnParseCallGameObjectMethodTag from './OnParseCallGameObjectMethodTag.js'; +import OnParseEaseGameObjectPropertyTag from './OnParseEaseGameObjectPropertyTag.js'; + +const ParseCallbacks = [ + OnParseAddGameObjectTag, OnParseRemoveAllGameObjectsTag, + OnParseCallGameObjectMethodTag, + OnParseEaseGameObjectPropertyTag +]; + +const AddGameObjectManager = GameObjectManagerMethods.addGameObjectManager; + +export default { + addGameObjectManager(config, GameObjectManagerClass) { + if (config === undefined) { + config = {}; + } + var name = config.name; + if (!name) { + console.warn(`Parameter 'name' is required in TextPlayer.addGameObjectManager(config) method`); + } + + AddGameObjectManager.call(this, config, GameObjectManagerClass); + + // Register parse callbacks + var customParseCallbacks = config.parseCallbacks; + if (!customParseCallbacks) { + customParseCallbacks = ParseCallbacks; + } else { + customParseCallbacks = [ + ...customParseCallbacks, // customParseCallbacks have higher priority + ...ParseCallbacks + ]; + } + for (var i = 0, cnt = customParseCallbacks.length; i < cnt; i++) { + customParseCallbacks[i](this, this.parser, config); + } + + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseAddGameObjectTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseAddGameObjectTag.js new file mode 100644 index 000000000..5ac444597 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseAddGameObjectTag.js @@ -0,0 +1,75 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var IsAddGameObjectTag = function (tags, goType) { + // goType.name + return (tags.length === 2) && (tags[0] === goType) +} + +var OnParseAddGameObjectTag = function (textPlayer, parser, config) { + var goType = config.name; + parser + .on('+', function (tag, ...args) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name=key,frame], or [goType.name] + var tags = tag.split('.'); + var name; + if (IsAddGameObjectTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + + AppendCommandBase.call(textPlayer, + `${goType}.add`, // name + AddGameObject, // callback + [goType, name, ...args], // params + textPlayer, // scope + ); + + parser.skipEvent(); + }) + .on('-', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [/goType.name] + var tags = tag.split('.'); + var name; + if (IsAddGameObjectTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + + AppendCommandBase.call(textPlayer, + `${goType}.remove`, // name + RemoveGameObject, // callback + [goType, name], // params + textPlayer, // scope + ); + + parser.skipEvent(); + }) +} + +var AddGameObject = function (params) { + var goType, args; + [goType, ...args] = params; + // this: textPlayer + var gameObjectManager = this.getGameObjectManager(goType); + gameObjectManager.add(...args); +} + +var RemoveGameObject = function (params) { + var goType, args; + [goType, ...args] = params; + // this: textPlayer + var gameObjectManager = this.getGameObjectManager(goType); + gameObjectManager.remove(...args); +} + +export default OnParseAddGameObjectTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseCallGameObjectMethodTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseCallGameObjectMethodTag.js new file mode 100644 index 000000000..291674861 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseCallGameObjectMethodTag.js @@ -0,0 +1,63 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var IsPropTag = function (tags, goType) { + // goType.name.prop + return (tags.length === 3) && (tags[0] === goType); +} + +var OnParseCallGameObjectMethodTag = function (textPlayer, parser, config) { + var goType = config.name; + parser + .on(`+`, function (tag, ...parameters) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name.methodName=value0,value1,value2...] + // [goType.name.prop=value] + var tags = tag.split('.'); + var name, prop; + if (IsPropTag(tags, goType)) { + name = tags[1]; + prop = tags[2]; + } else { + return; + } + + AppendCommandBase.call(textPlayer, + `${goType}.call`, // name + CallMethod, // callback + [goType, name, prop, ...parameters], // params + textPlayer, // scope + ); + + parser.skipEvent(); + }) +} + +var CallMethod = function (params) { + var goType, name, prop, args; + [goType, name, prop, ...args] = params; + // this: textPlayer + + var eventName = `${goType}.${prop}`; + this.emit( + eventName, + name, ...args + ); + if (this.listenerCount(eventName) > 0) { + return; + } + + var gameObjectManager = this.getGameObjectManager(goType); + if (gameObjectManager.hasMethod(name, prop)) { + // Is method + gameObjectManager.call(name, prop, ...args); + } else { + // Is property + gameObjectManager.setProperty(name, prop, args[0]); + } + +} + +export default OnParseCallGameObjectMethodTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseEaseGameObjectPropertyTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseEaseGameObjectPropertyTag.js new file mode 100644 index 000000000..1ac5183a1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseEaseGameObjectPropertyTag.js @@ -0,0 +1,99 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var EaseMode = { + to: true, yoyo: true, from: true, + toLeft: true, toRight: true, toUp: true, toDown: true, + yoyoLeft: true, yoyoRight: true, yoyoUp: true, yoyoDown: true, + fromLeft: true, fromRight: true, fromUp: true, fromDown: true, +} +var IsEasePropertyTag = function (tags, goType) { + // goType.name.prop.to + return (tags.length === 4) && (tags[0] === goType) && EaseMode[tags[3]]; +} + +var OnParseEaseGameObjectPropertyTag = function (textPlayer, parser, config) { + var goType = config.name; + var gameObjectManager = textPlayer.getGameObjectManager(goType); + parser + .on(`+`, function (tag, value, duration, ease, repeat) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name.prop.to=value,duration] + // [goType.name.prop.to=value,duration,ease,repeat] + // [goType.name.prop.to=value,duration,repeat] + var tags = tag.split('.'); + var name, property, easeMode; + if (IsEasePropertyTag(tags, goType)) { + name = tags[1]; + property = tags[2]; + easeMode = tags[3]; + } else { + return; + } + + if (typeof (ease) === 'number') { + repeat = ease; + ease = undefined; + } + + AppendCommandBase.call(textPlayer, + `${goType}.ease`, // name + EaseProperty, // callback + [ + goType, + name, property, value, + duration, ease, repeat, easeMode + ], // params + textPlayer, // scope + ); + + parser.skipEvent(); + }) +} + +var EaseProperty = function (params) { + var goType, name, property, value, duration, ease, repeat, easeMode; + [ + goType, + name, property, value, + duration, ease, repeat, easeMode + ] = params; + // this: textPlayer + var gameObjectManager = this.getGameObjectManager(goType); + + var currentValue = gameObjectManager.getProperty(name, property); + // Only can tween number property + if (typeof (currentValue) !== 'number') { + return; + } + + if (easeMode.endsWith('Left') || easeMode.endsWith('Up')) { + if (easeMode.startsWith('to') || easeMode.startsWith('yoyo')) { + value = currentValue - value; + } else if (easeMode.startsWith('from')) { + gameObjectManager.setProperty(name, property, (currentValue - value)); + value = currentValue; + } + } else if (easeMode.endsWith('Right') || easeMode.endsWith('Down')) { + if (easeMode.startsWith('to') || easeMode.startsWith('yoyo')) { + value = currentValue + value; + } else if (easeMode.startsWith('from')) { + gameObjectManager.setProperty(name, property, (currentValue + value)); + value = currentValue; + } + } else if (easeMode === 'from') { + gameObjectManager.setProperty(name, property, value); + value = currentValue; + } + + var isYoyo = easeMode.startsWith('yoyo'); + + gameObjectManager.easeProperty( + name, property, value, + duration, ease, repeat, isYoyo + ); +} + +export default OnParseEaseGameObjectPropertyTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseRemoveAllGameObjectsTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseRemoveAllGameObjectsTag.js new file mode 100644 index 000000000..5c074ebba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/gameobjectmanager/OnParseRemoveAllGameObjectsTag.js @@ -0,0 +1,33 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseRemoveAllGameObjectsTag = function (textPlayer, parser, config) { + var goType = config.name; + parser + .on('-', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [/goType] + if (tag === goType) { + } else { + return; + } + + AppendCommandBase.call(textPlayer, + `${goType}.removeall`, // name + RemoveAllSprites, // callback + goType, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) +} + +var RemoveAllSprites = function (goType) { + // this: textPlayer + var gameObjectManager = this.getGameObjectManager(goType); + gameObjectManager.removeAll(); +} + +export default OnParseRemoveAllGameObjectsTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/AddSpriteManager.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/AddSpriteManager.js new file mode 100644 index 000000000..9ab7a4371 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/AddSpriteManager.js @@ -0,0 +1,21 @@ +import SpriteManager from '../../../../../utils/sprite/spritemanager/SpriteManager.js'; +import OnParsePlayAnimationTag from './OnParsePlayAnimationTag.js'; +import OnParsePauseAnimationTag from './OnParsePauseAnimationTag.js'; +import OnParseChainAnimationTag from './OnParseChainAnimationTag.js'; + +const ParseCallbacks = [ + OnParsePlayAnimationTag, + OnParsePauseAnimationTag, + OnParseChainAnimationTag, +]; + +var AddSpriteManager = function (config) { + if (config === undefined) { + config = {}; + } + config.name = 'sprite'; + config.parseCallbacks = ParseCallbacks; + this.addGameObjectManager(config, SpriteManager); +} + +export default AddSpriteManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/OnParseChainAnimationTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/OnParseChainAnimationTag.js new file mode 100644 index 000000000..2ca6ae3a8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/OnParseChainAnimationTag.js @@ -0,0 +1,45 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var IsChainAnimationTag = function (tags, goType) { + // goType.name.chain + return (tags.length === 3) && (tags[0] === goType) && (tags[2] === 'chain'); +} + +var OnParseChainAnimationTag = function (textPlayer, parser, config) { + var goType = config.name; + parser + .on('+', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name.chain=key] + var tags = tag.split('.'); + var name; + if (IsChainAnimationTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + + var keys = Array.prototype.slice.call(arguments, 1); + AppendCommandBase.call(textPlayer, + `${goType}.chain`, // name + ChainAnimation, // callback + [goType, name, keys], // params + textPlayer, // scope + ); + + parser.skipEvent(); + }) +} + +var ChainAnimation = function (params) { + var goType, args; + [goType, ...args] = params; + // this: textPlayer + var gameObjectManager = this.getGameObjectManager(goType); + gameObjectManager.chainAnimation(...args); +} + +export default OnParseChainAnimationTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/OnParsePauseAnimationTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/OnParsePauseAnimationTag.js new file mode 100644 index 000000000..1cdddbdb4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/OnParsePauseAnimationTag.js @@ -0,0 +1,44 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var IsPauseAnimationTag = function (tags, goType) { + // goType.name.pause + return (tags.length === 3) && (tags[0] === goType) && (tags[2] === 'pause'); +} + +var OnParsePauseAnimationTag = function (textPlayer, parser, config) { + var goType = config.name; + parser + .on('+', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name.pause=key] + var tags = tag.split('.'); + var name; + if (IsPauseAnimationTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + + AppendCommandBase.call(textPlayer, + `${goType}.pause`, // name + PauseAnimation, // callback + [goType, name], // params + textPlayer, // scope + ); + + parser.skipEvent(); + }) +} + +var PauseAnimation = function (params) { + var goType, args; + [goType, ...args] = params; + // this: textPlayer + var gameObjectManager = this.getGameObjectManager(goType); + gameObjectManager.pauseAnimation(...args); +} + +export default OnParsePauseAnimationTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/OnParsePlayAnimationTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/OnParsePlayAnimationTag.js new file mode 100644 index 000000000..0b94a4317 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/OnParsePlayAnimationTag.js @@ -0,0 +1,108 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var IsPlayAnimationTag = function (tags, goType) { + // goType.name.play + return (tags.length === 3) && (tags[0] === goType) && (tags[2] === 'play'); +} + +var IsStopAnimationTag = function (tags, goType) { + // goType.name.stop + return (tags.length === 3) && (tags[0] === goType) && (tags[2] === 'stop'); +} + +var OnParsePlayAnimationTag = function (textPlayer, parser, config) { + var goType = config.name; + parser + .on('+', function (tag, ...keys) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name.play=key], or [goType.name.play=key0,key1,...] + var tags = tag.split('.'); + var name; + if (IsPlayAnimationTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + + AppendCommandBase.call(textPlayer, + `${goType}.play`, // name + PlayAnimation, // callback + [goType, name, keys], // params + textPlayer, // scope + ); + + parser.skipEvent(); + }) + .on('+', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name.stop] + var tags = tag.split('.'); + var name; + if (IsStopAnimationTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + + AppendCommandBase.call(textPlayer, + `${goType}.stop`, // name + StopAnimation, // callback + [goType, name], // params + textPlayer, // scope + ); + + parser.skipEvent(); + }) + .on('-', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [/goType.name.play] + var tags = tag.split('.'); + var name; + if (IsPlayAnimationTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + + AppendCommandBase.call(textPlayer, + `${goType}.stop`, // name + StopAnimation, // callback + [goType, name], // params + textPlayer, // scope + ); + + parser.skipEvent(); + }) +} + +var PlayAnimation = function (params) { + var goType, name, keys; + [goType, name, keys] = params; + var key = keys.shift(); + + // this: textPlayer + var gameObjectManager = this.getGameObjectManager(goType); + gameObjectManager.playAnimation(name, key); + if (keys.length > 0) { + gameObjectManager.chainAnimation(name, keys); + } +} + +var StopAnimation = function (params) { + var goType, args; + [goType, ...args] = params; + // this: textPlayer + var gameObjectManager = this.getGameObjectManager(goType); + gameObjectManager.stopAnimation(...args); +} + +export default OnParsePlayAnimationTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/SpriteMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/SpriteMethods.js new file mode 100644 index 000000000..57cdbb186 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/spritemanager/SpriteMethods.js @@ -0,0 +1,11 @@ +export default { + getSprite(name) { + return this.getGameObject('sprite', name); + }, + + addSprite(name, gameObject) { + this.addGameObject('sprite', name, gameObject); + return this; + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/ClearEvents.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/ClearEvents.js new file mode 100644 index 000000000..6e5736f79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/ClearEvents.js @@ -0,0 +1,9 @@ +import { ClearEvents as Events } from './Events.js'; + +var ClearEvents = function (textPlayer) { + for (var i = 0, cnt = Events.length; i < cnt; i++) { + textPlayer.emit(Events[i]); + } +} + +export default ClearEvents; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/Events.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/Events.js new file mode 100644 index 000000000..4c37982e1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/Events.js @@ -0,0 +1,15 @@ +// Internal events + +const RemoveWaitEvents = '_remove.wait'; +const StopPlayEvent = '_remove.play'; + +const ClearEvents = [ + RemoveWaitEvents, + StopPlayEvent +] + +export { + RemoveWaitEvents, + StopPlayEvent, + ClearEvents +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/Progress.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/Progress.js new file mode 100644 index 000000000..4723b40a7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/Progress.js @@ -0,0 +1,5 @@ +var Progress = function (textPlayer, config) { + return textPlayer.typeWriter.timeline.addTimer(config); +} + +export default Progress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/GetWrapCallback.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/GetWrapCallback.js new file mode 100644 index 000000000..6709d3a65 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/GetWrapCallback.js @@ -0,0 +1,9 @@ +import { RemoveWaitEvents } from '../Events.js'; + +var GetWrapCallback = function (textPlayer, callback, args, scope, removeFrom) { + return function () { + textPlayer.emit(RemoveWaitEvents, removeFrom); // Remove all wait events + callback.apply(scope, args); + } +} +export default GetWrapCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitCallback.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitCallback.js new file mode 100644 index 000000000..91a7fa130 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitCallback.js @@ -0,0 +1,10 @@ +import GetWrapCallback from './GetWrapCallback.js'; + +var WaitCallback = function (textPlayer, postfixName, callback, args, scope) { + var wrapCallback = GetWrapCallback(textPlayer, callback, args, scope, 'custom'); + + var eventName = (postfixName) ? `wait.${postfixName}` : 'wait'; + textPlayer.emit(eventName, wrapCallback); +} + +export default WaitCallback; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitCameraEffect.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitCameraEffect.js new file mode 100644 index 000000000..53a8d2ceb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitCameraEffect.js @@ -0,0 +1,77 @@ +import GetWrapCallback from './GetWrapCallback.js'; +import { RemoveWaitEvents } from '../Events.js'; + +var IsWaitCameraEffect = function (name) { + switch (name) { + case 'camera.fadein': + case 'camera.fadeout': + case 'camera.flash': + case 'camera.shake': + case 'camera.zoom': + case 'camera.rotate': + case 'camera.scroll': + return true; + default: + return false; + } +} + +var WaitCameraEffect = function (textPlayer, effectName, callback, args, scope) { + var wrapCallback = GetWrapCallback(textPlayer, callback, args, scope, `camera.${effectName}`); + + var camera = textPlayer.targetCamera; + + var effect, completeEventName; + switch (effectName) { + case 'camera.fadein': + effect = camera.fadeEffect; + completeEventName = 'camerafadeincomplete'; + break; + + case 'camera.fadeout': + effect = camera.fadeEffect; + completeEventName = 'camerafadeoutcomplete'; + break; + + case 'camera.flash': + effect = camera.flashEffect; + completeEventName = 'cameraflashcomplete'; + break; + + case 'camera.shake': + effect = camera.shakeEffect; + completeEventName = 'camerashakecomplete'; + break; + + case 'camera.zoom': + effect = camera.zoomEffect; + completeEventName = 'camerazoomcomplete'; + break; + + case 'camera.rotate': + effect = camera.rotateToEffect; + completeEventName = 'camerarotatecomplete'; + break; + + case 'camera.scroll': + effect = camera.panEffect; + completeEventName = 'camerapancomplete'; + break; + } + + if (!effect.isRunning) { + textPlayer.emit('wait.camera', effectName); + wrapCallback(); + + } else { + // Remove all wait events + textPlayer.once(RemoveWaitEvents, function (removeFrom) { + camera.off(completeEventName, wrapCallback, textPlayer); + }); + camera.once(completeEventName, wrapCallback, textPlayer); + textPlayer.emit('wait.camera', effectName); + } + +} + +export { IsWaitCameraEffect, WaitCameraEffect }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitClick.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitClick.js new file mode 100644 index 000000000..05b85df99 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitClick.js @@ -0,0 +1,23 @@ +import GetWrapCallback from './GetWrapCallback.js'; +import { RemoveWaitEvents } from '../Events.js'; + +var WaitClick = function (textPlayer, callback, args, scope) { + var clickEE = textPlayer.clickEE; + + if (!clickEE) { + return; + } + + var wrapCallback = GetWrapCallback(textPlayer, callback, args, scope, 'click'); + + // Remove all wait events + textPlayer.once(RemoveWaitEvents, function () { + clickEE.off('pointerdown', wrapCallback, textPlayer); + }); + + clickEE.once('pointerdown', wrapCallback, textPlayer); + + textPlayer.emit('wait.click'); +} + +export default WaitClick; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitGameObject.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitGameObject.js new file mode 100644 index 000000000..82111a40b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitGameObject.js @@ -0,0 +1,108 @@ +import GetWrapCallback from './GetWrapCallback.js'; +import { RemoveWaitEvents } from '../Events.js'; + +var IsWaitGameObject = function (textPlayer, name) { + var names = name.split('.'); + return textPlayer.gameObjectManagers.hasOwnProperty(names[0]); +} + +var WaitGameObject = function (textPlayer, tag, callback, args, scope) { + var wrapCallback = GetWrapCallback(textPlayer, callback, args, scope); + var tags = tag.split('.'); + var goType = tags[0]; + var gameObjectManager = textPlayer.getGameObjectManager(goType); + var waitEventName = `wait.${goType}` + switch (tags.length) { + case 1: // 'goType' : wait all sprites has beeen destroyed + if (gameObjectManager.isEmpty) { + textPlayer.emit(waitEventName); + wrapCallback(); + } else { + // Remove all wait events + textPlayer.once(RemoveWaitEvents, function (removeFrom) { + gameObjectManager.off('empty', wrapCallback, textPlayer); + }); + gameObjectManager.once('empty', wrapCallback, textPlayer); + textPlayer.emit(waitEventName); + } + return; + + case 2: // 'goType.name' : wait goType.name has been destroyed + var name = tags[1]; + if (!gameObjectManager.has(name)) { + textPlayer.emit(waitEventName, name); + wrapCallback(); + } else { + var spriteData = gameObjectManager.get(name); + var gameObject = spriteData.gameObject; + // Remove all wait events + textPlayer.once(RemoveWaitEvents, function () { + gameObject.off('destroy', wrapCallback, textPlayer); + }); + + gameObject.once('destroy', wrapCallback, textPlayer); + textPlayer.emit(waitEventName, name); + } + return; + + case 3: // 'goType.name.prop' : wait ease goType.name.prop has been completed + var name = tags[1], + prop = tags[2]; + + var value = gameObjectManager.getProperty(name, prop); + // Can start tween task for a number property + if (typeof (value) === 'number') { + var task = gameObjectManager.getTweenTask(name, prop); + if (!task) { + textPlayer.emit(waitEventName, name, prop); + wrapCallback(); + } else { + // Remove all wait events + textPlayer.once(RemoveWaitEvents, function () { + task.off('complete', wrapCallback, textPlayer); + }); + + task.once('complete', wrapCallback, textPlayer); + textPlayer.emit(waitEventName, name, prop); + } + return; + } + + var dataKey = prop; + var matchFalseFlag = dataKey.startsWith('!'); + if (matchFalseFlag) { + dataKey = dataKey.substring(1); + } + // Wait until flag is true/false + if (gameObjectManager.hasData(name, dataKey)) { + var gameObject = gameObjectManager.getGO(name); + var flag = gameObject.getData(dataKey); + var matchTrueFlag = !matchFalseFlag; + if (flag === matchTrueFlag) { + textPlayer.emit(waitEventName, name, prop); + wrapCallback(); + } else { + // Remove all wait events + var eventName = `changedata-${dataKey}`; + var callback = function (gameObject, value, previousValue) { + value = !!value; + if (value === matchTrueFlag) { + wrapCallback.call(textPlayer); + } + } + textPlayer.once(RemoveWaitEvents, function () { + gameObject.off(eventName, callback); + }); + + gameObject.on(eventName, callback); + textPlayer.emit(waitEventName, name, prop); + } + return; + } + + } + +} + + +export { IsWaitGameObject, WaitGameObject }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitKeyDown.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitKeyDown.js new file mode 100644 index 000000000..7f5c9619e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitKeyDown.js @@ -0,0 +1,20 @@ +import GetWrapCallback from './GetWrapCallback.js'; +import { RemoveWaitEvents } from '../Events.js'; + +var WaitKeyDown = function (textPlayer, keyName, callback, args, scope) { + var wrapCallback = GetWrapCallback(textPlayer, callback, args, scope, 'keydown'); + + var eventName = `keydown-${keyName.toUpperCase()}`; + var keyboard = textPlayer.scene.input.keyboard; + + // Remove all wait events + textPlayer.once(RemoveWaitEvents, function () { + keyboard.off(eventName, wrapCallback, textPlayer); + }); + + keyboard.once(eventName, wrapCallback, textPlayer); + + textPlayer.emit('wait.keydown', keyName); +} + +export default WaitKeyDown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitMultiple.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitMultiple.js new file mode 100644 index 000000000..3299f1e8e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitMultiple.js @@ -0,0 +1,62 @@ +import WaitCallback from './WaitCallback.js'; +import WaitTime from './WaitTime.js'; +import WaitClick from './WaitClick.js'; +import WaitMusic from './WaitMusic.js'; +import { IsWaitCameraEffect, WaitCameraEffect } from './WaitCameraEffect.js'; +import WaitKeyDown from './WaitKeyDown.js'; +import { IsWaitGameObject, WaitGameObject } from './WaitGameObject.js' + +const KeyCodes = Phaser.Input.Keyboard.KeyCodes; + +var WaitMultiple = function (textPlayer, names, callback, args, scope) { + if ((typeof (names) === 'string') && (names.length > 1) && (names.indexOf('|') !== -1)) { + names = names.split('|'); + } else { + names = [names]; + } + + for (var i = 0, cnt = names.length; i < cnt; i++) { + var name = names[i]; + + if ((name == null) || (name === 'wait')) { // Wait event + WaitCallback(textPlayer, undefined, callback, args, scope); + + } else if ((typeof (name) === 'number') || !isNaN(name)) { // A number, or a number string + WaitTime(textPlayer, parseFloat(name), callback, args, scope); + + } else if (name === 'click') { // 'click' + WaitClick(textPlayer, callback, args, scope); + + } else if (name === 'se') { + var music = textPlayer.soundManager.getLastSoundEffect(); + WaitMusic(textPlayer, music, callback, args, scope); + + } else if (name === 'se2') { + var music = textPlayer.soundManager.getLastSoundEffect2(); + WaitMusic(textPlayer, music, callback, args, scope); + + } else if (name === 'bgm') { + var music = textPlayer.soundManager.getBackgroundMusic(); + WaitMusic(textPlayer, music, callback, args, scope); + + } else if (name === 'bgm2') { + var music = textPlayer.soundManager.getBackgroundMusic2(); + WaitMusic(textPlayer, music, callback, args, scope); + + } else if (KeyCodes.hasOwnProperty(name.toUpperCase())) { + WaitKeyDown(textPlayer, name, callback, args, scope); + + } else if (IsWaitCameraEffect(name)) { + WaitCameraEffect(textPlayer, name, callback, args, scope); + + } else if (IsWaitGameObject(textPlayer, name)) { + WaitGameObject(textPlayer, name, callback, args, scope); + + } else { + WaitCallback(textPlayer, name, callback, args, scope); + + } + } +} + +export default WaitMultiple; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitMusic.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitMusic.js new file mode 100644 index 000000000..ae63df344 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitMusic.js @@ -0,0 +1,23 @@ +import GetWrapCallback from './GetWrapCallback.js'; +import { RemoveWaitEvents } from '../Events.js'; + +var WaitMusic = function (textPlayer, music, callback, args, scope) { + var wrapCallback = GetWrapCallback(textPlayer, callback, args, scope, 'music'); + + if (music) { + // Remove all wait events + textPlayer.once(RemoveWaitEvents, function () { + music.off('complete', wrapCallback, textPlayer); + }); + + music.once('complete', wrapCallback, textPlayer); + } + + textPlayer.emit('wait.music', music); + + if (!music) { + wrapCallback(); + } +} + +export default WaitMusic; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitTime.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitTime.js new file mode 100644 index 000000000..9fecbe360 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/methods/utils/wait/WaitTime.js @@ -0,0 +1,22 @@ +import GetWrapCallback from './GetWrapCallback.js'; +import { RemoveWaitEvents } from '../Events.js'; + +var WaitTime = function (textPlayer, time, callback, args, scope) { + var wrapCallback = GetWrapCallback(textPlayer, callback, args, scope, 'time'); + + var timer; + + // Remove all wait events + textPlayer.once(RemoveWaitEvents, function () { + if (timer) { + timer.remove(); + timer = undefined; + } + }); + + timer = textPlayer.timeline.delayCall(time, wrapCallback); + + textPlayer.emit('wait.time', time); +} + +export default WaitTime; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/AddParseCallbacks.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/AddParseCallbacks.js new file mode 100644 index 000000000..e6e2a8af7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/AddParseCallbacks.js @@ -0,0 +1,69 @@ +import ParseColorTag from './textstyle/OnParseColorTag.js'; +import ParseStrokeColorTag from './textstyle/OnParseStrokeColorTag.js'; +import ParseBoldTag from './textstyle/OnParseBoldTag.js'; +import ParseItalicTag from './textstyle/OnParseItalicTag.js'; +import ParseFontSizeTag from './textstyle/OnParseFontSizeTag.js'; +import ParseOffsetYTag from './textstyle/OnParseOffsetYTag.js'; +import ParseOffsetXTag from './textstyle/OnParseOffsetXTag.js'; +import ParseLeftSpaceTag from './textstyle/OnParseLeftSpaceTag.js'; +import ParseRightSpaceTag from './textstyle/OnParseRightSpaceTag.js'; +import ParseShadowColorTag from './textstyle/OnParseShadowColorTag.js'; +import ParseAlignTag from './textstyle/OnParseAlignTag.js' +import ParseImageTag from './image/OnParseImageTag.js'; +import ParseSpaceTag from './space/OnParseSpaceTag.js'; +import ParseTypingSpeedTag from './typing/OnParseTypingSpeedTag.js'; +import ParsePlaySoundEffectTag from './soundeffect/OnParsePlaySoundEffectTag.js'; +import ParseFadeInSoundEffectTag from './soundeffect/OnParseFadeInSoundEffectTag.js'; +import ParseFadeOutSoundEffectTag from './soundeffect/OnParseFadeOutSoundEffectTag.js'; +import ParseSetSoundEffectVolumeTag from './soundeffect/OnParseSetSoundEffectVolumeTag.js'; +import ParsePlayBackgroundMusicTag from './backgroundmusic/OnParsePlayBackgroundMusicTag.js'; +import ParseFadeInBackgroundMusicTag from './backgroundmusic/OnParseFadeInBackgroundMusicTag.js'; +import ParseFadeOutBackgroundMusicTag from './backgroundmusic/OnParseFadeOutBackgroundMusicTag.js'; +import ParseCrossFadeBackgroundMusicTag from './backgroundmusic/OnParseCrossFadeBackgroundMusicTag.js'; +import ParsePauseBackgroundMusicTag from './backgroundmusic/OnParsePauseBackgroundMusicTag.js'; +import ParseFadeInCameraTag from './camera/OnParseFadeInCameraTag.js'; +import ParseFadeOutCameraTag from './camera/OnParseFadeOutCameraTag.js'; +import ParseShakeCameraTag from './camera/OnParseShakeCameraTag.js'; +import ParseFlashCameraTag from './camera/OnParseFlashCameraTag.js'; +import ParseZoomCameraTag from './camera/OnParseZoomCameraTag.js'; +import ParseRotateCameraTag from './camera/OnParseRotateCameraTag.js'; +import ParseScrollCameraTag from './camera/OnParseScrollCameraTag.js'; +import ParseWaitTag from './wait/OnParseWaitTag.js'; +import ParseNewLineTag from './content/OnParseNewLineTag.js'; +import ParsePageBreakTag from './content/OnParsePageBreakTag.js'; +import ParseContentOff from './content/OnParseContentOff.js'; +import ParseContentOn from './content/OnParseContentOn.js'; +import ParseContent from './content/OnParseContent.js'; +import ParseCustomTag from './custom/OnParseCustomTag.js'; + +const ParseCallbacks = [ + ParseColorTag, ParseStrokeColorTag, + ParseBoldTag, ParseItalicTag, + ParseFontSizeTag, ParseShadowColorTag, ParseAlignTag, + ParseOffsetYTag, ParseOffsetXTag, ParseLeftSpaceTag, ParseRightSpaceTag, + ParseImageTag, + ParseSpaceTag, + + ParseTypingSpeedTag, + + ParsePlaySoundEffectTag, ParseFadeInSoundEffectTag, ParseFadeOutSoundEffectTag, ParseSetSoundEffectVolumeTag, + ParsePlayBackgroundMusicTag, ParseFadeInBackgroundMusicTag, ParseFadeOutBackgroundMusicTag, ParseCrossFadeBackgroundMusicTag, ParsePauseBackgroundMusicTag, + + ParseFadeInCameraTag, ParseFadeOutCameraTag, ParseShakeCameraTag, ParseFlashCameraTag, ParseZoomCameraTag, ParseRotateCameraTag, ParseScrollCameraTag, + + ParseWaitTag, + + ParseNewLineTag, ParsePageBreakTag, + ParseContentOff, ParseContentOn, + ParseContent, + + ParseCustomTag, +]; + +var AddParseCallbacks = function (textPlayer, parser, config) { + for (var i = 0, cnt = ParseCallbacks.length; i < cnt; i++) { + ParseCallbacks[i](textPlayer, parser, config); + } +} + +export default AddParseCallbacks; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/Parser.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/Parser.js new file mode 100644 index 000000000..8e0f4f635 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/Parser.js @@ -0,0 +1,42 @@ +import BracketParser from '../../../../bracketparser.js'; +import AddParseCallbacks from './AddParseCallbacks.js'; +import PreProcessSource from './PreProcessSource.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Parser extends BracketParser { + constructor(textPlayer, config) { + if (config === undefined) { + config = {}; + } + if (!config.hasOwnProperty('delimiters')) { + config.delimiters = '[]'; + } + super(config); + + AddParseCallbacks(textPlayer, this, config); + + this.setCommentLineStartSymbol(GetValue(config, 'comment', '//')); + this.setContentOutputEnable(); + } + + setCommentLineStartSymbol(symbol) { + this.commentLineStart = symbol; + return this; + } + + setContentOutputEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.contentOutputEnable = enable; + return this; + } + + start(source) { + super.start(PreProcessSource(this, source)); + return this; + } +} + +export default Parser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/PreProcessSource.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/PreProcessSource.js new file mode 100644 index 000000000..fb3341436 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/PreProcessSource.js @@ -0,0 +1,28 @@ +/* +Skip line +- An empty line, only has space +- A comment line, start with commentLineStart ('//') +*/ + +var PreProcess = function (parser, source) { + var comentLineStart = parser.commentLineStart; + var lines = source.split('\n'); + for (var i = 0, cnt = lines.length; i < cnt; i++) { + var line = lines[i]; + if (line === '') { + // Do nothing + + } else if (line.trim().length === 0) { + // An empty line, only has space + lines[i] = ''; + + } else if (comentLineStart && line.startsWith(comentLineStart)) { + // A comment line, start with commentLineStart ('//') + lines[i] = ''; + } + } + // Use [r] to put \n + return lines.join(''); +} + +export default PreProcess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseCrossFadeBackgroundMusicTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseCrossFadeBackgroundMusicTag.js new file mode 100644 index 000000000..4b35fe891 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseCrossFadeBackgroundMusicTag.js @@ -0,0 +1,46 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseCrossFadeBackgroundMusicTag = function (textPlayer, parser, config) { + var tagName = 'bgm.cross'; + parser + .on(`+${tagName}`, function (name, fadeTime) { + AppendCommandBase.call(textPlayer, + tagName, // name + CrossFadeBackgroundMusic, // callback + [name, fadeTime], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'bgm2.cross'; + parser + .on(`+${tagName}`, function (name, fadeTime) { + AppendCommandBase.call(textPlayer, + tagName, // name + CrossFadeBackgroundMusic2, // callback + [name, fadeTime], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +var CrossFadeBackgroundMusic = function (params) { + // this: textPlayer + this.soundManager.crossFadeBackgroundMusic(...params); +} + +var CrossFadeBackgroundMusic2 = function (params) { + // this: textPlayer + this.soundManager.crossFadeBackgroundMusic2(...params); +} + +export default OnParseCrossFadeBackgroundMusicTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseFadeInBackgroundMusicTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseFadeInBackgroundMusicTag.js new file mode 100644 index 000000000..292523daa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseFadeInBackgroundMusicTag.js @@ -0,0 +1,46 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseFadeInBackgroundMusicTag = function (textPlayer, parser, config) { + var tagName = 'bgm.fadein'; + parser + .on(`+${tagName}`, function (time) { + AppendCommandBase.call(textPlayer, + tagName, // name + FadeInBackgroundMusic, // callback + time, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'bgm2.fadein'; + parser + .on(`+${tagName}`, function (time) { + AppendCommandBase.call(textPlayer, + tagName, // name + FadeInBackgroundMusic2, // callback + time, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +var FadeInBackgroundMusic = function (time) { + // this: textPlayer + this.soundManager.fadeInBackgroundMusic(time); +} + +var FadeInBackgroundMusic2 = function (time) { + // this: textPlayer + this.soundManager.fadeInBackgroundMusic2(time); +} + +export default OnParseFadeInBackgroundMusicTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseFadeOutBackgroundMusicTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseFadeOutBackgroundMusicTag.js new file mode 100644 index 000000000..40030b59a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseFadeOutBackgroundMusicTag.js @@ -0,0 +1,48 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseFadeOutBackgroundMusicTag = function (textPlayer, parser, config) { + var tagName = 'bgm.fadeout'; + parser + .on(`+${tagName}`, function (time, isStopped) { + isStopped = (isStopped === 'stop'); + AppendCommandBase.call(textPlayer, + tagName, // name + FadeOutBackgroundMusic, // callback + [time, isStopped], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'bgm2.fadeout'; + parser + .on(`+${tagName}`, function (time, isStopped) { + isStopped = (isStopped === 'stop'); + AppendCommandBase.call(textPlayer, + tagName, // name + FadeOutBackgroundMusic2, // callback + [time, isStopped], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +var FadeOutBackgroundMusic = function (params) { + // this: textPlayer + this.soundManager.fadeOutBackgroundMusic(...params); +} + +var FadeOutBackgroundMusic2 = function (params) { + // this: textPlayer + this.soundManager.fadeOutBackgroundMusic2(...params); +} + +export default OnParseFadeOutBackgroundMusicTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParsePauseBackgroundMusicTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParsePauseBackgroundMusicTag.js new file mode 100644 index 000000000..eb9e12411 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParsePauseBackgroundMusicTag.js @@ -0,0 +1,68 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParsePauseBackgroundMusicTag = function (textPlayer, parser, config) { + var tagName = 'bgm.pause'; + parser + .on(`+${tagName}`, function () { + AppendCommandBase.call(textPlayer, + tagName, // name + PauseBackgroundMusic, // callback + undefined, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + AppendCommandBase.call(textPlayer, + 'bgm.resume', // name + ResumeBackgroundMusic, // callback + undefined, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + + + var tagName = 'bgm2.pause'; + parser + .on(`+${tagName}`, function () { + AppendCommandBase.call(textPlayer, + tagName, // name + PauseBackgroundMusic2, // callback + undefined, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + AppendCommandBase.call(textPlayer, + 'bgm2.resume', // name + ResumeBackgroundMusic2, // callback + undefined, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) +} + +var PauseBackgroundMusic = function () { + // this: textPlayer + this.soundManager.pauseBackgroundMusic(); +} + +var ResumeBackgroundMusic = function () { + // this: textPlayer + this.soundManager.resumeBackgroundMusic(); +} + +var PauseBackgroundMusic2 = function () { + // this: textPlayer + this.soundManager.pauseBackgroundMusic2(); +} + +var ResumeBackgroundMusic2 = function () { + // this: textPlayer + this.soundManager.resumeBackgroundMusic2(); +} + +export default OnParsePauseBackgroundMusicTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParsePlayBackgroundMusicTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParsePlayBackgroundMusicTag.js new file mode 100644 index 000000000..edc7c019e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParsePlayBackgroundMusicTag.js @@ -0,0 +1,80 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParsePlayBackgroundMusicTag = function (textPlayer, parser, config) { + var tagName = 'bgm'; + parser + .on(`+${tagName}`, function (name, fadeInTime) { + AppendCommandBase.call(textPlayer, + tagName, // name + PlayBackgroundMusic, // callback + [name, fadeInTime], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + AppendCommandBase.call(textPlayer, + 'bgm.stop', // name + StopBackgroundMusic, // callback + undefined, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + + + var tagName = 'bgm2'; + parser + .on(`+${tagName}`, function (name, fadeInTime) { + AppendCommandBase.call(textPlayer, + tagName, // name + PlayBackgroundMusic2, // callback + [name, fadeInTime], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + AppendCommandBase.call(textPlayer, + 'bgm2.stop', // name + StopBackgroundMusic2, // callback + undefined, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) +} + +var PlayBackgroundMusic = function (params) { + var name = params[0]; + var fadeInTime = params[1]; + + // this: textPlayer + this.soundManager.playBackgroundMusic(name); + if (fadeInTime) { + this.soundManager.fadeInBackgroundMusic(fadeInTime); + } +} + +var StopBackgroundMusic = function () { + // this: textPlayer + this.soundManager.stopBackgroundMusic(); +} + +var PlayBackgroundMusic2 = function (params) { + var name = params[0]; + var fadeInTime = params[1]; + + // this: textPlayer + this.soundManager.playBackgroundMusic2(name); + if (fadeInTime) { + this.soundManager.fadeInBackgroundMusic2(fadeInTime); + } +} + +var StopBackgroundMusic2 = function () { + // this: textPlayer + this.soundManager.stopBackgroundMusic2(); +} + +export default OnParsePlayBackgroundMusicTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseSetBackgroundMusicVolumeTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseSetBackgroundMusicVolumeTag.js new file mode 100644 index 000000000..d2607e5cf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/backgroundmusic/OnParseSetBackgroundMusicVolumeTag.js @@ -0,0 +1,45 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseSetBackgroundMusicVolumeTag = function (textPlayer, parser, config) { + var tagName = 'bgm.volume'; + parser + .on(`+${tagName}`, function (volume) { + AppendCommandBase.call(textPlayer, + tagName, // name + SetBackgroundMusicVolume, // callback + volume, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'bgm2.volume'; + parser + .on(`+${tagName}`, function (volume) { + AppendCommandBase.call(textPlayer, + tagName, // name + SetBackgroundMusicVolume2, // callback + volume, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +var SetBackgroundMusicVolume = function (volume) { + // this: textPlayer + this.soundManager.setBackgroundMusicVolume(volume); +} + +var SetBackgroundMusicVolume2 = function (volume) { + // this: textPlayer + this.soundManager.setBackgroundMusicVolume2(volume); +} +export default OnParseSetBackgroundMusicVolumeTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseFadeInCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseFadeInCameraTag.js new file mode 100644 index 000000000..20d97243e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseFadeInCameraTag.js @@ -0,0 +1,22 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseFadeInCameraTag = function (textPlayer, parser, config) { + var tagName = 'camera.fadein'; + parser + .on(`+${tagName}`, function (duration, red, green, blue) { + AppendCommandBase.call(textPlayer, + tagName, // name + PlayFadeInEffect, // callback + [duration, red, green, blue], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) +} + +var PlayFadeInEffect = function (params) { + // this: textPlayer + this.targetCamera.fadeIn(...params); +} + +export default OnParseFadeInCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseFadeOutCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseFadeOutCameraTag.js new file mode 100644 index 000000000..29a395949 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseFadeOutCameraTag.js @@ -0,0 +1,22 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseFadeOutCameraTag = function (textPlayer, parser, config) { + var tagName = 'camera.fadeout'; + parser + .on(`+${tagName}`, function (duration, red, green, blue) { + AppendCommandBase.call(textPlayer, + tagName, // name + PlayFadeOutEffect, // callback + [duration, red, green, blue], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) +} + +var PlayFadeOutEffect = function (params) { + // this: textPlayer + this.targetCamera.fadeOut(...params); +} + +export default OnParseFadeOutCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseFlashCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseFlashCameraTag.js new file mode 100644 index 000000000..fd2685491 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseFlashCameraTag.js @@ -0,0 +1,22 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseFlashCameraTag = function (textPlayer, parser, config) { + var tagName = 'camera.flash'; + parser + .on(`+${tagName}`, function (duration, red, green, blue) { + AppendCommandBase.call(textPlayer, + tagName, // name + PlayFlashEffect, // callback + [duration, red, green, blue], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) +} + +var PlayFlashEffect = function (params) { + // this: textPlayer + this.targetCamera.flash(...params); +} + +export default OnParseFlashCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseRotateCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseRotateCameraTag.js new file mode 100644 index 000000000..02e45c816 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseRotateCameraTag.js @@ -0,0 +1,44 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +const DegToRad = Phaser.Math.DegToRad; + +var OnParseRotateCameraTag = function (textPlayer, parser, config) { + var tagName = 'camera.rotate'; + parser + .on(`+${tagName}`, function (value) { + value = DegToRad(value); + AppendCommandBase.call(textPlayer, + tagName, // name + Rotate, // callback + value, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`+${tagName}.to`, function (value, duration, ease) { + value = DegToRad(value); + AppendCommandBase.call(textPlayer, + 'camera.rotate.to', // name + RotateTo, // callback + [value, duration, ease], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) +} + +var Rotate = function (value) { + // this: textPlayer + this.targetCamera.setRotation(value); +} + +var RotateTo = function (params) { + var value = params[0]; + var duration = params[1]; + var ease = params[2]; + + // this: textPlayer + this.targetCamera.rotateTo(value, false, duration, ease); +} + +export default OnParseRotateCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseScrollCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseScrollCameraTag.js new file mode 100644 index 000000000..e1f2f6d9a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseScrollCameraTag.js @@ -0,0 +1,50 @@ +import AppendCommand from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseScrollCameraTag = function (textPlayer, parser, config) { + var tagName = 'camera.scroll'; + parser + .on(`+${tagName}`, function (x, y) { + AppendCommand.call(textPlayer, + tagName, // name + Scroll, // callback + [x, y], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`+${tagName}.to`, function (x, y, duration, ease) { + AppendCommand.call(textPlayer, + 'camera.scroll.to', // name + ScrollTo, // callback + [x, y, duration, ease], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) +} + +var Scroll = function (params) { + // this: textPlayer + this.targetCamera.setScroll(...params); +} + +var ScrollTo = function (params) { + var x = params[0]; + var y = params[1]; + var duration = params[2]; + var ease = params[3]; + + // this: textPlayer + var camera = this.targetCamera; + var xSave = camera.scrollX; + var ySave = camera.scrollY; + camera.setScroll(x, y); + x += camera.centerX; + y += camera.centerY; + camera.setScroll(xSave, ySave); + + // x,y in pan() is the centerX, centerY + camera.pan(x, y, duration, ease); +} + +export default OnParseScrollCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseShakeCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseShakeCameraTag.js new file mode 100644 index 000000000..d4133de6c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseShakeCameraTag.js @@ -0,0 +1,22 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseShakeCameraTag = function (textPlayer, parser, config) { + var tagName = 'camera.shake'; + parser + .on(`+${tagName}`, function (duration, intensity) { + AppendCommandBase.call(textPlayer, + tagName, // name + PlayShakeEffect, // callback + [duration, intensity], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) +} + +var PlayShakeEffect = function (params) { + // this: textPlayer + this.targetCamera.shake(...params); +} + +export default OnParseShakeCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseZoomCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseZoomCameraTag.js new file mode 100644 index 000000000..bfc10a933 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/camera/OnParseZoomCameraTag.js @@ -0,0 +1,36 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseZoomCameraTag = function (textPlayer, parser, config) { + var tagName = 'camera.zoom'; + parser + .on(`+${tagName}`, function (value) { + AppendCommandBase.call(textPlayer, + tagName, // name + Zoom, // callback + value, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`+${tagName}.to`, function (value, duration, ease) { + AppendCommandBase.call(textPlayer, + 'camera.zoom.to', // name + ZoomTo, // callback + [value, duration, ease], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) +} + +var Zoom = function (value) { + // this: textPlayer + this.targetCamera.setZoom(value); +} + +var ZoomTo = function (params) { + // this: textPlayer + this.targetCamera.zoomTo(...params); +} + +export default OnParseZoomCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseContent.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseContent.js new file mode 100644 index 000000000..79ef6acab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseContent.js @@ -0,0 +1,15 @@ +import AppendTextBase from '../../../dynamictext/methods/AppendText.js'; + +var OnParseContent = function (textPlayer, parser, config) { + parser + .on('content', function (content) { + if (parser.contentOutputEnable) { + AppendTextBase.call(textPlayer, content); + } else { + var startTag = `+${parser.lastTagStart}`; + textPlayer.emit(`parser.${startTag}#content`, parser, content); + } + }) +} + +export default OnParseContent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseContentOff.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseContentOff.js new file mode 100644 index 000000000..549579ad4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseContentOff.js @@ -0,0 +1,10 @@ +var OnParseContentOff = function (textPlayer, parser, config) { + var tagName = 'content.off'; + parser + .on(`+${tagName}`, function () { + parser.setContentOutputEnable(false); + parser.skipEvent(); + }) +} + +export default OnParseContentOff; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseContentOn.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseContentOn.js new file mode 100644 index 000000000..4b3d3d5d0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseContentOn.js @@ -0,0 +1,10 @@ +var OnParseContentOn = function (textPlayer, parser, config) { + var tagName = 'content.on'; + parser + .on(`+${tagName}`, function () { + parser.setContentOutputEnable(); + parser.skipEvent(); + }) +} + +export default OnParseContentOn; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseNewLineTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseNewLineTag.js new file mode 100644 index 000000000..66ec2c1a4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParseNewLineTag.js @@ -0,0 +1,15 @@ +import AppendTextBase from '../../../dynamictext/methods/AppendText.js'; + +var OnParseNewLineTag = function (textPlayer, parser, config) { + var tagName = 'r'; + parser + .on(`+${tagName}`, function () { + AppendTextBase.call(textPlayer, '\n'); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +export default OnParseNewLineTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParsePageBreakTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParsePageBreakTag.js new file mode 100644 index 000000000..488746252 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/content/OnParsePageBreakTag.js @@ -0,0 +1,19 @@ +import AppendTextBase from '../../../dynamictext/methods/AppendText.js'; + +var OnParsePageBreakTag = function (textPlayer, parser, config) { + var tagNames = ['pagebreak', 'pb']; + for (var i = 0, cnt = tagNames.length; i < cnt; i++) { + var tagName = tagNames[i]; + parser + .on(`+${tagName}`, function () { + AppendTextBase.call(textPlayer, '\f'); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + } + +} + +export default OnParsePageBreakTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/custom/OnParseCustomTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/custom/OnParseCustomTag.js new file mode 100644 index 000000000..bdccbeeec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/custom/OnParseCustomTag.js @@ -0,0 +1,53 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseCustomTag = function (textPlayer, parser, config) { + parser + .on('start', function () { + textPlayer.emit('parser.start', parser); + }) + .on('+', function (tagName, ...value) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + var startTag = `+${tagName}`; + var param = value; + textPlayer.emit(`parser.${startTag}`, parser, ...value, param); + AppendCommand(textPlayer, startTag, param); + }) + .on('-', function (tagName) { + if (parser.skipEventFlag) { + return; + } + + var endTag = `-${tagName}`; + var param = []; + textPlayer.emit(`parser.${endTag}`, parser, param); + AppendCommand(textPlayer, endTag, param); + }) + .on('complete', function () { + textPlayer.emit('parser.complete', parser); + }) +} + +var FireEvent = function (param, tagName) { + var eventName = `tag.${tagName}`; + // this: textPlayer + if (param == null) { + this.emit(eventName); + } else { + this.emit(eventName, ...param); + } + +} + +var AppendCommand = function (textPlayer, name, param) { + AppendCommandBase.call(textPlayer, + name, // name + FireEvent, // callback + param, // params + textPlayer, // scope + ); +} + +export default OnParseCustomTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/image/OnParseImageTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/image/OnParseImageTag.js new file mode 100644 index 000000000..0f47065c5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/image/OnParseImageTag.js @@ -0,0 +1,24 @@ +import AppendImageBase from '../../../dynamictext/methods/AppendImage.js'; + +var OnParseImageTag = function (textPlayer, parser, config) { + var tagName = 'img'; + parser + .on(`+${tagName}`, function (name) { + var imgData = textPlayer.imageManager.get(name); + AppendImageBase.call(textPlayer, + imgData.key, imgData.frame, + { + width: imgData.width, + hieght: imgData.height, + leftSpace: imgData.left, + rightSpace: imgData.right + } + ) + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +export default OnParseImageTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParseFadeInSoundEffectTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParseFadeInSoundEffectTag.js new file mode 100644 index 000000000..c486d300d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParseFadeInSoundEffectTag.js @@ -0,0 +1,46 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseFadeInSoundEffectTag = function (textPlayer, parser, config) { + var tagName = 'se.fadein' + parser + .on(`+${tagName}`, function (time) { + AppendCommandBase.call(textPlayer, + tagName, // name + FadeInSoundEffect, // callback + time, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'se2.fadein' + parser + .on(`+${tagName}`, function (time) { + AppendCommandBase.call(textPlayer, + tagName, // name + FadeInSoundEffect2, // callback + time, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +var FadeInSoundEffect = function (time) { + // this: textPlayer + this.soundManager.fadeInSoundEffect(time); +} + +var FadeInSoundEffect2 = function (time) { + // this: textPlayer + this.soundManager.fadeInSoundEffect2(time); +} + +export default OnParseFadeInSoundEffectTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParseFadeOutSoundEffectTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParseFadeOutSoundEffectTag.js new file mode 100644 index 000000000..620c11e44 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParseFadeOutSoundEffectTag.js @@ -0,0 +1,47 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseFadeOutSoundEffectTag = function (textPlayer, parser, config) { + var tagName = 'se.fadeout'; + parser + .on(`+${tagName}`, function (time, isStopped) { + isStopped = (isStopped === 'stop'); + AppendCommandBase.call(textPlayer, + tagName, // name + FadeOutSoundEffect, // callback + [time, isStopped], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'se2.fadeout'; + parser + .on(`+${tagName}`, function (time, isStopped) { + isStopped = (isStopped === 'stop'); + AppendCommandBase.call(textPlayer, + tagName, // name + FadeOutSoundEffect2, // callback + [time, isStopped], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +var FadeOutSoundEffect = function (params) { + // this: textPlayer + this.soundManager.fadeOutSoundEffect(...params); +} + +var FadeOutSoundEffect2 = function (params) { + // this: textPlayer + this.soundManager.fadeOutSoundEffect2(...params); +} +export default OnParseFadeOutSoundEffectTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParsePlaySoundEffectTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParsePlaySoundEffectTag.js new file mode 100644 index 000000000..982502e84 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParsePlaySoundEffectTag.js @@ -0,0 +1,64 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParsePlaySoundEffectTag = function (textPlayer, parser, config) { + var tagName = 'se'; + parser + .on(`+${tagName}`, function (name, fadeInTime) { + AppendCommandBase.call(textPlayer, + tagName, // name + PlaySoundEffect, // callback + [name, fadeInTime], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'se2'; + parser + .on(`+${tagName}`, function (name, fadeInTime) { + AppendCommandBase.call(textPlayer, + tagName, // name + PlaySoundEffect2, // callback + [name, fadeInTime], // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +var PlaySoundEffect = function (params) { + if (this.skipSoundEffect) { + return; + } + + var name = params[0]; + var fadeInTime = params[1]; + + this.soundManager.playSoundEffect(name); // this: textPlayer + if (fadeInTime) { + this.soundManager.fadeInSoundEffect(fadeInTime); + } +} + +var PlaySoundEffect2 = function (params) { + if (this.skipSoundEffect) { + return; + } + + var name = params[0]; + var fadeInTime = params[1]; + + this.soundManager.playSoundEffect2(name); // this: textPlayer + if (fadeInTime) { + this.soundManager.fadeInSoundEffect2(fadeInTime); + } +} + +export default OnParsePlaySoundEffectTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParseSetSoundEffectVolumeTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParseSetSoundEffectVolumeTag.js new file mode 100644 index 000000000..9f5496a7a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/soundeffect/OnParseSetSoundEffectVolumeTag.js @@ -0,0 +1,45 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseSetSoundEffectVolumeTag = function (textPlayer, parser, config) { + var tagName = 'se.volume'; + parser + .on(`+${tagName}`, function (volume) { + AppendCommandBase.call(textPlayer, + tagName, // name + SetSoundEffectVolume, // callback + volume, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'se2.volume'; + parser + .on(`+${tagName}`, function (volume) { + AppendCommandBase.call(textPlayer, + tagName, // name + SetSoundEffectVolume2, // callback + volume, // params + textPlayer, // scope + ); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +var SetSoundEffectVolume = function (volume) { + // this: textPlayer + this.soundManager.setSoundEffectVolume(volume, true); +} + +var SetSoundEffectVolume2 = function (volume) { + // this: textPlayer + this.soundManager.setSoundEffectVolume2(volume, true); +} +export default OnParseSetSoundEffectVolumeTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/space/OnParseSpaceTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/space/OnParseSpaceTag.js new file mode 100644 index 000000000..26854135d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/space/OnParseSpaceTag.js @@ -0,0 +1,17 @@ +import AppendSpaceBase from '../../../dynamictext/methods/AppendSpace.js'; + +var OnParseImageTag = function (textPlayer, parser, config) { + var tagName = 'space'; + parser + .on(`+${tagName}`, function (width) { + AppendSpaceBase.call(textPlayer, + width + ) + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +export default OnParseImageTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseAlignTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseAlignTag.js new file mode 100644 index 000000000..17d976684 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseAlignTag.js @@ -0,0 +1,17 @@ +var OnParseAlignTag = function (textPlayer, parser, config) { + var tagName = 'align'; + parser + .on(`+${tagName}`, function (align) { + textPlayer.textStyle.setAlign(align); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + textPlayer.textStyle.setAlign(); + parser.skipEvent(); + }) + .on('complete', function () { + textPlayer.textStyle.setAlign(); + }) +} + +export default OnParseAlignTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseBoldTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseBoldTag.js new file mode 100644 index 000000000..97cd696d5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseBoldTag.js @@ -0,0 +1,17 @@ +var OnParseBoldTag = function (textPlayer, parser, config) { + var tagName = 'b'; + parser + .on('start', function () { + textPlayer.textStyle.setBold(false); + }) + .on(`+${tagName}`, function () { + textPlayer.textStyle.setBold(true); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + textPlayer.textStyle.setBold(false); + parser.skipEvent(); + }) +} + +export default OnParseBoldTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseColorTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseColorTag.js new file mode 100644 index 000000000..a4f9998ab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseColorTag.js @@ -0,0 +1,21 @@ +var OnParseColorTag = function (textPlayer, parser, config) { + var tagName = 'color'; + var defaultColor; + parser + .on('start', function () { + defaultColor = textPlayer.textStyle.color; + }) + .on(`+${tagName}`, function (color) { + textPlayer.textStyle.setColor(color); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + textPlayer.textStyle.setColor(defaultColor); + parser.skipEvent(); + }) + .on('complete', function () { + textPlayer.textStyle.setColor(defaultColor); + }) +} + +export default OnParseColorTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseFontSizeTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseFontSizeTag.js new file mode 100644 index 000000000..5aa12198d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseFontSizeTag.js @@ -0,0 +1,21 @@ +var OnParseFontSizeTag = function (textPlayer, parser, config) { + var tagName = 'size'; + var defaultFontSize; + parser + .on('start', function () { + defaultFontSize = textPlayer.textStyle.fontSize; + }) + .on(`+${tagName}`, function (fontSize) { + textPlayer.textStyle.setFontSize(fontSize); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + textPlayer.textStyle.setFontSize(defaultFontSize); + parser.skipEvent(); + }) + .on('complete', function () { + textPlayer.textStyle.setFontSize(defaultFontSize); + }) +} + +export default OnParseFontSizeTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseItalicTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseItalicTag.js new file mode 100644 index 000000000..2209e9668 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseItalicTag.js @@ -0,0 +1,17 @@ +var OnParseItalicTag = function (textPlayer, parser, config) { + var tagName = 'i'; + parser + .on('start', function () { + textPlayer.textStyle.setItalic(false); + }) + .on(`+${tagName}`, function () { + textPlayer.textStyle.setItalic(true); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + textPlayer.textStyle.setItalic(false); + parser.skipEvent(); + }) +} + +export default OnParseItalicTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseLeftSpaceTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseLeftSpaceTag.js new file mode 100644 index 000000000..4fb8b2062 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseLeftSpaceTag.js @@ -0,0 +1,25 @@ +var OnParseLeftSpaceTag = function (textPlayer, parser, config) { + var tagName = 'left'; + var defaultLeftSpace; + parser + .on('start', function () { + defaultLeftSpace = textPlayer.textStyle.leftSpace; + textPlayer.textStyle.setLeftSpace(0); + }) + .on(`+${tagName}`, function (space) { + if (space === undefined) { + space = defaultLeftSpace; + } + textPlayer.textStyle.setLeftSpace(space); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + textPlayer.textStyle.setLeftSpace(0); + parser.skipEvent(); + }) + .on('complete', function () { + textPlayer.textStyle.setLeftSpace(0); + }) +} + +export default OnParseLeftSpaceTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseOffsetXTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseOffsetXTag.js new file mode 100644 index 000000000..7960f39a6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseOffsetXTag.js @@ -0,0 +1,25 @@ +var OnParseOffsetXTag = function (textPlayer, parser, config) { + var tagName = 'x'; + var defaultOffsetX; + parser + .on('start', function () { + defaultOffsetX = textPlayer.textStyle.offsetY; + textPlayer.textStyle.setOffsetX(0); + }) + .on(`+${tagName}`, function (y) { + if (y === undefined) { + y = defaultOffsetX; + } + textPlayer.textStyle.setOffsetX(y); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + textPlayer.textStyle.setOffsetX(0); + parser.skipEvent(); + }) + .on('complete', function () { + textPlayer.textStyle.setOffsetX(0); + }) +} + +export default OnParseOffsetXTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseOffsetYTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseOffsetYTag.js new file mode 100644 index 000000000..142ceb745 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseOffsetYTag.js @@ -0,0 +1,25 @@ +var OnParseOffsetYTag = function (textPlayer, parser, config) { + var tagName = 'y'; + var defaultOffsetY; + parser + .on('start', function () { + defaultOffsetY = textPlayer.textStyle.offsetY; + textPlayer.textStyle.setOffsetY(0); + }) + .on(`+${tagName}`, function (y) { + if (y === undefined) { + y = defaultOffsetY; + } + textPlayer.textStyle.setOffsetY(y); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + textPlayer.textStyle.setOffsetY(0); + parser.skipEvent(); + }) + .on('complete', function () { + textPlayer.textStyle.setOffsetY(0); + }) +} + +export default OnParseOffsetYTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseRightSpaceTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseRightSpaceTag.js new file mode 100644 index 000000000..1db7d8c10 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseRightSpaceTag.js @@ -0,0 +1,25 @@ +var OnParseRightSpaceTag = function (textPlayer, parser, config) { + var tagName = 'right'; + var defaultRightSpace; + parser + .on('start', function () { + defaultRightSpace = textPlayer.textStyle.rightSpace; + textPlayer.textStyle.setRightSpace(0); + }) + .on(`+${tagName}`, function (space) { + if (space === undefined) { + space = defaultRightSpace; + } + textPlayer.textStyle.setRightSpace(space); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + textPlayer.textStyle.setRightSpace(0); + parser.skipEvent(); + }) + .on('complete', function () { + textPlayer.textStyle.setRightSpace(0); + }) +} + +export default OnParseRightSpaceTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseShadowColorTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseShadowColorTag.js new file mode 100644 index 000000000..0a544502b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseShadowColorTag.js @@ -0,0 +1,25 @@ +var OnParseShadowColorTag = function (textPlayer, parser, config) { + var tagName = 'shadow'; + var defaultShadowColor; + parser + .on('start', function () { + defaultShadowColor = textPlayer.textStyle.shadowColor; + textPlayer.textStyle.setShadowColor(null); + }) + .on(`+${tagName}`, function (color) { + if (color === undefined) { + color = defaultShadowColor; + } + textPlayer.textStyle.setShadowColor(color); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + textPlayer.textStyle.setShadowColor(null); + parser.skipEvent(); + }) + .on('complete', function () { + textPlayer.textStyle.setShadowColor(defaultShadowColor); + }) +} + +export default OnParseShadowColorTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseStrokeColorTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseStrokeColorTag.js new file mode 100644 index 000000000..ffae78b79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/textstyle/OnParseStrokeColorTag.js @@ -0,0 +1,25 @@ +var OnParseStrokeColorTag = function (textPlayer, parser, config) { + var tagName = 'stroke'; + var defaultStroke; + parser + .on('start', function () { + defaultStroke = textPlayer.textStyle.stroke; + textPlayer.textStyle.setStrokeStyle(null); + }) + .on(`+${tagName}`, function (color) { + if (color === undefined) { + color = defaultStroke; + } + textPlayer.textStyle.setStrokeStyle(color); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + textPlayer.textStyle.setStrokeStyle(null); + parser.skipEvent(); + }) + .on('complete', function () { + textPlayer.textStyle.setStrokeStyle(defaultStroke); + }) +} + +export default OnParseStrokeColorTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/typing/OnParseTypingSpeedTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/typing/OnParseTypingSpeedTag.js new file mode 100644 index 000000000..13566c8dc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/typing/OnParseTypingSpeedTag.js @@ -0,0 +1,29 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseTypingSpeedTag = function (textPlayer, parser, config) { + var tagName = 'speed'; + parser + .on(`+${tagName}`, function (speed) { + AppendCommand(textPlayer, speed); + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + AppendCommand(textPlayer, undefined); + parser.skipEvent(); + }) +} + +var SetTypingSpeed = function (speed) { + this.typeWriter.setTypingSpeed(speed); // this: textPlayer +} + +var AppendCommand = function (textPlayer, speed) { + AppendCommandBase.call(textPlayer, + 'speed', // name + SetTypingSpeed, // callback + speed, // params + textPlayer, // scope + ); +} + +export default OnParseTypingSpeedTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/wait/OnParseWaitTag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/wait/OnParseWaitTag.js new file mode 100644 index 000000000..d2c6c6d56 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/parser/wait/OnParseWaitTag.js @@ -0,0 +1,36 @@ +import AppendCommandBase from '../../../dynamictext/methods/AppendCommand.js'; + +var OnParseWaitTag = function (textPlayer, parser, config) { + var tagWait = 'wait'; + var tagClick = 'click'; + parser + .on(`+${tagWait}`, function (name) { + AppendCommand(textPlayer, name); + parser.skipEvent(); + }) + .on(`-${tagWait}`, function () { + parser.skipEvent(); + }) + .on(`+${tagClick}`, function () { // Equal to [wait=click] + AppendCommand(textPlayer, 'click'); + parser.skipEvent(); + }) + .on(`-${tagClick}`, function () { // Equal to [/wait] + parser.skipEvent(); + }) +} + +var Wait = function (name) { + this.typeWriter.wait(name); // this: textPlayer +} + +var AppendCommand = function (textPlayer, name) { + AppendCommandBase.call(textPlayer, + 'wait', // name + Wait, // callback + name, // params + textPlayer, // scope + ); +} + +export default OnParseWaitTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/FadeOutPage.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/FadeOutPage.js new file mode 100644 index 000000000..00e834e04 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/FadeOutPage.js @@ -0,0 +1,29 @@ +const PageFadeOutCompleteEvent = 'page.fadeout'; + +var FadeOutPage = function () { + if (!this.fadeOutPageCallback || !this.children) { + this.emit(PageFadeOutCompleteEvent); + return this; + } + + var renderableChildren = this.children.filter(function (child) { return child.renderable }); + var waitObject = this.fadeOutPageCallback(renderableChildren, this.fadeOutPageDuration); + if (!waitObject) { + this.emit(PageFadeOutCompleteEvent); + } else if (waitObject.once) { + waitObject.once('complete', function () { + this.emit(PageFadeOutCompleteEvent); + }, this); + } else if (waitObject.then) { + var self = this; + waitObject.then(function () { + self.emit(PageFadeOutCompleteEvent); + }) + } else { + this.emit(PageFadeOutCompleteEvent); + } + + return this; +} + +export default FadeOutPage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Methods.js new file mode 100644 index 000000000..82a652b34 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Methods.js @@ -0,0 +1,37 @@ +import TypingSpeedMethods from './TypingSpeedMethods.js'; +import FadeOutPage from './FadeOutPage.js'; +import Start from './Start.js'; +import Typing from './Typing.js'; +import Pause from './Pause.js'; +import Resume from './Resume.js'; +import PauseTyping from './PauseTyping.js'; +import ResumeTyping from './ResumeTyping.js'; +import Wait from './Wait.js'; +import SetIgnoreWait from './SetIgnoreWait.js'; +import SetSkipSpaceEnable from './SetSkipSpaceEnable.js'; +import SetSkipTypingAnimation from './SetSkipTypingAnimation.js'; +import SetSkipSoundEffect from './SetSkipSoundEffect.js'; +import SkipCurrentTypingDelay from './SkipCurrentTypingDelay.js'; + +var Methods = { + fadeOutPage: FadeOutPage, + start: Start, + typing: Typing, + pause: Pause, + resume: Resume, + pauseTyping: PauseTyping, + resumeTyping: ResumeTyping, + wait: Wait, + setIgnoreWait: SetIgnoreWait, + setSkipSpaceEnable: SetSkipSpaceEnable, + setSkipTypingAnimation: SetSkipTypingAnimation, + setSkipSoundEffect: SetSkipSoundEffect, + skipCurrentTypingDelay: SkipCurrentTypingDelay, +} + +Object.assign( + Methods, + TypingSpeedMethods +); + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Pause.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Pause.js new file mode 100644 index 000000000..4b9cacaaa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Pause.js @@ -0,0 +1,7 @@ +var Pause = function () { + // Pause typing timer and animation progresses + this.timeline.pause(); + return this; +} + +export default Pause; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/PauseTyping.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/PauseTyping.js new file mode 100644 index 000000000..db8358e51 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/PauseTyping.js @@ -0,0 +1,17 @@ +var PauseTyping = function () { + // Already in typingPaused state + if (this.isTypingPaused) { + return this; + } + + if (this.typingTimer) { // Pause when typing timer is counting + this.typingTimer.pause(); + this.isTypingPaused = true; + } else if (this.inTypingProcessLoop) { // Pause in loop of typing(), by tag + this.inTypingProcessLoop = false; + this.isTypingPaused = true; + } + return this; +} + +export default PauseTyping; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Resume.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Resume.js new file mode 100644 index 000000000..37fc417c1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Resume.js @@ -0,0 +1,7 @@ +var Resume = function () { + // Resume typing timer and animation progresses + this.timeline.resume(); + return this; +} + +export default Resume; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/ResumeTyping.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/ResumeTyping.js new file mode 100644 index 000000000..a13625393 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/ResumeTyping.js @@ -0,0 +1,21 @@ +var ResumeTyping = function (offsetTime) { + // Already not in typingPaused state + if (!this.isTypingPaused) { + return this; + } + if (offsetTime === undefined) { + offsetTime = 0; + } + + if (this.typingTimer) { // Pause when typing timer is paused + this.isTypingPaused = false; + this.typingTimer.resume(); + this.typingTimer.remainder += offsetTime; + } else if (this.isTypingPaused) { // Resume paused by tag + this.isTypingPaused = false; + this.typing(offsetTime); + } + return this; +} + +export default ResumeTyping; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetIgnoreWait.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetIgnoreWait.js new file mode 100644 index 000000000..bc2ef6c21 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetIgnoreWait.js @@ -0,0 +1,9 @@ +var SetIgnoreWait = function (value) { + if (value === undefined) { + value = true; + } + this.ignoreWait = value; + return this; +} + +export default SetIgnoreWait; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetSkipSoundEffect.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetSkipSoundEffect.js new file mode 100644 index 000000000..d4da2ebd3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetSkipSoundEffect.js @@ -0,0 +1,16 @@ +var SetSkipSoundEffect = function (value) { + if (value === undefined) { + value = true; + } + this.skipSoundEffect = value; + + if (value) { + var soundManager = this.textPlayer._soundManager; + if (soundManager) { + soundManager.fadeOutAllSoundEffects(100, true); + } + } + return this; +} + +export default SetSkipSoundEffect; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetSkipSpaceEnable.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetSkipSpaceEnable.js new file mode 100644 index 000000000..75a8a01cc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetSkipSpaceEnable.js @@ -0,0 +1,9 @@ +var SetSkipSpaceEnable = function (enable) { + if (enable === undefined) { + enable = true; + } + this.skipSpaceEnable = enable; + return this; +} + +export default SetSkipSpaceEnable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetSkipTypingAnimation.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetSkipTypingAnimation.js new file mode 100644 index 000000000..981c55e5e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SetSkipTypingAnimation.js @@ -0,0 +1,19 @@ +import { TypingAnimationTimerType, } from './TimerTypes.js'; + +var SetSkipTypingAnimation = function (value) { + if (value === undefined) { + value = true; + } + this.skipTypingAnimation = value; + + if (value) { + // Skip current playing typing-animation + var timers = this.timeline.getTimers(TypingAnimationTimerType); + for (var i = 0, cnt = timers.length; i < cnt; i++) { + timers[i].seek(1); + } + } + return this; +} + +export default SetSkipTypingAnimation; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SkipCurrentTypingDelay.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SkipCurrentTypingDelay.js new file mode 100644 index 000000000..81d8f5432 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/SkipCurrentTypingDelay.js @@ -0,0 +1,8 @@ +var SkipCurrentTypingDelay = function () { + if (this.typingTimer) { + this.typingTimer.seek(1); + } + return this; +} + +export default SkipCurrentTypingDelay; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Start.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Start.js new file mode 100644 index 000000000..1033ed7ec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Start.js @@ -0,0 +1,16 @@ +import { WaitComplete } from '../../../../utils/promise/WaitEvent.js'; + +var Start = function (children) { + this.children = children; + this.index = 0; + this.isPageTyping = true; + + if (this.onTypeStart) { + this.onTypeStart(children); + } + this.typing(); + + return WaitComplete(this); // Promise +} + +export default Start; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/TimerTypes.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/TimerTypes.js new file mode 100644 index 000000000..a1f6a536a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/TimerTypes.js @@ -0,0 +1,7 @@ +const TypingDelayTimerType = 'delay'; +const TypingAnimationTimerType = 'anim'; + +export { + TypingAnimationTimerType, + TypingDelayTimerType +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/TypeWriter.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/TypeWriter.js new file mode 100644 index 000000000..6017db03c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/TypeWriter.js @@ -0,0 +1,120 @@ +import EventEmitterMethods from '../../../../utils/eventemitter/EventEmitterMethods.js'; +import Methods from './Methods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class TypeWriter { + constructor(textPlayer, config) { + this.setEventEmitter(); + this.textPlayer = textPlayer; + this.isPageTyping = false; + this.typingTimer = undefined; // Typing delay + this.pauseTypingTimer = undefined; // Wait time + this.inTypingProcessLoop = false; // Used in this.typing() + this.isTypingPaused = false; // Used in this.wait(), this.pauseTyping(), this.resumeTyping() + this.setIgnoreWait(false); + this.setSkipTypingAnimation(false); + + this.setTypingStartCallback(GetValue(config, 'onTypingStart', SetChildrenInvisible)); + this.setDefaultTypingSpeed(GetValue(config, 'speed', 250)); + this.setTypingSpeed(); + this.setSkipSpaceEnable(GetValue(config, 'skipSpace', false)); + this.setAnimationConfig(GetValue(config, 'animation', undefined)); + this.setMinSizeEnable(GetValue(config, 'minSizeEnable', false)); + + this.setFadeOutPageCallback(GetValue(config, 'fadeOutPage')); + + } + + destroy() { + this.destroyEventEmitter(); + + this.textPlayer = undefined; + + this.typingTimer = undefined; + + this.pauseTypingTimer = undefined; + + this.onTypeStart = undefined; + + this.animationConfig = undefined; + } + + get timeline() { + return this.textPlayer.timeline; + } + + setTypingStartCallback(callback) { + this.onTypeStart = callback; + return this; + } + + setAnimationConfig(config) { + if (!config) { + config = {}; + } + + if (!config.hasOwnProperty('duration')) { + config.duration = 0; + } + + if (!config.hasOwnProperty('onStart')) { + // Apply default onStart callback + config.onStart = SetChildVisible; + } + + this.animationConfig = config; + return this; + } + + setFadeOutPageCallback(callback) { + this.fadeOutPageCallback = callback; + return this; + } + + setMinSizeEnable(enable) { + if (enable === undefined) { + enable = true; + } + + this.minSizeEnable = enable; + return this; + } + + getNextChild() { + var child = this.nextChild; + this.index = Math.min(this.index + 1, this.children.length); // Point to next child + this._nextChild = undefined; + return child; + } + + get nextChild() { + if (!this._nextChild) { + this._nextChild = this.children[this.index]; + } + return this._nextChild; + } +} + +var SetChildVisible = function (child) { + if (child.setVisible) { + child.setVisible(); + } +} + +var SetChildrenInvisible = function (children) { + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + if (child.setVisible) { + child.setVisible(false); + } + } +} + +Object.assign( + TypeWriter.prototype, + EventEmitterMethods, + Methods, +); + +export default TypeWriter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Typing.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Typing.js new file mode 100644 index 000000000..405be5f5d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Typing.js @@ -0,0 +1,94 @@ +import { IsCommand, IsSpaceChar } from '../../dynamictext/bob/Types.js'; +import { TypingDelayTimerType, TypingAnimationTimerType, } from './TimerTypes.js'; + +var Typing = function (offsetTime) { + if (offsetTime === undefined) { + offsetTime = 0; + } + + var delay = 0; + this.inTypingProcessLoop = true; + while (this.inTypingProcessLoop) { + var child = this.getNextChild(); + if (!child) { + if (this.timeline.isRunning) { + // Wait until last animationConfig is end + this.timeline.once('complete', function () { + this.isPageTyping = false; + this.emit('complete'); + }, this); + } else { + this.isPageTyping = false; + this.emit('complete'); + } + break; // Leave this typing loop + } + + if (child.renderable) { + // Typing this char + var animationConfig = this.animationConfig; + if (animationConfig.duration > 0) { + var animationTimer = this.timeline.addTimer({ + name: TypingAnimationTimerType, + target: child, + duration: animationConfig.duration, + yoyo: animationConfig.yoyo, + onStart: animationConfig.onStart, + onProgress: animationConfig.onProgress, + onComplete: animationConfig.onComplete, + }) + if (this.skipTypingAnimation) { + animationTimer.seek(1); + } + } else { // No animationConfig, only invoke onStart callback + if (animationConfig.onStart) { + animationConfig.onStart(child, 0); + } + } + + // Set to min size + if (this.minSizeEnable) { + this.textPlayer.setToMinSize(); + } + + this.textPlayer.emit('typing', child); + + var nextChild = this.nextChild; + if (nextChild) { + if (this.skipSpaceEnable && IsSpaceChar(nextChild)) { + // Don't have delay when typing a space character + } else { + delay += (this.speed + offsetTime); + offsetTime = 0; + if (delay > 0) { + // Process next character later + this.typingTimer = this.timeline.addTimer({ + name: TypingDelayTimerType, + target: this, + duration: delay, + onComplete: function (target, t, timer) { + target.typingTimer = undefined; + Typing.call(target, timer.remainder); + } + }) + break; // Leave this typing loop + } + } + } + // Process next child + } else if (IsCommand(child)) { + child.exec(); + // Process next child + } + + } + + // Set to min size + if (this.minSizeEnable) { + this.textPlayer.setToMinSize(); + } + + this.inTypingProcessLoop = false; +} + +export default Typing; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/TypingSpeedMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/TypingSpeedMethods.js new file mode 100644 index 000000000..41649cb86 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/TypingSpeedMethods.js @@ -0,0 +1,14 @@ +export default { + setDefaultTypingSpeed(speed) { + this.defaultSpeed = speed; + return this; + }, + + setTypingSpeed(speed) { + if (speed === undefined) { + speed = this.defaultSpeed; + } + this.speed = speed; + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Wait.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Wait.js new file mode 100644 index 000000000..9af94caf1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/dynamictext/textplayer/typewriter/Wait.js @@ -0,0 +1,15 @@ +import WaitMultiple from '../methods/utils/wait/WaitMultiple.js'; + +var Wait = function (name) { + // Already in typingPaused state, or ignore any wait + if (this.ignoreWait) { + return this; + } + + this.pauseTyping(); + WaitMultiple(this.textPlayer, name, this.resumeTyping, [], this); + + return this; +} + +export default Wait; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/layer/layermanager/LayerManager.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/layer/layermanager/LayerManager.d.ts new file mode 100644 index 000000000..c4cccea02 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/layer/layermanager/LayerManager.d.ts @@ -0,0 +1,31 @@ +import GOManager from '../../../utils/gameobject/gomanager/GOManager'; + +export default LayerManager; + +declare namespace LayerManager { + interface IConfig { + layers?: string[]; + + createGameObject?: GOManager.CreateGameObjectCallbackType, + } +} + +declare class LayerManager extends GOManager { + constructor( + scene: Phaser.Scene, + config?: LayerManager.IConfig + ) + constructor( + scene: Phaser.Scene, + config?: string[] + ) + + getLayer(name: string): Phaser.GameObjects.Layer; + + getLayers(out?: Phaser.GameObjects.GameObject[]): Phaser.GameObjects.Layer[]; + + addToLayer( + name: string, + gameObject: Phaser.GameObjects.GameObject + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/layer/layermanager/LayerManager.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/layer/layermanager/LayerManager.js new file mode 100644 index 000000000..7b2154b3f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/layer/layermanager/LayerManager.js @@ -0,0 +1,90 @@ +import GOManager from '../../../utils/gameobject/gomanager/GOManager.js'; +import SortGameObjectsByDepth from '../../../utils/system/SortGameObjectsByDepth.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class LayerManager extends GOManager { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } else if (Array.isArray(config)) { + config = { + layers: config + } + } + + if (!config.hasOwnProperty('fade')) { + config.fade = 0; + } + + config.viewportCoordinate = false; + + super(scene, config); + + var initLayers = GetValue(config, 'layers'); + if (initLayers) { + for (var i = 0, cnt = initLayers.length; i < cnt; i++) { + this.add(initLayers[i]); + } + } + } + + setCreateGameObjectCallback(callback, scope) { + if (!callback) { + callback = CreateLayer; + } + super.setCreateGameObjectCallback(callback, scope); + return this; + } + + // Override + addGO(name, gameObject) { + super.addGO(name, gameObject); + gameObject.name = name; + + return this; + } + + // New methods + getLayer(name) { + return this.getGO(name); + } + + getLayers(out) { + if (out === undefined) { + out = []; + } + this.forEachGO(function (gameObject) { + out.push(gameObject); + }) + SortGameObjectsByDepth(out, false); + return out; + } + + addToLayer(name, gameObject) { + var layer = this.getGO(name); + if (!layer) { + console.warn(`Can't get layer "${name}"`); + return; + } + + if (gameObject.isRexContainerLite) { + gameObject.addToLayer(layer); + } else { + layer.add(gameObject); + } + + return this; + } +} + +var CreateLayer = function (scene, depth) { + var layer = scene.add.layer(); + if (depth !== undefined) { + layer.setDepth(depth); + } + return layer; +} + + +export default LayerManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/.eslintrc.yml b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/.eslintrc.yml new file mode 100644 index 000000000..9d17f9ae5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/.eslintrc.yml @@ -0,0 +1,30 @@ +extends: + - eslint:recommended + - plugin:@typescript-eslint/eslint-recommended + - plugin:@typescript-eslint/recommended + - plugin:@typescript-eslint/recommended-requiring-type-checking + - plugin:prettier/recommended + - prettier/@typescript-eslint +plugins: + - '@typescript-eslint' +parser: '@typescript-eslint/parser' +parserOptions: + sourceType: module + ecmaVersion: 2020 + project: ./tsconfig.json +rules: + prettier/prettier: + - error + - singleQuote: true + '@typescript-eslint/camelcase': warn + '@typescript-eslint/no-use-before-define': off + no-empty-function: off + '@typescript-eslint/no-empty-function': + - error + - allow: + - constructors + 'no-fallthrough': warn + '@typescript-eslint/unbound-method': off + 'no-inner-declarations': off + '@typescript-eslint/class-name-casing': warn + 'prefer-const': warn diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/CHANGELOG.md b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/CHANGELOG.md new file mode 100644 index 000000000..be8d7042b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/CHANGELOG.md @@ -0,0 +1,73 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [4-r.4] - 2021-12-09 + +### Fixed + +* Fix useless void 0. +* Fix a warning when `SegmentType` could not be obtained when loading motion. +* Fix return correct error values for out-of-index arguments in cubismjson by [@cocor-au-lait](https://github.com/cocor-au-lait). +* Fix a bug that motions currently played do not fade out when play a motion. + + +## [4-r.3] - 2021-06-10 + +### Fixed + +* Fix motion event time value from Int to Float. + + +## [4-r.3-beta.1] - 2021-05-13 + +### Added + +* Implement a function to get the correct value when the time axis of the Bezier handle cannot be linear. + + +## [4-r.2] - 2021-03-09 + +### Fixed + +* Fix implementation of `iterator#increment` in `csmmap` and `csmvector`. +* Fix delay in starting fade-out for expressions. +* Fix Physics input reflect flag on evaluate. +* Fix reference size of model matrix. +* Fix `Int` to `Float` when getting `PhysicsSettings.Vertices.Radius` in `physics3.json` parsing. + * **[INFO]** This fix may change the behavior of the physics operations. + The behavior changes if the value of `PhysicsSettings.Vertices.Radius` in `physics3.json` is less than `1.0`. + If you want to return to the behavior before Cubism SDK for Web R1, + change the value of the corresponding `PhysicsSettings.Vertices.Radius` to `0`. + * This fix is related to fix applied to `Cubism Editor 4.0.05 beta1` and later. + Please see [Cubism Editor Changelog](https://docs.live2d.com/cubism-editor-manual/updates4/). + * **Fix physics and scene blending settings where the length of the pendulum would be converted to an integer when displayed.** + +### Changed + +* Rename the function name that handles seconds from `Time` to `Seconds`. +* Avoiding needless namespace syntax to simplify imports by [@cocor-au-lait](https://github.com/cocor-au-lait) + + +## [4-r.1] - 2020-01-30 + +### Added + +* Add `.editorconfig`, `.gitattributes` and `.gitignore`. +* Add document `README.md` and `CHANGELOG.md`. +* Add `package.json` for development and build. +* Add Prettier and ESLint for format and check code quolity. + +### Changed + +* Move source files to `/src` directory. +* Reformat code using Prettier and ESLint. + + +[4-r.4]: https://github.com/Live2D/CubismWebFramework/compare/4-r.3...4-r.4 +[4-r.3]: https://github.com/Live2D/CubismWebFramework/compare/4-r.3-beta.1...4-r.3 +[4-r.3-beta.1]: https://github.com/Live2D/CubismWebFramework/compare/4-r.2...4-r.3-beta.1 +[4-r.2]: https://github.com/Live2D/CubismWebFramework/compare/4-r.1...4-r.2 +[4-r.1]: https://github.com/Live2D/CubismWebFramework/compare/ce2585a919ac6e99f64dd468933772c6f1abbcc7...4-r.1 diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/LICENSE.md b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/LICENSE.md new file mode 100644 index 000000000..bd0be75fc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/LICENSE.md @@ -0,0 +1,45 @@ +## Definitions + +### Live2D Cubism Components + +Cubism Web Framework is included in Live2D Cubism Components. + +Cubism Web Framework は Live2D Cubism Components に含まれます。 + +Cubism Web Framework 包括在 Live2D Cubism Components 中。 + +## Cubism SDK Release License + +*All business* users must obtain a Cubism SDK Release License. "Business" means an entity with the annual gross revenue more than ten million (10,000,000) JPY for the most recent fiscal year. + +* [Cubism SDK Release License](https://www.live2d.com/en/download/cubism-sdk/release-license/) + +直近会計年度の売上高が 1000 万円以上の事業者様がご利用になる場合は、Cubism SDK リリースライセンス(出版許諾契約)に同意していただく必要がございます。 + +* [Cubism SDK リリースライセンス](https://www.live2d.com/ja/download/cubism-sdk/release-license/) + +如果您的企业在最近一个会计年度的销售额达到或超过1000万日元,您必须得到Cubism SDK的出版授权许可(出版许可协议)。 + +* [Cubism SDK发行许可证](https://www.live2d.com/zh-CHS/download/cubism-sdk/release-license/) + +## Live2D Open Software License + +Live2D Cubism Components is available under Live2D Open Software License. + +* [Live2D Open Software License](https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html) +* [Live2D Open Software 使用許諾契約書](https://www.live2d.com/eula/live2d-open-software-license-agreement_jp.html) +* [Live2D Open Software 使用授权协议](https://www.live2d.com/eula/live2d-open-software-license-agreement_cn.html) + + +## Live2D Proprietary Software License + +Live2D Cubism Core is available under Live2D Proprietary Software License. + +* [Live2D Proprietary Software License Agreement](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html) +* [Live2D Proprietary Software 使用許諾契約書](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_jp.html) +* [Live2D Proprietary Software 使用授权协议](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_cn.html) + + +--- + +Please contact us from [here](https://www.live2d.jp/contact/) for more license information. diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/README.md b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/README.md new file mode 100644 index 000000000..5e1d4c4ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/README.md @@ -0,0 +1,143 @@ +# Cubism Web Framework + +Live2D Cubism 4 Editor で出力したモデルをアプリケーションで利用するためのフレームワークです。 + +モデルを表示、操作するための各種機能を提供します。 +モデルをロードするには Live2D Cubism Core ライブラリと組み合わせて使用します。 + +ビルドを行うことで、ブラウザで利用可能な JavaScript ライブラリとして利用することができます。 + + +## ライセンス + +本 SDK を使用する前に、[ライセンス](LICENSE.md)をご確認ください。 + + +## 開発環境 + +### Node.js + +* 17.2.0 +* 16.13.1 +* 14.18.2 +* 12.22.7 + +### TypeScript + +4.5.2 + + +## 開発環境構築 + +1. [Node.js] と [Visual Studio Code] をインストールします +1. Visual Studio Code で本プロジェクトを開き、推奨拡張機能をインストールします + * 拡張機能タブから `@recommended` と入力することで確認できます +1. コマンドパレット(*View > Command Palette...*)で `>Tasks: Run Task` を入力してタスク一覧を表示します +1. `npm: install` を選択して依存パッケージのダウンロードを行います + +コマンドパレットのタスク一覧から各種コマンドを実行することができます。 + +NOTE: デバック用の設定は、`.vscode/tasks.json` に記述しています。 + +## タスク一覧 + +### `npm: build` + +ソースファイルのビルドを行い、`dist` ディレクトリに出力します。 + +`tsconfig.json` を編集することで設定内容を変更できます。 + +### `npm: test` + +TypeScript の型チェックテストを行います。 + +`tsconfig.json` を編集することで設定内容を変更できます。 + +### `npm: lint` + +`src` ディレクトリ内の TypeScript ファイルの静的解析を行います。 + +`.eslintrc.yml` を編集することで設定内容を変更できます。 + +### `npm: lint:fix` + +`src` ディレクトリ内の TypeScript ファイルの静的解析及び自動修正を行います。 + +`.eslintrc.yml` を編集することで設定内容を変更できます。 + +### `npm: clean` + +ビルド成果物ディレクトリ(`dist`)を削除します。 + + +## コンポーネント + +### effect + +自動まばたきやリップシンクなど、モデルに対してモーション情報をエフェクト的に付加する機能を提供します。 + +### id + +モデルに設定されたパラメータ名・パーツ名・Drawable名を独自の型で管理する機能を提供します。 + +### math + +行列計算やベクトル計算など、モデルの操作や描画に必要な算術演算の機能を提供します。 + +### model + +モデルを取り扱うための各種機能(生成、更新、破棄)を提供します。 + +### motion + +モデルにモーションデータを適用するための各種機能(モーション再生、パラメータブレンド)を提供します。 + +### physics + +モデルに物理演算による変形操作を適用するための機能を提供します。 + +### rendering + +モデルを描画するためのグラフィックス命令を実装したレンダラを提供します。 + +### type + +フレームワーク内で使用する型定義を提供します。 + +### utils + +JSONパーサーやログ出力などのユーティリティ機能を提供します。 + + +## Live2D Cubism Core for Web + +当リポジトリには Cubism Core for Web は同梱されていません。 + +[Cubism SDK for Web] からダウンロードしてください。 + +[Cubism SDK for Web]: https://www.live2d.com/download/cubism-sdk/download-web/ + + +## サンプル + +標準的なアプリケーションの実装例は [CubismWebSamples] を参照ください。 + +[CubismWebSamples]: https://github.com/Live2D/CubismWebSamples + + +## マニュアル + +[Cubism SDK Manual](https://docs.live2d.com/cubism-sdk-manual/top/) + + +## 変更履歴 + +当リポジトリの変更履歴については [CHANGELOG.md](CHANGELOG.md) を参照ください。 + + +## コミュニティ + +ユーザー同士でCubism SDKの活用方法の提案や質問をしたい場合は、是非コミュニティをご活用ください。 + +- [Live2D 公式コミュニティ](https://creatorsforum.live2d.com/) +- [Live2D community(English)](http://community.live2d.com/) diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/package-lock.json b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/package-lock.json new file mode 100644 index 000000000..898d14ef1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/package-lock.json @@ -0,0 +1,1157 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", + "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.18.0.tgz", + "integrity": "sha512-kuO8WQjV+RCZvAXVRJfXWiJ8iYEtfHlKgcqqqXg9uUkIolEHuUaMmm8/lcO4xwCOtaw6mY0gStn2Lg4/eUXXYQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "2.18.0", + "eslint-utils": "^1.4.3", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.18.0.tgz", + "integrity": "sha512-J6MopKPHuJYmQUkANLip7g9I82ZLe1naCbxZZW3O2sIxTiq/9YYoOELEKY7oPg0hJ0V/AQ225h2z0Yp+RRMXhw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.18.0", + "eslint-scope": "^5.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.18.0.tgz", + "integrity": "sha512-SJJPxFMEYEWkM6pGfcnjLU+NJIPo+Ko1QrCBL+i0+zV30ggLD90huEmMMhKLHBpESWy9lVEeWlQibweNQzyc+A==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "2.18.0", + "@typescript-eslint/typescript-estree": "2.18.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.18.0.tgz", + "integrity": "sha512-gVHylf7FDb8VSi2ypFuEL3hOtoC4HkZZ5dOjXvVjoyKdRrvXAOPSzpNRnKMfaUUEiSLP8UF9j9X9EDLxC0lfZg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^6.3.0", + "tsutils": "^3.17.1" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-jsx": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "dependencies": { + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + } + } + }, + "ansi-escapes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz", + "integrity": "sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, + "eslint-plugin-prettier": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz", + "integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "espree": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", + "dev": true, + "requires": { + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", + "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz", + "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.3.tgz", + "integrity": "sha512-+OiOVeVydu4hnCGLCSX+wedovR/Yzskv9BFqUNNKq9uU2qg7LCcCo3R86S2E7WLo0y/x2pnEZfZe1CoYnORUAw==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^2.4.2", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.2.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "regexpp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", + "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.1.tgz", + "integrity": "sha512-IQ4ikL8SjBiEDZfk+DFVwqRK8md24RWMEJkdSlgNLkyyAImcjf8SWvU1qFMDOb4igBClbTQ/ugPqXcRwdFTxZw==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + } + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typescript": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", + "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + } + } +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/package.json b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/package.json new file mode 100644 index 000000000..0625a0089 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/package.json @@ -0,0 +1,20 @@ +{ + "private": true, + "scripts": { + "build": "tsc", + "test": "tsc --noEmit", + "lint": "eslint src --ext .ts", + "lint:fix": "eslint src --ext .ts --fix", + "clean": "rimraf dist" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^2.18.0", + "@typescript-eslint/parser": "^2.18.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^6.10.0", + "eslint-plugin-prettier": "^3.1.2", + "prettier": "^1.19.1", + "rimraf": "^3.0.1", + "typescript": "^3.7.5" + } +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/cubismdefaultparameterid.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/cubismdefaultparameterid.ts new file mode 100644 index 000000000..1d716d59b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/cubismdefaultparameterid.ts @@ -0,0 +1,118 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * @brief パラメータIDのデフォルト値を保持する定数
    + * デフォルト値の仕様は以下のマニュアルに基づく
    + * https://docs.live2d.com/cubism-editor-manual/standard-parametor-list/ + */ +export const CubismDefaultParameterId = Object.freeze>({ + // パーツID + HitAreaPrefix: 'HitArea', + HitAreaHead: 'Head', + HitAreaBody: 'Body', + PartsIdCore: 'Parts01Core', + PartsArmPrefix: 'Parts01Arm_', + PartsArmLPrefix: 'Parts01ArmL_', + PartsArmRPrefix: 'Parts01ArmR_', + // パラメータID + ParamAngleX: 'ParamAngleX', + ParamAngleY: 'ParamAngleY', + ParamAngleZ: 'ParamAngleZ', + ParamEyeLOpen: 'ParamEyeLOpen', + ParamEyeLSmile: 'ParamEyeLSmile', + ParamEyeROpen: 'ParamEyeROpen', + ParamEyeRSmile: 'ParamEyeRSmile', + ParamEyeBallX: 'ParamEyeBallX', + ParamEyeBallY: 'ParamEyeBallY', + ParamEyeBallForm: 'ParamEyeBallForm', + ParamBrowLY: 'ParamBrowLY', + ParamBrowRY: 'ParamBrowRY', + ParamBrowLX: 'ParamBrowLX', + ParamBrowRX: 'ParamBrowRX', + ParamBrowLAngle: 'ParamBrowLAngle', + ParamBrowRAngle: 'ParamBrowRAngle', + ParamBrowLForm: 'ParamBrowLForm', + ParamBrowRForm: 'ParamBrowRForm', + ParamMouthForm: 'ParamMouthForm', + ParamMouthOpenY: 'ParamMouthOpenY', + ParamCheek: 'ParamCheek', + ParamBodyAngleX: 'ParamBodyAngleX', + ParamBodyAngleY: 'ParamBodyAngleY', + ParamBodyAngleZ: 'ParamBodyAngleZ', + ParamBreath: 'ParamBreath', + ParamArmLA: 'ParamArmLA', + ParamArmRA: 'ParamArmRA', + ParamArmLB: 'ParamArmLB', + ParamArmRB: 'ParamArmRB', + ParamHandL: 'ParamHandL', + ParamHandR: 'ParamHandR', + ParamHairFront: 'ParamHairFront', + ParamHairSide: 'ParamHairSide', + ParamHairBack: 'ParamHairBack', + ParamHairFluffy: 'ParamHairFluffy', + ParamShoulderY: 'ParamShoulderY', + ParamBustX: 'ParamBustX', + ParamBustY: 'ParamBustY', + ParamBaseX: 'ParamBaseX', + ParamBaseY: 'ParamBaseY', + ParamNONE: 'NONE:' +}); + +// Namespace definition for compatibility. +import * as $ from './cubismdefaultparameterid'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const HitAreaBody = $.CubismDefaultParameterId.HitAreaBody; + export const HitAreaHead = $.CubismDefaultParameterId.HitAreaHead; + export const HitAreaPrefix = $.CubismDefaultParameterId.HitAreaPrefix; + export const ParamAngleX = $.CubismDefaultParameterId.ParamAngleX; + export const ParamAngleY = $.CubismDefaultParameterId.ParamAngleY; + export const ParamAngleZ = $.CubismDefaultParameterId.ParamAngleZ; + export const ParamArmLA = $.CubismDefaultParameterId.ParamArmLA; + export const ParamArmLB = $.CubismDefaultParameterId.ParamArmLB; + export const ParamArmRA = $.CubismDefaultParameterId.ParamArmRA; + export const ParamArmRB = $.CubismDefaultParameterId.ParamArmRB; + export const ParamBaseX = $.CubismDefaultParameterId.ParamBaseX; + export const ParamBaseY = $.CubismDefaultParameterId.ParamBaseY; + export const ParamBodyAngleX = $.CubismDefaultParameterId.ParamBodyAngleX; + export const ParamBodyAngleY = $.CubismDefaultParameterId.ParamBodyAngleY; + export const ParamBodyAngleZ = $.CubismDefaultParameterId.ParamBodyAngleZ; + export const ParamBreath = $.CubismDefaultParameterId.ParamBreath; + export const ParamBrowLAngle = $.CubismDefaultParameterId.ParamBrowLAngle; + export const ParamBrowLForm = $.CubismDefaultParameterId.ParamBrowLForm; + export const ParamBrowLX = $.CubismDefaultParameterId.ParamBrowLX; + export const ParamBrowLY = $.CubismDefaultParameterId.ParamBrowLY; + export const ParamBrowRAngle = $.CubismDefaultParameterId.ParamBrowRAngle; + export const ParamBrowRForm = $.CubismDefaultParameterId.ParamBrowRForm; + export const ParamBrowRX = $.CubismDefaultParameterId.ParamBrowRX; + export const ParamBrowRY = $.CubismDefaultParameterId.ParamBrowRY; + export const ParamBustX = $.CubismDefaultParameterId.ParamBustX; + export const ParamBustY = $.CubismDefaultParameterId.ParamBustY; + export const ParamCheek = $.CubismDefaultParameterId.ParamCheek; + export const ParamEyeBallForm = $.CubismDefaultParameterId.ParamEyeBallForm; + export const ParamEyeBallX = $.CubismDefaultParameterId.ParamEyeBallX; + export const ParamEyeBallY = $.CubismDefaultParameterId.ParamEyeBallY; + export const ParamEyeLOpen = $.CubismDefaultParameterId.ParamEyeLOpen; + export const ParamEyeLSmile = $.CubismDefaultParameterId.ParamEyeLSmile; + export const ParamEyeROpen = $.CubismDefaultParameterId.ParamEyeROpen; + export const ParamEyeRSmile = $.CubismDefaultParameterId.ParamEyeRSmile; + export const ParamHairBack = $.CubismDefaultParameterId.ParamHairBack; + export const ParamHairFluffy = $.CubismDefaultParameterId.ParamHairFluffy; + export const ParamHairFront = $.CubismDefaultParameterId.ParamHairFront; + export const ParamHairSide = $.CubismDefaultParameterId.ParamHairSide; + export const ParamHandL = $.CubismDefaultParameterId.ParamHandL; + export const ParamHandR = $.CubismDefaultParameterId.ParamHandR; + export const ParamMouthForm = $.CubismDefaultParameterId.ParamMouthForm; + export const ParamMouthOpenY = $.CubismDefaultParameterId.ParamMouthOpenY; + export const ParamNONE = $.CubismDefaultParameterId.ParamNONE; + export const ParamShoulderY = $.CubismDefaultParameterId.ParamShoulderY; + export const PartsArmLPrefix = $.CubismDefaultParameterId.PartsArmLPrefix; + export const PartsArmPrefix = $.CubismDefaultParameterId.PartsArmPrefix; + export const PartsArmRPrefix = $.CubismDefaultParameterId.PartsArmRPrefix; + export const PartsIdCore = $.CubismDefaultParameterId.PartsIdCore; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/cubismframeworkconfig.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/cubismframeworkconfig.ts new file mode 100644 index 000000000..3ff7d4607 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/cubismframeworkconfig.ts @@ -0,0 +1,32 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +//======================================================== +// ログ出力関数の設定 +//======================================================== + +//---------- ログ出力レベル 選択項目 定義 ---------- +// 詳細ログ出力設定 +export const CSM_LOG_LEVEL_VERBOSE = 0; +// デバッグログ出力設定 +export const CSM_LOG_LEVEL_DEBUG = 1; +// Infoログ出力設定 +export const CSM_LOG_LEVEL_INFO = 2; +// 警告ログ出力設定 +export const CSM_LOG_LEVEL_WARNING = 3; +// エラーログ出力設定 +export const CSM_LOG_LEVEL_ERROR = 4; +// ログ出力オフ設定 +export const CSM_LOG_LEVEL_OFF = 5; + +/** + * ログ出力レベル設定。 + * + * 強制的にログ出力レベルを変える時に定義を有効にする。 + * CSM_LOG_LEVEL_VERBOSE ~ CSM_LOG_LEVEL_OFF を選択する。 + */ +export const CSM_LOG_LEVEL: number = CSM_LOG_LEVEL_VERBOSE; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/cubismmodelsettingjson.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/cubismmodelsettingjson.ts new file mode 100644 index 000000000..5e635c611 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/cubismmodelsettingjson.ts @@ -0,0 +1,830 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ICubismModelSetting } from './icubismmodelsetting'; +import { CubismIdHandle } from './id/cubismid'; +import { CubismFramework } from './live2dcubismframework'; +import { csmMap, iterator } from './type/csmmap'; +import { csmVector } from './type/csmvector'; +import { CubismJson, Value } from './utils/cubismjson'; + +/** + * Model3Jsonのキー文字列 + */ + +// JSON Keys +const Version = 'Version'; +const FileReferences = 'FileReferences'; +const Groups = 'Groups'; +const Layout = 'Layout'; +const HitAreas = 'HitAreas'; + +const Moc = 'Moc'; +const Textures = 'Textures'; +const Physics = 'Physics'; +const Pose = 'Pose'; +const Expressions = 'Expressions'; +const Motions = 'Motions'; + +const UserData = 'UserData'; +const Name = 'Name'; +const FilePath = 'File'; +const Id = 'Id'; +const Ids = 'Ids'; +const Target = 'Target'; + +// Motions +const Idle = 'Idle'; +const TapBody = 'TapBody'; +const PinchIn = 'PinchIn'; +const PinchOut = 'PinchOut'; +const Shake = 'Shake'; +const FlickHead = 'FlickHead'; +const Parameter = 'Parameter'; + +const SoundPath = 'Sound'; +const FadeInTime = 'FadeInTime'; +const FadeOutTime = 'FadeOutTime'; + +// Layout +const CenterX = 'CenterX'; +const CenterY = 'CenterY'; +const X = 'X'; +const Y = 'Y'; +const Width = 'Width'; +const Height = 'Height'; + +const LipSync = 'LipSync'; +const EyeBlink = 'EyeBlink'; + +const InitParameter = 'init_param'; +const InitPartsVisible = 'init_parts_visible'; +const Val = 'val'; + +enum FrequestNode { + FrequestNode_Groups, // getRoot().getValueByString(Groups) + FrequestNode_Moc, // getRoot().getValueByString(FileReferences).getValueByString(Moc) + FrequestNode_Motions, // getRoot().getValueByString(FileReferences).getValueByString(Motions) + FrequestNode_Expressions, // getRoot().getValueByString(FileReferences).getValueByString(Expressions) + FrequestNode_Textures, // getRoot().getValueByString(FileReferences).getValueByString(Textures) + FrequestNode_Physics, // getRoot().getValueByString(FileReferences).getValueByString(Physics) + FrequestNode_Pose, // getRoot().getValueByString(FileReferences).getValueByString(Pose) + FrequestNode_HitAreas // getRoot().getValueByString(HitAreas) +} + +/** + * Model3Jsonパーサー + * + * model3.jsonファイルをパースして値を取得する + */ +export class CubismModelSettingJson extends ICubismModelSetting { + /** + * 引数付きコンストラクタ + * + * @param buffer Model3Jsonをバイト配列として読み込んだデータバッファ + * @param size Model3Jsonのデータサイズ + */ + public constructor(buffer: ArrayBuffer, size: number) { + super(); + this._json = CubismJson.create(buffer, size); + + if (this._json) { + this._jsonValue = new csmVector(); + + // 順番はenum FrequestNodeと一致させる + this._jsonValue.pushBack(this._json.getRoot().getValueByString(Groups)); + this._jsonValue.pushBack( + this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(Moc) + ); + this._jsonValue.pushBack( + this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(Motions) + ); + this._jsonValue.pushBack( + this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(Expressions) + ); + this._jsonValue.pushBack( + this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(Textures) + ); + this._jsonValue.pushBack( + this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(Physics) + ); + this._jsonValue.pushBack( + this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(Pose) + ); + this._jsonValue.pushBack(this._json.getRoot().getValueByString(HitAreas)); + } + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CubismJson.delete(this._json); + + this._jsonValue = null; + } + + /** + * CubismJsonオブジェクトを取得する + * + * @return CubismJson + */ + public GetJson(): CubismJson { + return this._json; + } + + /** + * Mocファイルの名前を取得する + * @return Mocファイルの名前 + */ + public getModelFileName(): string { + if (!this.isExistModelFile()) { + return ''; + } + return this._jsonValue.at(FrequestNode.FrequestNode_Moc).getRawString(); + } + + /** + * モデルが使用するテクスチャの数を取得する + * テクスチャの数 + */ + public getTextureCount(): number { + if (!this.isExistTextureFiles()) { + return 0; + } + + return this._jsonValue.at(FrequestNode.FrequestNode_Textures).getSize(); + } + + /** + * テクスチャが配置されたディレクトリの名前を取得する + * @return テクスチャが配置されたディレクトリの名前 + */ + public getTextureDirectory(): string { + return this._jsonValue + .at(FrequestNode.FrequestNode_Textures) + .getRawString(); + } + + /** + * モデルが使用するテクスチャの名前を取得する + * @param index 配列のインデックス値 + * @return テクスチャの名前 + */ + public getTextureFileName(index: number): string { + return this._jsonValue + .at(FrequestNode.FrequestNode_Textures) + .getValueByIndex(index) + .getRawString(); + } + + /** + * モデルに設定された当たり判定の数を取得する + * @return モデルに設定された当たり判定の数 + */ + public getHitAreasCount(): number { + if (!this.isExistHitAreas()) { + return 0; + } + + return this._jsonValue.at(FrequestNode.FrequestNode_HitAreas).getSize(); + } + + /** + * 当たり判定に設定されたIDを取得する + * + * @param index 配列のindex + * @return 当たり判定に設定されたID + */ + public getHitAreaId(index: number): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._jsonValue + .at(FrequestNode.FrequestNode_HitAreas) + .getValueByIndex(index) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * 当たり判定に設定された名前を取得する + * @param index 配列のインデックス値 + * @return 当たり判定に設定された名前 + */ + public getHitAreaName(index: number): string { + return this._jsonValue + .at(FrequestNode.FrequestNode_HitAreas) + .getValueByIndex(index) + .getValueByString(Name) + .getRawString(); + } + + /** + * 物理演算設定ファイルの名前を取得する + * @return 物理演算設定ファイルの名前 + */ + public getPhysicsFileName(): string { + if (!this.isExistPhysicsFile()) { + return ''; + } + + return this._jsonValue.at(FrequestNode.FrequestNode_Physics).getRawString(); + } + + /** + * パーツ切り替え設定ファイルの名前を取得する + * @return パーツ切り替え設定ファイルの名前 + */ + public getPoseFileName(): string { + if (!this.isExistPoseFile()) { + return ''; + } + + return this._jsonValue.at(FrequestNode.FrequestNode_Pose).getRawString(); + } + + /** + * 表情設定ファイルの数を取得する + * @return 表情設定ファイルの数 + */ + public getExpressionCount(): number { + if (!this.isExistExpressionFile()) { + return 0; + } + + return this._jsonValue.at(FrequestNode.FrequestNode_Expressions).getSize(); + } + + /** + * 表情設定ファイルを識別する名前(別名)を取得する + * @param index 配列のインデックス値 + * @return 表情の名前 + */ + public getExpressionName(index: number): string { + return this._jsonValue + .at(FrequestNode.FrequestNode_Expressions) + .getValueByIndex(index) + .getValueByString(Name) + .getRawString(); + } + + /** + * 表情設定ファイルの名前を取得する + * @param index 配列のインデックス値 + * @return 表情設定ファイルの名前 + */ + public getExpressionFileName(index: number): string { + return this._jsonValue + .at(FrequestNode.FrequestNode_Expressions) + .getValueByIndex(index) + .getValueByString(FilePath) + .getRawString(); + } + + /** + * モーショングループの数を取得する + * @return モーショングループの数 + */ + public getMotionGroupCount(): number { + if (!this.isExistMotionGroups()) { + return 0; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getKeys() + .getSize(); + } + + /** + * モーショングループの名前を取得する + * @param index 配列のインデックス値 + * @return モーショングループの名前 + */ + public getMotionGroupName(index: number): string { + if (!this.isExistMotionGroups()) { + return null; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getKeys() + .at(index); + } + + /** + * モーショングループに含まれるモーションの数を取得する + * @param groupName モーショングループの名前 + * @return モーショングループの数 + */ + public getMotionCount(groupName: string): number { + if (!this.isExistMotionGroupName(groupName)) { + return 0; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getSize(); + } + + /** + * グループ名とインデックス値からモーションファイル名を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return モーションファイルの名前 + */ + public getMotionFileName(groupName: string, index: number): string { + if (!this.isExistMotionGroupName(groupName)) { + return ''; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(FilePath) + .getRawString(); + } + + /** + * モーションに対応するサウンドファイルの名前を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return サウンドファイルの名前 + */ + public getMotionSoundFileName(groupName: string, index: number): string { + if (!this.isExistMotionSoundFile(groupName, index)) { + return ''; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(SoundPath) + .getRawString(); + } + + /** + * モーション開始時のフェードイン処理時間を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return フェードイン処理時間[秒] + */ + public getMotionFadeInTimeValue(groupName: string, index: number): number { + if (!this.isExistMotionFadeIn(groupName, index)) { + return -1.0; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(FadeInTime) + .toFloat(); + } + + /** + * モーション終了時のフェードアウト処理時間を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return フェードアウト処理時間[秒] + */ + public getMotionFadeOutTimeValue(groupName: string, index: number): number { + if (!this.isExistMotionFadeOut(groupName, index)) { + return -1.0; + } + + return this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(FadeOutTime) + .toFloat(); + } + + /** + * ユーザーデータのファイル名を取得する + * @return ユーザーデータのファイル名 + */ + public getUserDataFile(): string { + if (!this.isExistUserDataFile()) { + return ''; + } + + return this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(UserData) + .getRawString(); + } + + /** + * レイアウト情報を取得する + * @param outLayoutMap csmMapクラスのインスタンス + * @return true レイアウト情報が存在する + * @return false レイアウト情報が存在しない + */ + public getLayoutMap(outLayoutMap: csmMap): boolean { + // 存在しない要素にアクセスするとエラーになるためValueがnullの場合はnullを代入する + const map: csmMap = this._json + .getRoot() + .getValueByString(Layout) + .getMap(); + + if (map == null) { + return false; + } + + let ret = false; + + for ( + const ite: iterator = map.begin(); + ite.notEqual(map.end()); + ite.preIncrement() + ) { + outLayoutMap.setValue(ite.ptr().first, ite.ptr().second.toFloat()); + ret = true; + } + + return ret; + } + + /** + * 目パチに関連付けられたパラメータの数を取得する + * @return 目パチに関連付けられたパラメータの数 + */ + public getEyeBlinkParameterCount(): number { + if (!this.isExistEyeBlinkParameters()) { + return 0; + } + + let num = 0; + for ( + let i = 0; + i < this._jsonValue.at(FrequestNode.FrequestNode_Groups).getSize(); + i++ + ) { + const refI: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Groups) + .getValueByIndex(i); + if (refI.isNull() || refI.isError()) { + continue; + } + + if (refI.getValueByString(Name).getRawString() == EyeBlink) { + num = refI + .getValueByString(Ids) + .getVector() + .getSize(); + break; + } + } + + return num; + } + + /** + * 目パチに関連付けられたパラメータのIDを取得する + * @param index 配列のインデックス値 + * @return パラメータID + */ + public getEyeBlinkParameterId(index: number): CubismIdHandle { + if (!this.isExistEyeBlinkParameters()) { + return null; + } + + for ( + let i = 0; + i < this._jsonValue.at(FrequestNode.FrequestNode_Groups).getSize(); + i++ + ) { + const refI: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Groups) + .getValueByIndex(i); + if (refI.isNull() || refI.isError()) { + continue; + } + + if (refI.getValueByString(Name).getRawString() == EyeBlink) { + return CubismFramework.getIdManager().getId( + refI + .getValueByString(Ids) + .getValueByIndex(index) + .getRawString() + ); + } + } + return null; + } + + /** + * リップシンクに関連付けられたパラメータの数を取得する + * @return リップシンクに関連付けられたパラメータの数 + */ + public getLipSyncParameterCount(): number { + if (!this.isExistLipSyncParameters()) { + return 0; + } + + let num = 0; + for ( + let i = 0; + i < this._jsonValue.at(FrequestNode.FrequestNode_Groups).getSize(); + i++ + ) { + const refI: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Groups) + .getValueByIndex(i); + if (refI.isNull() || refI.isError()) { + continue; + } + + if (refI.getValueByString(Name).getRawString() == LipSync) { + num = refI + .getValueByString(Ids) + .getVector() + .getSize(); + break; + } + } + + return num; + } + + /** + * リップシンクに関連付けられたパラメータの数を取得する + * @param index 配列のインデックス値 + * @return パラメータID + */ + public getLipSyncParameterId(index: number): CubismIdHandle { + if (!this.isExistLipSyncParameters()) { + return null; + } + + for ( + let i = 0; + i < this._jsonValue.at(FrequestNode.FrequestNode_Groups).getSize(); + i++ + ) { + const refI: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Groups) + .getValueByIndex(i); + if (refI.isNull() || refI.isError()) { + continue; + } + + if (refI.getValueByString(Name).getRawString() == LipSync) { + return CubismFramework.getIdManager().getId( + refI + .getValueByString(Ids) + .getValueByIndex(index) + .getRawString() + ); + } + } + return null; + } + + /** + * モデルファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistModelFile(): boolean { + const node: Value = this._jsonValue.at(FrequestNode.FrequestNode_Moc); + return !node.isNull() && !node.isError(); + } + + /** + * テクスチャファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistTextureFiles(): boolean { + const node: Value = this._jsonValue.at(FrequestNode.FrequestNode_Textures); + return !node.isNull() && !node.isError(); + } + + /** + * 当たり判定のキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistHitAreas(): boolean { + const node: Value = this._jsonValue.at(FrequestNode.FrequestNode_HitAreas); + return !node.isNull() && !node.isError(); + } + + /** + * 物理演算ファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistPhysicsFile(): boolean { + const node: Value = this._jsonValue.at(FrequestNode.FrequestNode_Physics); + return !node.isNull() && !node.isError(); + } + + /** + * ポーズ設定ファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistPoseFile(): boolean { + const node: Value = this._jsonValue.at(FrequestNode.FrequestNode_Pose); + return !node.isNull() && !node.isError(); + } + + /** + * 表情設定ファイルのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistExpressionFile(): boolean { + const node: Value = this._jsonValue.at( + FrequestNode.FrequestNode_Expressions + ); + return !node.isNull() && !node.isError(); + } + + /** + * モーショングループのキーが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistMotionGroups(): boolean { + const node: Value = this._jsonValue.at(FrequestNode.FrequestNode_Motions); + return !node.isNull() && !node.isError(); + } + + /** + * 引数で指定したモーショングループのキーが存在するかどうかを確認する + * @param groupName グループ名 + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistMotionGroupName(groupName: string): boolean { + const node: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName); + return !node.isNull() && !node.isError(); + } + + /** + * 引数で指定したモーションに対応するサウンドファイルのキーが存在するかどうかを確認する + * @param groupName グループ名 + * @param index 配列のインデックス値 + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistMotionSoundFile(groupName: string, index: number): boolean { + const node: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(SoundPath); + return !node.isNull() && !node.isError(); + } + + /** + * 引数で指定したモーションに対応するフェードイン時間のキーが存在するかどうかを確認する + * @param groupName グループ名 + * @param index 配列のインデックス値 + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistMotionFadeIn(groupName: string, index: number): boolean { + const node: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(FadeInTime); + return !node.isNull() && !node.isError(); + } + + /** + * 引数で指定したモーションに対応するフェードアウト時間のキーが存在するかどうかを確認する + * @param groupName グループ名 + * @param index 配列のインデックス値 + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistMotionFadeOut(groupName: string, index: number): boolean { + const node: Value = this._jsonValue + .at(FrequestNode.FrequestNode_Motions) + .getValueByString(groupName) + .getValueByIndex(index) + .getValueByString(FadeOutTime); + return !node.isNull() && !node.isError(); + } + + /** + * UserDataのファイル名が存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistUserDataFile(): boolean { + const node: Value = this._json + .getRoot() + .getValueByString(FileReferences) + .getValueByString(UserData); + return !node.isNull() && !node.isError(); + } + + /** + * 目ぱちに対応付けられたパラメータが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistEyeBlinkParameters(): boolean { + if ( + this._jsonValue.at(FrequestNode.FrequestNode_Groups).isNull() || + this._jsonValue.at(FrequestNode.FrequestNode_Groups).isError() + ) { + return false; + } + + for ( + let i = 0; + i < this._jsonValue.at(FrequestNode.FrequestNode_Groups).getSize(); + ++i + ) { + if ( + this._jsonValue + .at(FrequestNode.FrequestNode_Groups) + .getValueByIndex(i) + .getValueByString(Name) + .getRawString() == EyeBlink + ) { + return true; + } + } + + return false; + } + + /** + * リップシンクに対応付けられたパラメータが存在するかどうかを確認する + * @return true キーが存在する + * @return false キーが存在しない + */ + private isExistLipSyncParameters(): boolean { + if ( + this._jsonValue.at(FrequestNode.FrequestNode_Groups).isNull() || + this._jsonValue.at(FrequestNode.FrequestNode_Groups).isError() + ) { + return false; + } + for ( + let i = 0; + i < this._jsonValue.at(FrequestNode.FrequestNode_Groups).getSize(); + ++i + ) { + if ( + this._jsonValue + .at(FrequestNode.FrequestNode_Groups) + .getValueByIndex(i) + .getValueByString(Name) + .getRawString() == LipSync + ) { + return true; + } + } + return false; + } + + private _json: CubismJson; + private _jsonValue: csmVector; +} + +// Namespace definition for compatibility. +import * as $ from './cubismmodelsettingjson'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismModelSettingJson = $.CubismModelSettingJson; + export type CubismModelSettingJson = $.CubismModelSettingJson; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/effect/cubismbreath.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/effect/cubismbreath.ts new file mode 100644 index 000000000..0f35d2e3c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/effect/cubismbreath.ts @@ -0,0 +1,124 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismModel } from '../model/cubismmodel'; +import { csmVector } from '../type/csmvector'; + +/** + * 呼吸機能 + * + * 呼吸機能を提供する。 + */ +export class CubismBreath { + /** + * インスタンスの作成 + */ + public static create(): CubismBreath { + return new CubismBreath(); + } + + /** + * インスタンスの破棄 + * @param instance 対象のCubismBreath + */ + public static delete(instance: CubismBreath): void { + if (instance != null) { + instance = null; + } + } + + /** + * 呼吸のパラメータの紐づけ + * @param breathParameters 呼吸を紐づけたいパラメータのリスト + */ + public setParameters(breathParameters: csmVector): void { + this._breathParameters = breathParameters; + } + + /** + * 呼吸に紐づいているパラメータの取得 + * @return 呼吸に紐づいているパラメータのリスト + */ + public getParameters(): csmVector { + return this._breathParameters; + } + + /** + * モデルのパラメータの更新 + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + */ + public updateParameters(model: CubismModel, deltaTimeSeconds: number): void { + this._currentTime += deltaTimeSeconds; + + const t: number = this._currentTime * 2.0 * 3.14159; + + for (let i = 0; i < this._breathParameters.getSize(); ++i) { + const data: BreathParameterData = this._breathParameters.at(i); + + model.addParameterValueById( + data.parameterId, + data.offset + data.peak * Math.sin(t / data.cycle), + data.weight + ); + } + } + + /** + * コンストラクタ + */ + public constructor() { + this._currentTime = 0.0; + } + + _breathParameters: csmVector; // 呼吸にひもづいているパラメータのリスト + _currentTime: number; // 積算時間[秒] +} + +/** + * 呼吸のパラメータ情報 + */ +export class BreathParameterData { + /** + * コンストラクタ + * @param parameterId 呼吸をひもづけるパラメータID + * @param offset 呼吸を正弦波としたときの、波のオフセット + * @param peak 呼吸を正弦波としたときの、波の高さ + * @param cycle 呼吸を正弦波としたときの、波の周期 + * @param weight パラメータへの重み + */ + constructor( + parameterId?: CubismIdHandle, + offset?: number, + peak?: number, + cycle?: number, + weight?: number + ) { + this.parameterId = parameterId == undefined ? null : parameterId; + this.offset = offset == undefined ? 0.0 : offset; + this.peak = peak == undefined ? 0.0 : peak; + this.cycle = cycle == undefined ? 0.0 : cycle; + this.weight = weight == undefined ? 0.0 : weight; + } + + parameterId: CubismIdHandle; // 呼吸をひもづけるパラメータID\ + offset: number; // 呼吸を正弦波としたときの、波のオフセット + peak: number; // 呼吸を正弦波としたときの、波の高さ + cycle: number; // 呼吸を正弦波としたときの、波の周期 + weight: number; // パラメータへの重み +} + +// Namespace definition for compatibility. +import * as $ from './cubismbreath'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const BreathParameterData = $.BreathParameterData; + export type BreathParameterData = $.BreathParameterData; + export const CubismBreath = $.CubismBreath; + export type CubismBreath = $.CubismBreath; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/effect/cubismeyeblink.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/effect/cubismeyeblink.ts new file mode 100644 index 000000000..efb969f61 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/effect/cubismeyeblink.ts @@ -0,0 +1,233 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ICubismModelSetting } from '../icubismmodelsetting'; +import { CubismIdHandle } from '../id/cubismid'; +import { CubismModel } from '../model/cubismmodel'; +import { csmVector } from '../type/csmvector'; + +/** + * 自動まばたき機能 + * + * 自動まばたき機能を提供する。 + */ +export class CubismEyeBlink { + /** + * インスタンスを作成する + * @param modelSetting モデルの設定情報 + * @return 作成されたインスタンス + * @note 引数がNULLの場合、パラメータIDが設定されていない空のインスタンスを作成する。 + */ + public static create( + modelSetting: ICubismModelSetting = null + ): CubismEyeBlink { + return new CubismEyeBlink(modelSetting); + } + + /** + * インスタンスの破棄 + * @param eyeBlink 対象のCubismEyeBlink + */ + public static delete(eyeBlink: CubismEyeBlink): void { + if (eyeBlink != null) { + eyeBlink = null; + } + } + + /** + * まばたきの間隔の設定 + * @param blinkingInterval まばたきの間隔の時間[秒] + */ + public setBlinkingInterval(blinkingInterval: number): void { + this._blinkingIntervalSeconds = blinkingInterval; + } + + /** + * まばたきのモーションの詳細設定 + * @param closing まぶたを閉じる動作の所要時間[秒] + * @param closed まぶたを閉じている動作の所要時間[秒] + * @param opening まぶたを開く動作の所要時間[秒] + */ + public setBlinkingSetting( + closing: number, + closed: number, + opening: number + ): void { + this._closingSeconds = closing; + this._closedSeconds = closed; + this._openingSeconds = opening; + } + + /** + * まばたきさせるパラメータIDのリストの設定 + * @param parameterIds パラメータのIDのリスト + */ + public setParameterIds(parameterIds: csmVector): void { + this._parameterIds = parameterIds; + } + + /** + * まばたきさせるパラメータIDのリストの取得 + * @return パラメータIDのリスト + */ + public getParameterIds(): csmVector { + return this._parameterIds; + } + + /** + * モデルのパラメータの更新 + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + */ + public updateParameters(model: CubismModel, deltaTimeSeconds: number): void { + this._userTimeSeconds += deltaTimeSeconds; + let parameterValue: number; + let t = 0.0; + + switch (this._blinkingState) { + case EyeState.EyeState_Closing: + t = + (this._userTimeSeconds - this._stateStartTimeSeconds) / + this._closingSeconds; + + if (t >= 1.0) { + t = 1.0; + this._blinkingState = EyeState.EyeState_Closed; + this._stateStartTimeSeconds = this._userTimeSeconds; + } + + parameterValue = 1.0 - t; + + break; + case EyeState.EyeState_Closed: + t = + (this._userTimeSeconds - this._stateStartTimeSeconds) / + this._closedSeconds; + + if (t >= 1.0) { + this._blinkingState = EyeState.EyeState_Opening; + this._stateStartTimeSeconds = this._userTimeSeconds; + } + + parameterValue = 0.0; + + break; + case EyeState.EyeState_Opening: + t = + (this._userTimeSeconds - this._stateStartTimeSeconds) / + this._openingSeconds; + + if (t >= 1.0) { + t = 1.0; + this._blinkingState = EyeState.EyeState_Interval; + this._nextBlinkingTime = this.determinNextBlinkingTiming(); + } + + parameterValue = t; + + break; + case EyeState.EyeState_Interval: + if (this._nextBlinkingTime < this._userTimeSeconds) { + this._blinkingState = EyeState.EyeState_Closing; + this._stateStartTimeSeconds = this._userTimeSeconds; + } + + parameterValue = 1.0; + + break; + case EyeState.EyeState_First: + default: + this._blinkingState = EyeState.EyeState_Interval; + this._nextBlinkingTime = this.determinNextBlinkingTiming(); + + parameterValue = 1.0; + break; + } + + if (!CubismEyeBlink.CloseIfZero) { + parameterValue = -parameterValue; + } + + for (let i = 0; i < this._parameterIds.getSize(); ++i) { + model.setParameterValueById(this._parameterIds.at(i), parameterValue); + } + } + + /** + * コンストラクタ + * @param modelSetting モデルの設定情報 + */ + public constructor(modelSetting: ICubismModelSetting) { + this._blinkingState = EyeState.EyeState_First; + this._nextBlinkingTime = 0.0; + this._stateStartTimeSeconds = 0.0; + this._blinkingIntervalSeconds = 4.0; + this._closingSeconds = 0.1; + this._closedSeconds = 0.05; + this._openingSeconds = 0.15; + this._userTimeSeconds = 0.0; + this._parameterIds = new csmVector(); + + if (modelSetting == null) { + return; + } + + for (let i = 0; i < modelSetting.getEyeBlinkParameterCount(); ++i) { + this._parameterIds.pushBack(modelSetting.getEyeBlinkParameterId(i)); + } + } + + /** + * 次の瞬きのタイミングの決定 + * + * @return 次のまばたきを行う時刻[秒] + */ + public determinNextBlinkingTiming(): number { + const r: number = Math.random(); + return ( + this._userTimeSeconds + r * (2.0 * this._blinkingIntervalSeconds - 1.0) + ); + } + + _blinkingState: number; // 現在の状態 + _parameterIds: csmVector; // 操作対象のパラメータのIDのリスト + _nextBlinkingTime: number; // 次のまばたきの時刻[秒] + _stateStartTimeSeconds: number; // 現在の状態が開始した時刻[秒] + _blinkingIntervalSeconds: number; // まばたきの間隔[秒] + _closingSeconds: number; // まぶたを閉じる動作の所要時間[秒] + _closedSeconds: number; // まぶたを閉じている動作の所要時間[秒] + _openingSeconds: number; // まぶたを開く動作の所要時間[秒] + _userTimeSeconds: number; // デルタ時間の積算値[秒] + + /** + * IDで指定された目のパラメータが、0のときに閉じるなら true 、1の時に閉じるなら false 。 + */ + static readonly CloseIfZero: boolean = true; +} + +/** + * まばたきの状態 + * + * まばたきの状態を表す列挙型 + */ +export enum EyeState { + EyeState_First = 0, // 初期状態 + EyeState_Interval, // まばたきしていない状態 + EyeState_Closing, // まぶたが閉じていく途中の状態 + EyeState_Closed, // まぶたが閉じている状態 + EyeState_Opening // まぶたが開いていく途中の状態 +} + +// Namespace definition for compatibility. +import * as $ from './cubismeyeblink'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismEyeBlink = $.CubismEyeBlink; + export type CubismEyeBlink = $.CubismEyeBlink; + export const EyeState = $.EyeState; + export type EyeState = $.EyeState; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/effect/cubismpose.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/effect/cubismpose.ts new file mode 100644 index 000000000..ceaedee0b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/effect/cubismpose.ts @@ -0,0 +1,400 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CubismModel } from '../model/cubismmodel'; +import { csmVector, iterator } from '../type/csmvector'; +import { CubismJson, Value } from '../utils/cubismjson'; + +const Epsilon = 0.001; +const DefaultFadeInSeconds = 0.5; + +// Pose.jsonのタグ +const FadeIn = 'FadeInTime'; +const Link = 'Link'; +const Groups = 'Groups'; +const Id = 'Id'; + +/** + * パーツの不透明度の設定 + * + * パーツの不透明度の管理と設定を行う。 + */ +export class CubismPose { + /** + * インスタンスの作成 + * @param pose3json pose3.jsonのデータ + * @param size pose3.jsonのデータのサイズ[byte] + * @return 作成されたインスタンス + */ + public static create(pose3json: ArrayBuffer, size: number): CubismPose { + const ret: CubismPose = new CubismPose(); + const json: CubismJson = CubismJson.create(pose3json, size); + const root: Value = json.getRoot(); + + // フェード時間の指定 + if (!root.getValueByString(FadeIn).isNull()) { + ret._fadeTimeSeconds = root + .getValueByString(FadeIn) + .toFloat(DefaultFadeInSeconds); + + if (ret._fadeTimeSeconds <= 0.0) { + ret._fadeTimeSeconds = DefaultFadeInSeconds; + } + } + + // パーツグループ + const poseListInfo: Value = root.getValueByString(Groups); + const poseCount: number = poseListInfo.getSize(); + + for (let poseIndex = 0; poseIndex < poseCount; ++poseIndex) { + const idListInfo: Value = poseListInfo.getValueByIndex(poseIndex); + const idCount: number = idListInfo.getSize(); + let groupCount = 0; + + for (let groupIndex = 0; groupIndex < idCount; ++groupIndex) { + const partInfo: Value = idListInfo.getValueByIndex(groupIndex); + const partData: PartData = new PartData(); + const parameterId: CubismIdHandle = CubismFramework.getIdManager().getId( + partInfo.getValueByString(Id).getRawString() + ); + + partData.partId = parameterId; + + // リンクするパーツの設定 + if (!partInfo.getValueByString(Link).isNull()) { + const linkListInfo: Value = partInfo.getValueByString(Link); + const linkCount: number = linkListInfo.getSize(); + + for (let linkIndex = 0; linkIndex < linkCount; ++linkIndex) { + const linkPart: PartData = new PartData(); + const linkId: CubismIdHandle = CubismFramework.getIdManager().getId( + linkListInfo.getValueByIndex(linkIndex).getString() + ); + + linkPart.partId = linkId; + + partData.link.pushBack(linkPart); + } + } + + ret._partGroups.pushBack(partData.clone()); + + ++groupCount; + } + + ret._partGroupCounts.pushBack(groupCount); + } + + CubismJson.delete(json); + + return ret; + } + + /** + * インスタンスを破棄する + * @param pose 対象のCubismPose + */ + public static delete(pose: CubismPose): void { + if (pose != null) { + pose = null; + } + } + + /** + * モデルのパラメータの更新 + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + */ + public updateParameters(model: CubismModel, deltaTimeSeconds: number): void { + // 前回のモデルと同じでない場合は初期化が必要 + if (model != this._lastModel) { + // パラメータインデックスの初期化 + this.reset(model); + } + + this._lastModel = model; + + // 設定から時間を変更すると、経過時間がマイナスになる事があるので、経過時間0として対応 + if (deltaTimeSeconds < 0.0) { + deltaTimeSeconds = 0.0; + } + + let beginIndex = 0; + + for (let i = 0; i < this._partGroupCounts.getSize(); i++) { + const partGroupCount: number = this._partGroupCounts.at(i); + + this.doFade(model, deltaTimeSeconds, beginIndex, partGroupCount); + + beginIndex += partGroupCount; + } + + this.copyPartOpacities(model); + } + + /** + * 表示を初期化 + * @param model 対象のモデル + * @note 不透明度の初期値が0でないパラメータは、不透明度を1に設定する + */ + public reset(model: CubismModel): void { + let beginIndex = 0; + + for (let i = 0; i < this._partGroupCounts.getSize(); ++i) { + const groupCount: number = this._partGroupCounts.at(i); + + for (let j: number = beginIndex; j < beginIndex + groupCount; ++j) { + this._partGroups.at(j).initialize(model); + + const partsIndex: number = this._partGroups.at(j).partIndex; + const paramIndex: number = this._partGroups.at(j).parameterIndex; + + if (partsIndex < 0) { + continue; + } + + model.setPartOpacityByIndex(partsIndex, j == beginIndex ? 1.0 : 0.0); + model.setParameterValueByIndex(paramIndex, j == beginIndex ? 1.0 : 0.0); + + for (let k = 0; k < this._partGroups.at(j).link.getSize(); ++k) { + this._partGroups + .at(j) + .link.at(k) + .initialize(model); + } + } + + beginIndex += groupCount; + } + } + + /** + * パーツの不透明度をコピー + * + * @param model 対象のモデル + */ + public copyPartOpacities(model: CubismModel): void { + for ( + let groupIndex = 0; + groupIndex < this._partGroups.getSize(); + ++groupIndex + ) { + const partData: PartData = this._partGroups.at(groupIndex); + + if (partData.link.getSize() == 0) { + continue; // 連動するパラメータはない + } + + const partIndex: number = this._partGroups.at(groupIndex).partIndex; + const opacity: number = model.getPartOpacityByIndex(partIndex); + + for ( + let linkIndex = 0; + linkIndex < partData.link.getSize(); + ++linkIndex + ) { + const linkPart: PartData = partData.link.at(linkIndex); + const linkPartIndex: number = linkPart.partIndex; + + if (linkPartIndex < 0) { + continue; + } + + model.setPartOpacityByIndex(linkPartIndex, opacity); + } + } + } + + /** + * パーツのフェード操作を行う。 + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + * @param beginIndex フェード操作を行うパーツグループの先頭インデックス + * @param partGroupCount フェード操作を行うパーツグループの個数 + */ + public doFade( + model: CubismModel, + deltaTimeSeconds: number, + beginIndex: number, + partGroupCount: number + ): void { + let visiblePartIndex = -1; + let newOpacity = 1.0; + + const phi = 0.5; + const backOpacityThreshold = 0.15; + + // 現在、表示状態になっているパーツを取得 + for (let i: number = beginIndex; i < beginIndex + partGroupCount; ++i) { + const partIndex: number = this._partGroups.at(i).partIndex; + const paramIndex: number = this._partGroups.at(i).parameterIndex; + + if (model.getParameterValueByIndex(paramIndex) > Epsilon) { + if (visiblePartIndex >= 0) { + break; + } + + visiblePartIndex = i; + newOpacity = model.getPartOpacityByIndex(partIndex); + + // 新しい不透明度を計算 + newOpacity += deltaTimeSeconds / this._fadeTimeSeconds; + + if (newOpacity > 1.0) { + newOpacity = 1.0; + } + } + } + + if (visiblePartIndex < 0) { + visiblePartIndex = 0; + newOpacity = 1.0; + } + + // 表示パーツ、非表示パーツの不透明度を設定する + for (let i: number = beginIndex; i < beginIndex + partGroupCount; ++i) { + const partsIndex: number = this._partGroups.at(i).partIndex; + + // 表示パーツの設定 + if (visiblePartIndex == i) { + model.setPartOpacityByIndex(partsIndex, newOpacity); // 先に設定 + } + // 非表示パーツの設定 + else { + let opacity: number = model.getPartOpacityByIndex(partsIndex); + let a1: number; // 計算によって求められる不透明度 + + if (newOpacity < phi) { + a1 = (newOpacity * (phi - 1)) / phi + 1.0; // (0,1),(phi,phi)を通る直線式 + } else { + a1 = ((1 - newOpacity) * phi) / (1.0 - phi); // (1,0),(phi,phi)を通る直線式 + } + + // 背景の見える割合を制限する場合 + const backOpacity: number = (1.0 - a1) * (1.0 - newOpacity); + + if (backOpacity > backOpacityThreshold) { + a1 = 1.0 - backOpacityThreshold / (1.0 - newOpacity); + } + + if (opacity > a1) { + opacity = a1; // 計算の不透明度よりも大きければ(濃ければ)不透明度を上げる + } + + model.setPartOpacityByIndex(partsIndex, opacity); + } + } + } + + /** + * コンストラクタ + */ + public constructor() { + this._fadeTimeSeconds = DefaultFadeInSeconds; + this._lastModel = null; + this._partGroups = new csmVector(); + this._partGroupCounts = new csmVector(); + } + + _partGroups: csmVector; // パーツグループ + _partGroupCounts: csmVector; // それぞれのパーツグループの個数 + _fadeTimeSeconds: number; // フェード時間[秒] + _lastModel: CubismModel; // 前回操作したモデル +} + +/** + * パーツにまつわるデータを管理 + */ +export class PartData { + /** + * コンストラクタ + */ + constructor(v?: PartData) { + this.parameterIndex = 0; + this.partIndex = 0; + this.link = new csmVector(); + + if (v != undefined) { + this.partId = v.partId; + + for ( + const ite: iterator = v.link.begin(); + ite.notEqual(v.link.end()); + ite.preIncrement() + ) { + this.link.pushBack(ite.ptr().clone()); + } + } + } + + /** + * =演算子のオーバーロード + */ + public assignment(v: PartData): PartData { + this.partId = v.partId; + + for ( + const ite: iterator = v.link.begin(); + ite.notEqual(v.link.end()); + ite.preIncrement() + ) { + this.link.pushBack(ite.ptr().clone()); + } + + return this; + } + + /** + * 初期化 + * @param model 初期化に使用するモデル + */ + public initialize(model: CubismModel): void { + this.parameterIndex = model.getParameterIndex(this.partId); + this.partIndex = model.getPartIndex(this.partId); + + model.setParameterValueByIndex(this.parameterIndex, 1); + } + + /** + * オブジェクトのコピーを生成する + */ + public clone(): PartData { + const clonePartData: PartData = new PartData(); + + clonePartData.partId = this.partId; + clonePartData.parameterIndex = this.parameterIndex; + clonePartData.partIndex = this.partIndex; + clonePartData.link = new csmVector(); + + for ( + let ite: iterator = this.link.begin(); + ite.notEqual(this.link.end()); + ite.increment() + ) { + clonePartData.link.pushBack(ite.ptr().clone()); + } + + return clonePartData; + } + + partId: CubismIdHandle; // パーツID + parameterIndex: number; // パラメータのインデックス + partIndex: number; // パーツのインデックス + link: csmVector; // 連動するパラメータ +} + +// Namespace definition for compatibility. +import * as $ from './cubismpose'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismPose = $.CubismPose; + export type CubismPose = $.CubismPose; + export const PartData = $.PartData; + export type PartData = $.PartData; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/icubismallcator.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/icubismallcator.ts new file mode 100644 index 000000000..5e10ace9b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/icubismallcator.ts @@ -0,0 +1,51 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * メモリアロケーションを抽象化したクラス + * + * メモリ確保・解放処理をプラットフォーム側で実装して + * フレームワークから呼び出すためのインターフェース + */ +export abstract class ICubismAllocator { + /** + * アラインメント制約なしのヒープ・メモリーを確保します + * + * @param size 確保するバイト数 + * @return 成功すると割り当てられたメモリのアドレス。そうでなければ'0'を返す + */ + public abstract allocate(size: number): any; + + /** + * アラインメント制約なしのヒープ・メモリーを解放します。 + * + * @param memory 解放するメモリのアドレス + */ + public abstract deallocate(memory: any): void; + + /** + * アラインメント制約有のヒープ・メモリーを確保します。 + * @param size 確保するバイト数 + * @param alignment メモリーブロックのアラインメント幅 + * @return 成功すると割り当てられたメモリのアドレス。そうでなければ'0'を返す + */ + public abstract allocateAligned(size: number, alignment: number): any; + + /** + * アラインメント制約ありのヒープ・メモリーを解放します。 + * @param alignedMemory 解放するメモリのアドレス + */ + public abstract deallocateAligned(alignedMemory: any): void; +} + +// Namespace definition for compatibility. +import * as $ from './icubismallcator'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const ICubismAllocator = $.ICubismAllocator; + export type ICubismAllocator = $.ICubismAllocator; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/icubismmodelsetting.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/icubismmodelsetting.ts new file mode 100644 index 000000000..9c92e1c89 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/icubismmodelsetting.ts @@ -0,0 +1,203 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from './id/cubismid'; +import { csmMap } from './type/csmmap'; + +/** + * モデル設定情報を取り扱う関数を宣言した純粋仮想クラス。 + * + * このクラスを継承することで、モデル設定情報を取り扱うクラスになる。 + */ +export abstract class ICubismModelSetting { + /** + * Mocファイルの名前を取得する + * @return Mocファイルの名前 + */ + public abstract getModelFileName(): string; + + /** + * モデルが使用するテクスチャの数を取得する + * テクスチャの数 + */ + public abstract getTextureCount(): number; + + /** + * テクスチャが配置されたディレクトリの名前を取得する + * @return テクスチャが配置されたディレクトリの名前 + */ + public abstract getTextureDirectory(): string; + + /** + * モデルが使用するテクスチャの名前を取得する + * @param index 配列のインデックス値 + * @return テクスチャの名前 + */ + public abstract getTextureFileName(index: number): string; + + /** + * モデルに設定された当たり判定の数を取得する + * @return モデルに設定された当たり判定の数 + */ + public abstract getHitAreasCount(): number; + + /** + * 当たり判定に設定されたIDを取得する + * + * @param index 配列のindex + * @return 当たり判定に設定されたID + */ + public abstract getHitAreaId(index: number): CubismIdHandle; + + /** + * 当たり判定に設定された名前を取得する + * @param index 配列のインデックス値 + * @return 当たり判定に設定された名前 + */ + public abstract getHitAreaName(index: number): string; + + /** + * 物理演算設定ファイルの名前を取得する + * @return 物理演算設定ファイルの名前 + */ + public abstract getPhysicsFileName(): string; + + /** + * パーツ切り替え設定ファイルの名前を取得する + * @return パーツ切り替え設定ファイルの名前 + */ + public abstract getPoseFileName(): string; + + /** + * 表情設定ファイルの数を取得する + * @return 表情設定ファイルの数 + */ + public abstract getExpressionCount(): number; + + /** + * 表情設定ファイルを識別する名前(別名)を取得する + * @param index 配列のインデックス値 + * @return 表情の名前 + */ + public abstract getExpressionName(index: number): string; + + /** + * 表情設定ファイルの名前を取得する + * @param index 配列のインデックス値 + * @return 表情設定ファイルの名前 + */ + public abstract getExpressionFileName(index: number): string; + + /** + * モーショングループの数を取得する + * @return モーショングループの数 + */ + public abstract getMotionGroupCount(): number; + + /** + * モーショングループの名前を取得する + * @param index 配列のインデックス値 + * @return モーショングループの名前 + */ + public abstract getMotionGroupName(index: number): string; + + /** + * モーショングループに含まれるモーションの数を取得する + * @param groupName モーショングループの名前 + * @return モーショングループの数 + */ + public abstract getMotionCount(groupName: string): number; + + /** + * グループ名とインデックス値からモーションファイル名を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return モーションファイルの名前 + */ + public abstract getMotionFileName(groupName: string, index: number): string; + + /** + * モーションに対応するサウンドファイルの名前を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return サウンドファイルの名前 + */ + public abstract getMotionSoundFileName( + groupName: string, + index: number + ): string; + + /** + * モーション開始時のフェードイン処理時間を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return フェードイン処理時間[秒] + */ + public abstract getMotionFadeInTimeValue( + groupName: string, + index: number + ): number; + + /** + * モーション終了時のフェードアウト処理時間を取得する + * @param groupName モーショングループの名前 + * @param index 配列のインデックス値 + * @return フェードアウト処理時間[秒] + */ + public abstract getMotionFadeOutTimeValue( + groupName: string, + index: number + ): number; + + /** + * ユーザーデータのファイル名を取得する + * @return ユーザーデータのファイル名 + */ + public abstract getUserDataFile(): string; + + /** + * レイアウト情報を取得する + * @param outLayoutMap csmMapクラスのインスタンス + * @return true レイアウト情報が存在する + * @return false レイアウト情報が存在しない + */ + public abstract getLayoutMap(outLayoutMap: csmMap): boolean; + + /** + * 目パチに関連付けられたパラメータの数を取得する + * @return 目パチに関連付けられたパラメータの数 + */ + public abstract getEyeBlinkParameterCount(): number; + + /** + * 目パチに関連付けられたパラメータのIDを取得する + * @param index 配列のインデックス値 + * @return パラメータID + */ + public abstract getEyeBlinkParameterId(index: number): CubismIdHandle; + + /** + * リップシンクに関連付けられたパラメータの数を取得する + * @return リップシンクに関連付けられたパラメータの数 + */ + public abstract getLipSyncParameterCount(): number; + + /** + * リップシンクに関連付けられたパラメータの数を取得する + * @param index 配列のインデックス値 + * @return パラメータID + */ + public abstract getLipSyncParameterId(index: number): CubismIdHandle; +} + +// Namespace definition for compatibility. +import * as $ from './icubismmodelsetting'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const ICubismModelSetting = $.ICubismModelSetting; + export type ICubismModelSetting = $.ICubismModelSetting; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/id/cubismid.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/id/cubismid.ts new file mode 100644 index 000000000..2d72b0c32 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/id/cubismid.ts @@ -0,0 +1,79 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { csmString } from '../type/csmstring'; + +/** + * パラメータ名・パーツ名・Drawable名を保持 + * + * パラメータ名・パーツ名・Drawable名を保持するクラス。 + */ +export class CubismId { + /** + * ID名を取得する + */ + public getString(): csmString { + return this._id; + } + + /** + * コンストラクタ + */ + public constructor(id: string | csmString) { + if (typeof id === 'string') { + this._id = new csmString(id); + return; + } + + this._id = id; + } + + /** + * idを比較 + * @param c 比較するid + * @return 同じならばtrue,異なっていればfalseを返す + */ + public isEqual(c: string | csmString | CubismId): boolean { + if (typeof c === 'string') { + return this._id.isEqual(c); + } else if (c instanceof csmString) { + return this._id.isEqual(c.s); + } else if (c instanceof CubismId) { + return this._id.isEqual(c._id.s); + } + return false; + } + + /** + * idを比較 + * @param c 比較するid + * @return 同じならばtrue,異なっていればfalseを返す + */ + public isNotEqual(c: string | csmString | CubismId): boolean { + if (typeof c == 'string') { + return !this._id.isEqual(c); + } else if (c instanceof csmString) { + return !this._id.isEqual(c.s); + } else if (c instanceof CubismId) { + return !this._id.isEqual(c._id.s); + } + return false; + } + + private _id: csmString; // ID名 +} + +export declare type CubismIdHandle = CubismId; + +// Namespace definition for compatibility. +import * as $ from './cubismid'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismId = $.CubismId; + export type CubismId = $.CubismId; + export type CubismIdHandle = $.CubismIdHandle; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/id/cubismidmanager.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/id/cubismidmanager.ts new file mode 100644 index 000000000..4f8ecdee8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/id/cubismidmanager.ts @@ -0,0 +1,121 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { csmString } from '../type/csmstring'; +import { csmVector } from '../type/csmvector'; +import { CubismId } from './cubismid'; + +/** + * ID名の管理 + * + * ID名を管理する。 + */ +export class CubismIdManager { + /** + * コンストラクタ + */ + public constructor() { + this._ids = new csmVector(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + for (let i = 0; i < this._ids.getSize(); ++i) { + this._ids.set(i, void 0); + } + this._ids = null; + } + + /** + * ID名をリストから登録 + * + * @param ids ID名リスト + * @param count IDの個数 + */ + public registerIds(ids: string[] | csmString[]): void { + for (let i = 0; i < ids.length; i++) { + this.registerId(ids[i]); + } + } + + /** + * ID名を登録 + * + * @param id ID名 + */ + public registerId(id: string | csmString): CubismId { + let result: CubismId = null; + + if ('string' == typeof id) { + if ((result = this.findId(id)) != null) { + return result; + } + + result = new CubismId(id); + this._ids.pushBack(result); + } else { + return this.registerId(id.s); + } + + return result; + } + + /** + * ID名からIDを取得する + * + * @param id ID名 + */ + public getId(id: csmString | string): CubismId { + return this.registerId(id); + } + + /** + * ID名からIDの確認 + * + * @return true 存在する + * @return false 存在しない + */ + public isExist(id: csmString | string): boolean { + if ('string' == typeof id) { + return this.findId(id) != null; + } + return this.isExist(id.s); + } + + /** + * ID名からIDを検索する。 + * + * @param id ID名 + * @return 登録されているID。なければNULL。 + */ + private findId(id: string): CubismId { + for (let i = 0; i < this._ids.getSize(); ++i) { + if ( + this._ids + .at(i) + .getString() + .isEqual(id) + ) { + return this._ids.at(i); + } + } + + return null; + } + + private _ids: csmVector; // 登録されているIDのリスト +} + +// Namespace definition for compatibility. +import * as $ from './cubismidmanager'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismIdManager = $.CubismIdManager; + export type CubismIdManager = $.CubismIdManager; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/live2dcubismframework.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/live2dcubismframework.ts new file mode 100644 index 000000000..17dcefb3e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/live2dcubismframework.ts @@ -0,0 +1,277 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdManager } from './id/cubismidmanager'; +import { CubismRenderer } from './rendering/cubismrenderer'; +import { + CSM_ASSERT, + CubismLogInfo, + CubismLogWarning +} from './utils/cubismdebug'; +import { Value } from './utils/cubismjson'; + +export function strtod(s: string, endPtr: string[]): number { + let index = 0; + for (let i = 1; ; i++) { + const testC: string = s.slice(i - 1, i); + + // 指数・マイナスの可能性があるのでスキップする + if (testC == 'e' || testC == '-' || testC == 'E') { + continue; + } // 文字列の範囲を広げていく + + const test: string = s.substring(0, i); + const number = Number(test); + if (isNaN(number)) { + // 数値として認識できなくなったので終了 + break; + } // 最後に数値としてできたindexを格納しておく + + index = i; + } + let d = parseFloat(s); // パースした数値 + + if (isNaN(d)) { + // 数値として認識できなくなったので終了 + d = NaN; + } + + endPtr[0] = s.slice(index); // 後続の文字列 + return d; +} + +// ファイルスコープの変数を初期化 + +let s_isStarted = false; +let s_isInitialized = false; +let s_option: Option = null; +let s_cubismIdManager: CubismIdManager = null; + +/** + * Framework内で使う定数の宣言 + */ +export const Constant = Object.freeze>({ + vertexOffset: 0, // メッシュ頂点のオフセット値 + vertexStep: 2 // メッシュ頂点のステップ値 +}); + +export function csmDelete(address: T): void { + if (!address) { + return; + } + + address = void 0; +} + +/** + * Live2D Cubism SDK Original Workflow SDKのエントリポイント + * 利用開始時はCubismFramework.initialize()を呼び、CubismFramework.dispose()で終了する。 + */ +export class CubismFramework { + /** + * Cubism FrameworkのAPIを使用可能にする。 + * APIを実行する前に必ずこの関数を実行すること。 + * 一度準備が完了して以降は、再び実行しても内部処理がスキップされます。 + * + * @param option Optionクラスのインスタンス + * + * @return 準備処理が完了したらtrueが返ります。 + */ + public static startUp(option: Option = null): boolean { + if (s_isStarted) { + CubismLogInfo('CubismFramework.startUp() is already done.'); + return s_isStarted; + } + + s_option = option; + + if (s_option != null) { + Live2DCubismCore.Logging.csmSetLogFunction(s_option.logFunction); + } + + s_isStarted = true; + + // Live2D Cubism Coreバージョン情報を表示 + if (s_isStarted) { + const version: number = Live2DCubismCore.Version.csmGetVersion(); + const major: number = (version & 0xff000000) >> 24; + const minor: number = (version & 0x00ff0000) >> 16; + const patch: number = version & 0x0000ffff; + const versionNumber: number = version; + + CubismLogInfo( + `Live2D Cubism Core version: {0}.{1}.{2} ({3})`, + ('00' + major).slice(-2), + ('00' + minor).slice(-2), + ('0000' + patch).slice(-4), + versionNumber + ); + } + + CubismLogInfo('CubismFramework.startUp() is complete.'); + + return s_isStarted; + } + + /** + * StartUp()で初期化したCubismFrameworkの各パラメータをクリアします。 + * Dispose()したCubismFrameworkを再利用する際に利用してください。 + */ + public static cleanUp(): void { + s_isStarted = false; + s_isInitialized = false; + s_option = null; + s_cubismIdManager = null; + } + + /** + * Cubism Framework内のリソースを初期化してモデルを表示可能な状態にします。
    + * 再度Initialize()するには先にDispose()を実行する必要があります。 + */ + public static initialize(): void { + CSM_ASSERT(s_isStarted); + if (!s_isStarted) { + CubismLogWarning('CubismFramework is not started.'); + return; + } + + // --- s_isInitializedによる連続初期化ガード --- + // 連続してリソース確保が行われないようにする。 + // 再度Initialize()するには先にDispose()を実行する必要がある。 + if (s_isInitialized) { + CubismLogWarning( + 'CubismFramework.initialize() skipped, already initialized.' + ); + return; + } + + //---- static 初期化 ---- + Value.staticInitializeNotForClientCall(); + + s_cubismIdManager = new CubismIdManager(); + + s_isInitialized = true; + + CubismLogInfo('CubismFramework.initialize() is complete.'); + } + + /** + * Cubism Framework内の全てのリソースを解放します。 + * ただし、外部で確保されたリソースについては解放しません。 + * 外部で適切に破棄する必要があります。 + */ + public static dispose(): void { + CSM_ASSERT(s_isStarted); + if (!s_isStarted) { + CubismLogWarning('CubismFramework is not started.'); + return; + } + + // --- s_isInitializedによる未初期化解放ガード --- + // dispose()するには先にinitialize()を実行する必要がある。 + if (!s_isInitialized) { + // false...リソース未確保の場合 + CubismLogWarning('CubismFramework.dispose() skipped, not initialized.'); + return; + } + + Value.staticReleaseNotForClientCall(); + + s_cubismIdManager.release(); + s_cubismIdManager = null; + + // レンダラの静的リソース(シェーダプログラム他)を解放する + CubismRenderer.staticRelease(); + + s_isInitialized = false; + + CubismLogInfo('CubismFramework.dispose() is complete.'); + } + + /** + * Cubism FrameworkのAPIを使用する準備が完了したかどうか + * @return APIを使用する準備が完了していればtrueが返ります。 + */ + public static isStarted(): boolean { + return s_isStarted; + } + + /** + * Cubism Frameworkのリソース初期化がすでに行われているかどうか + * @return リソース確保が完了していればtrueが返ります + */ + public static isInitialized(): boolean { + return s_isInitialized; + } + + /** + * Core APIにバインドしたログ関数を実行する + * + * @praram message ログメッセージ + */ + public static coreLogFunction(message: string): void { + // Return if logging not possible. + if (!Live2DCubismCore.Logging.csmGetLogFunction()) { + return; + } + + Live2DCubismCore.Logging.csmGetLogFunction()(message); + } + + /** + * 現在のログ出力レベル設定の値を返す。 + * + * @return 現在のログ出力レベル設定の値 + */ + public static getLoggingLevel(): LogLevel { + if (s_option != null) { + return s_option.loggingLevel; + } + return LogLevel.LogLevel_Off; + } + + /** + * IDマネージャのインスタンスを取得する + * @return CubismManagerクラスのインスタンス + */ + public static getIdManager(): CubismIdManager { + return s_cubismIdManager; + } + + /** + * 静的クラスとして使用する + * インスタンス化させない + */ + private constructor() {} +} + +export class Option { + logFunction: Live2DCubismCore.csmLogFunction; // ログ出力の関数オブジェクト + loggingLevel: LogLevel; // ログ出力レベルの設定 +} + +/** + * ログ出力のレベル + */ +export enum LogLevel { + LogLevel_Verbose = 0, // 詳細ログ + LogLevel_Debug, // デバッグログ + LogLevel_Info, // Infoログ + LogLevel_Warning, // 警告ログ + LogLevel_Error, // エラーログ + LogLevel_Off // ログ出力無効 +} + +// Namespace definition for compatibility. +import * as $ from './live2dcubismframework'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const Constant = $.Constant; + export const csmDelete = $.csmDelete; + export const CubismFramework = $.CubismFramework; + export type CubismFramework = $.CubismFramework; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismmath.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismmath.ts new file mode 100644 index 000000000..fef3b6b54 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismmath.ts @@ -0,0 +1,334 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismVector2 } from './cubismvector2'; + +/** + * 数値計算などに使用するユーティリティクラス + */ +export class CubismMath { + static readonly Epsilon: number = 0.00001; + + /** + * 第一引数の値を最小値と最大値の範囲に収めた値を返す + * + * @param value 収められる値 + * @param min 範囲の最小値 + * @param max 範囲の最大値 + * @return 最小値と最大値の範囲に収めた値 + */ + static range(value: number, min: number, max: number): number { + if (value < min) { + value = min; + } else if (value > max) { + value = max; + } + + return value; + } + + /** + * サイン関数の値を求める + * + * @param x 角度値(ラジアン) + * @return サイン関数sin(x)の値 + */ + static sin(x: number): number { + return Math.sin(x); + } + + /** + * コサイン関数の値を求める + * + * @param x 角度値(ラジアン) + * @return コサイン関数cos(x)の値 + */ + static cos(x: number): number { + return Math.cos(x); + } + + /** + * 値の絶対値を求める + * + * @param x 絶対値を求める値 + * @return 値の絶対値 + */ + static abs(x: number): number { + return Math.abs(x); + } + + /** + * 平方根(ルート)を求める + * @param x -> 平方根を求める値 + * @return 値の平方根 + */ + static sqrt(x: number): number { + return Math.sqrt(x); + } + + /** + * 立方根を求める + * @param x -> 立方根を求める値 + * @return 値の立方根 + */ + static cbrt(x: number): number { + if (x === 0) { + return x; + } + + let cx: number = x; + const isNegativeNumber: boolean = cx < 0; + + if (isNegativeNumber) { + cx = -cx; + } + + let ret: number; + if (cx === Infinity) { + ret = Infinity; + } else { + ret = Math.exp(Math.log(cx) / 3); + ret = (cx / (ret * ret) + 2 * ret) / 3; + } + return isNegativeNumber ? -ret : ret; + } + + /** + * イージング処理されたサインを求める + * フェードイン・アウト時のイージングに利用できる + * + * @param value イージングを行う値 + * @return イージング処理されたサイン値 + */ + static getEasingSine(value: number): number { + if (value < 0.0) { + return 0.0; + } else if (value > 1.0) { + return 1.0; + } + + return 0.5 - 0.5 * this.cos(value * Math.PI); + } + + /** + * 大きい方の値を返す + * + * @param left 左辺の値 + * @param right 右辺の値 + * @return 大きい方の値 + */ + static max(left: number, right: number): number { + return left > right ? left : right; + } + + /** + * 小さい方の値を返す + * + * @param left 左辺の値 + * @param right 右辺の値 + * @return 小さい方の値 + */ + static min(left: number, right: number): number { + return left > right ? right : left; + } + + /** + * 角度値をラジアン値に変換する + * + * @param degrees 角度値 + * @return 角度値から変換したラジアン値 + */ + static degreesToRadian(degrees: number): number { + return (degrees / 180.0) * Math.PI; + } + + /** + * ラジアン値を角度値に変換する + * + * @param radian ラジアン値 + * @return ラジアン値から変換した角度値 + */ + static radianToDegrees(radian: number): number { + return (radian * 180.0) / Math.PI; + } + + /** + * 2つのベクトルからラジアン値を求める + * + * @param from 始点ベクトル + * @param to 終点ベクトル + * @return ラジアン値から求めた方向ベクトル + */ + static directionToRadian(from: CubismVector2, to: CubismVector2): number { + const q1: number = Math.atan2(to.y, to.x); + const q2: number = Math.atan2(from.y, from.x); + + let ret: number = q1 - q2; + + while (ret < -Math.PI) { + ret += Math.PI * 2.0; + } + + while (ret > Math.PI) { + ret -= Math.PI * 2.0; + } + + return ret; + } + + /** + * 2つのベクトルから角度値を求める + * + * @param from 始点ベクトル + * @param to 終点ベクトル + * @return 角度値から求めた方向ベクトル + */ + static directionToDegrees(from: CubismVector2, to: CubismVector2): number { + const radian: number = this.directionToRadian(from, to); + let degree: number = this.radianToDegrees(radian); + + if (to.x - from.x > 0.0) { + degree = -degree; + } + + return degree; + } + + /** + * ラジアン値を方向ベクトルに変換する。 + * + * @param totalAngle ラジアン値 + * @return ラジアン値から変換した方向ベクトル + */ + + static radianToDirection(totalAngle: number): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(); + + ret.x = this.sin(totalAngle); + ret.y = this.cos(totalAngle); + + return ret; + } + + /** + * 三次方程式の三次項の係数が0になったときに補欠的に二次方程式の解をもとめる。 + * a * x^2 + b * x + c = 0 + * + * @param a -> 二次項の係数値 + * @param b -> 一次項の係数値 + * @param c -> 定数項の値 + * @return 二次方程式の解 + */ + static quadraticEquation(a: number, b: number, c: number): number { + if (this.abs(a) < CubismMath.Epsilon) { + if (this.abs(b) < CubismMath.Epsilon) { + return -c; + } + return -c / b; + } + + return -(b + this.sqrt(b * b - 4.0 * a * c)) / (2.0 * a); + } + + /** + * カルダノの公式によってベジェのt値に該当する3次方程式の解を求める。 + * 重解になったときには0.0~1.0の値になる解を返す。 + * + * a * x^3 + b * x^2 + c * x + d = 0 + * + * @param a -> 三次項の係数値 + * @param b -> 二次項の係数値 + * @param c -> 一次項の係数値 + * @param d -> 定数項の値 + * @return 0.0~1.0の間にある解 + */ + static cardanoAlgorithmForBezier( + a: number, + b: number, + c: number, + d: number + ): number { + if (this.sqrt(a) < CubismMath.Epsilon) { + return this.range(this.quadraticEquation(b, c, d), 0.0, 1.0); + } + + const ba: number = b / a; + const ca: number = c / a; + const da: number = d / a; + + const p: number = (3.0 * ca - ba * ba) / 3.0; + const p3: number = p / 3.0; + const q: number = (2.0 * ba * ba * ba - 9.0 * ba * ca + 27.0 * da) / 27.0; + const q2: number = q / 2.0; + const discriminant: number = q2 * q2 + p3 * p3 * p3; + + const center = 0.5; + const threshold: number = center + 0.01; + + if (discriminant < 0.0) { + const mp3: number = -p / 3.0; + const mp33: number = mp3 * mp3 * mp3; + const r: number = this.sqrt(mp33); + const t: number = -q / (2.0 * r); + const cosphi: number = this.range(t, -1.0, 1.0); + const phi: number = Math.acos(cosphi); + const crtr: number = this.cbrt(r); + const t1: number = 2.0 * crtr; + + const root1: number = t1 * this.cos(phi / 3.0) - ba / 3.0; + if (this.abs(root1 - center) < threshold) { + return this.range(root1, 0.0, 1.0); + } + + const root2: number = + t1 * this.cos((phi + 2.0 * Math.PI) / 3.0) - ba / 3.0; + if (this.abs(root2 - center) < threshold) { + return this.range(root2, 0.0, 1.0); + } + + const root3: number = + t1 * this.cos((phi + 4.0 * Math.PI) / 3.0) - ba / 3.0; + return this.range(root3, 0.0, 1.0); + } + + if (discriminant == 0.0) { + let u1: number; + if (q2 < 0.0) { + u1 = this.cbrt(-q2); + } else { + u1 = -this.cbrt(q2); + } + + const root1: number = 2.0 * u1 - ba / 3.0; + if (this.abs(root1 - center) < threshold) { + return this.range(root1, 0.0, 1.0); + } + + const root2: number = -u1 - ba / 3.0; + return this.range(root2, 0.0, 1.0); + } + + const sd: number = this.sqrt(discriminant); + const u1: number = this.cbrt(sd - q2); + const v1: number = this.cbrt(sd + q2); + const root1: number = u1 - v1 - ba / 3.0; + return this.range(root1, 0.0, 1.0); + } + + /** + * コンストラクタ + */ + private constructor() {} +} + +// Namespace definition for compatibility. +import * as $ from './cubismmath'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMath = $.CubismMath; + export type CubismMath = $.CubismMath; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismmatrix44.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismmatrix44.ts new file mode 100644 index 000000000..eba595bde --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismmatrix44.ts @@ -0,0 +1,314 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * 4x4の行列 + * + * 4x4行列の便利クラス。 + */ +export class CubismMatrix44 { + /** + * コンストラクタ + */ + public constructor() { + this._tr = new Float32Array(16); // 4 * 4のサイズ + this.loadIdentity(); + } + + /** + * 受け取った2つの行列の乗算を行う。 + * + * @param a 行列a + * @param b 行列b + * @return 乗算結果の行列 + */ + public static multiply( + a: Float32Array, + b: Float32Array, + dst: Float32Array + ): void { + const c: Float32Array = new Float32Array([ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ]); + + const n = 4; + + for (let i = 0; i < n; ++i) { + for (let j = 0; j < n; ++j) { + for (let k = 0; k < n; ++k) { + c[j + i * 4] += a[k + i * 4] * b[j + k * 4]; + } + } + } + + for (let i = 0; i < 16; ++i) { + dst[i] = c[i]; + } + } + + /** + * 単位行列に初期化する + */ + public loadIdentity(): void { + const c: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ]); + + this.setMatrix(c); + } + + /** + * 行列を設定 + * + * @param tr 16個の浮動小数点数で表される4x4の行列 + */ + public setMatrix(tr: Float32Array): void { + for (let i = 0; i < 16; ++i) { + this._tr[i] = tr[i]; + } + } + + /** + * 行列を浮動小数点数の配列で取得 + * + * @return 16個の浮動小数点数で表される4x4の行列 + */ + public getArray(): Float32Array { + return this._tr; + } + + /** + * X軸の拡大率を取得 + * @return X軸の拡大率 + */ + public getScaleX(): number { + return this._tr[0]; + } + + /** + * Y軸の拡大率を取得する + * + * @return Y軸の拡大率 + */ + public getScaleY(): number { + return this._tr[5]; + } + + /** + * X軸の移動量を取得 + * @return X軸の移動量 + */ + public getTranslateX(): number { + return this._tr[12]; + } + + /** + * Y軸の移動量を取得 + * @return Y軸の移動量 + */ + public getTranslateY(): number { + return this._tr[13]; + } + + /** + * X軸の値を現在の行列で計算 + * + * @param src X軸の値 + * @return 現在の行列で計算されたX軸の値 + */ + public transformX(src: number): number { + return this._tr[0] * src + this._tr[12]; + } + + /** + * Y軸の値を現在の行列で計算 + * + * @param src Y軸の値 + * @return 現在の行列で計算されたY軸の値 + */ + public transformY(src: number): number { + return this._tr[5] * src + this._tr[13]; + } + + /** + * X軸の値を現在の行列で逆計算 + */ + public invertTransformX(src: number): number { + return (src - this._tr[12]) / this._tr[0]; + } + + /** + * Y軸の値を現在の行列で逆計算 + */ + public invertTransformY(src: number): number { + return (src - this._tr[13]) / this._tr[5]; + } + + /** + * 現在の行列の位置を起点にして移動 + * + * 現在の行列の位置を起点にして相対的に移動する。 + * + * @param x X軸の移動量 + * @param y Y軸の移動量 + */ + public translateRelative(x: number, y: number): void { + const tr1: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + x, + y, + 0.0, + 1.0 + ]); + + CubismMatrix44.multiply(tr1, this._tr, this._tr); + } + + /** + * 現在の行列の位置を移動 + * + * 現在の行列の位置を指定した位置へ移動する + * + * @param x X軸の移動量 + * @param y y軸の移動量 + */ + public translate(x: number, y: number): void { + this._tr[12] = x; + this._tr[13] = y; + } + + /** + * 現在の行列のX軸の位置を指定した位置へ移動する + * + * @param x X軸の移動量 + */ + public translateX(x: number): void { + this._tr[12] = x; + } + + /** + * 現在の行列のY軸の位置を指定した位置へ移動する + * + * @param y Y軸の移動量 + */ + public translateY(y: number): void { + this._tr[13] = y; + } + + /** + * 現在の行列の拡大率を相対的に設定する + * + * @param x X軸の拡大率 + * @param y Y軸の拡大率 + */ + public scaleRelative(x: number, y: number): void { + const tr1: Float32Array = new Float32Array([ + x, + 0.0, + 0.0, + 0.0, + 0.0, + y, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ]); + + CubismMatrix44.multiply(tr1, this._tr, this._tr); + } + + /** + * 現在の行列の拡大率を指定した倍率に設定する + * + * @param x X軸の拡大率 + * @param y Y軸の拡大率 + */ + public scale(x: number, y: number): void { + this._tr[0] = x; + this._tr[5] = y; + } + + /** + * 現在の行列に行列を乗算 + * + * @param m 行列 + */ + public multiplyByMatrix(m: CubismMatrix44): void { + CubismMatrix44.multiply(m.getArray(), this._tr, this._tr); + } + + /** + * オブジェクトのコピーを生成する + */ + public clone(): CubismMatrix44 { + const cloneMatrix: CubismMatrix44 = new CubismMatrix44(); + + for (let i = 0; i < this._tr.length; i++) { + cloneMatrix._tr[i] = this._tr[i]; + } + + return cloneMatrix; + } + + protected _tr: Float32Array; // 4x4行列データ +} + +// Namespace definition for compatibility. +import * as $ from './cubismmatrix44'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMatrix44 = $.CubismMatrix44; + export type CubismMatrix44 = $.CubismMatrix44; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismmodelmatrix.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismmodelmatrix.ts new file mode 100644 index 000000000..a17167cc3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismmodelmatrix.ts @@ -0,0 +1,226 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { csmMap, iterator } from '../type/csmmap'; +import { CubismMatrix44 } from './cubismmatrix44'; + +/** + * モデル座標設定用の4x4行列 + * + * モデル座標設定用の4x4行列クラス + */ +export class CubismModelMatrix extends CubismMatrix44 { + /** + * コンストラクタ + * + * @param w 横幅 + * @param h 縦幅 + */ + constructor(w?: number, h?: number) { + super(); + + this._width = w !== undefined ? w : 0.0; + this._height = h !== undefined ? h : 0.0; + + this.setHeight(2.0); + } + + /** + * 横幅を設定 + * + * @param w 横幅 + */ + public setWidth(w: number): void { + const scaleX: number = w / this._width; + const scaleY: number = scaleX; + this.scale(scaleX, scaleY); + } + + /** + * 縦幅を設定 + * @param h 縦幅 + */ + public setHeight(h: number): void { + const scaleX: number = h / this._height; + const scaleY: number = scaleX; + this.scale(scaleX, scaleY); + } + + /** + * 位置を設定 + * + * @param x X軸の位置 + * @param y Y軸の位置 + */ + public setPosition(x: number, y: number): void { + this.translate(x, y); + } + + /** + * 中心位置を設定 + * + * @param x X軸の中心位置 + * @param y Y軸の中心位置 + * + * @note widthかheightを設定したあとでないと、拡大率が正しく取得できないためずれる。 + */ + public setCenterPosition(x: number, y: number) { + this.centerX(x); + this.centerY(y); + } + + /** + * 上辺の位置を設定する + * + * @param y 上辺のY軸位置 + */ + public top(y: number): void { + this.setY(y); + } + + /** + * 下辺の位置を設定する + * + * @param y 下辺のY軸位置 + */ + public bottom(y: number) { + const h: number = this._height * this.getScaleY(); + + this.translateY(y - h); + } + + /** + * 左辺の位置を設定 + * + * @param x 左辺のX軸位置 + */ + public left(x: number): void { + this.setX(x); + } + + /** + * 右辺の位置を設定 + * + * @param x 右辺のX軸位置 + */ + public right(x: number): void { + const w = this._width * this.getScaleX(); + + this.translateX(x - w); + } + + /** + * X軸の中心位置を設定 + * + * @param x X軸の中心位置 + */ + public centerX(x: number): void { + const w = this._width * this.getScaleX(); + + this.translateX(x - w / 2.0); + } + + /** + * X軸の位置を設定 + * + * @param x X軸の位置 + */ + public setX(x: number): void { + this.translateX(x); + } + + /** + * Y軸の中心位置を設定 + * + * @param y Y軸の中心位置 + */ + public centerY(y: number): void { + const h: number = this._height * this.getScaleY(); + + this.translateY(y - h / 2.0); + } + + /** + * Y軸の位置を設定する + * + * @param y Y軸の位置 + */ + public setY(y: number): void { + this.translateY(y); + } + + /** + * レイアウト情報から位置を設定 + * + * @param layout レイアウト情報 + */ + public setupFromLayout(layout: csmMap): void { + const keyWidth = 'width'; + const keyHeight = 'height'; + const keyX = 'x'; + const keyY = 'y'; + const keyCenterX = 'center_x'; + const keyCenterY = 'center_y'; + const keyTop = 'top'; + const keyBottom = 'bottom'; + const keyLeft = 'left'; + const keyRight = 'right'; + + for ( + const ite: iterator = layout.begin(); + ite.notEqual(layout.end()); + ite.preIncrement() + ) { + const key: string = ite.ptr().first; + const value: number = ite.ptr().second; + + if (key == keyWidth) { + this.setWidth(value); + } else if (key == keyHeight) { + this.setHeight(value); + } + } + + for ( + const ite: iterator = layout.begin(); + ite.notEqual(layout.end()); + ite.preIncrement() + ) { + const key: string = ite.ptr().first; + const value: number = ite.ptr().second; + + if (key == keyX) { + this.setX(value); + } else if (key == keyY) { + this.setY(value); + } else if (key == keyCenterX) { + this.centerX(value); + } else if (key == keyCenterY) { + this.centerY(value); + } else if (key == keyTop) { + this.top(value); + } else if (key == keyBottom) { + this.bottom(value); + } else if (key == keyLeft) { + this.left(value); + } else if (key == keyRight) { + this.right(value); + } + } + } + + private _width: number; // 横幅 + private _height: number; // 縦幅 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmodelmatrix'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismModelMatrix = $.CubismModelMatrix; + export type CubismModelMatrix = $.CubismModelMatrix; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismtargetpoint.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismtargetpoint.ts new file mode 100644 index 000000000..9ee2e5947 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismtargetpoint.ts @@ -0,0 +1,169 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMath } from './cubismmath'; + +const FrameRate = 30; +const Epsilon = 0.01; + +/** + * 顔の向きの制御機能 + * + * 顔の向きの制御機能を提供するクラス。 + */ +export class CubismTargetPoint { + /** + * コンストラクタ + */ + public constructor() { + this._faceTargetX = 0.0; + this._faceTargetY = 0.0; + this._faceX = 0.0; + this._faceY = 0.0; + this._faceVX = 0.0; + this._faceVY = 0.0; + this._lastTimeSeconds = 0.0; + this._userTimeSeconds = 0.0; + } + + /** + * 更新処理 + */ + public update(deltaTimeSeconds: number): void { + // デルタ時間を加算する + this._userTimeSeconds += deltaTimeSeconds; + + // 首を中央から左右に振るときの平均的な速さは 秒速度。加速・減速を考慮して、その2倍を最高速度とする + // 顔の振り具合を、中央(0.0)から、左右は(+-1.0)とする + const faceParamMaxV: number = 40.0 / 10.0; // 7.5秒間に40分移動(5.3/sc) + const maxV: number = (faceParamMaxV * 1.0) / FrameRate; // 1frameあたりに変化できる速度の上限 + + if (this._lastTimeSeconds == 0.0) { + this._lastTimeSeconds = this._userTimeSeconds; + return; + } + + const deltaTimeWeight: number = + (this._userTimeSeconds - this._lastTimeSeconds) * FrameRate; + this._lastTimeSeconds = this._userTimeSeconds; + + // 最高速度になるまでの時間を + const timeToMaxSpeed = 0.15; + const frameToMaxSpeed: number = timeToMaxSpeed * FrameRate; // sec * frame/sec + const maxA: number = (deltaTimeWeight * maxV) / frameToMaxSpeed; // 1frameあたりの加速度 + + // 目指す向きは、(dx, dy)方向のベクトルとなる + const dx: number = this._faceTargetX - this._faceX; + const dy: number = this._faceTargetY - this._faceY; + + if (CubismMath.abs(dx) <= Epsilon && CubismMath.abs(dy) <= Epsilon) { + return; // 変化なし + } + + // 速度の最大よりも大きい場合は、速度を落とす + const d: number = CubismMath.sqrt(dx * dx + dy * dy); + + // 進行方向の最大速度ベクトル + const vx: number = (maxV * dx) / d; + const vy: number = (maxV * dy) / d; + + // 現在の速度から、新規速度への変化(加速度)を求める + let ax: number = vx - this._faceVX; + let ay: number = vy - this._faceVY; + + const a: number = CubismMath.sqrt(ax * ax + ay * ay); + + // 加速のとき + if (a < -maxA || a > maxA) { + ax *= maxA / a; + ay *= maxA / a; + } + + // 加速度を元の速度に足して、新速度とする + this._faceVX += ax; + this._faceVY += ay; + + // 目的の方向に近づいたとき、滑らかに減速するための処理 + // 設定された加速度で止まる事の出来る距離と速度の関係から + // 現在とりうる最高速度を計算し、それ以上の時は速度を落とす + // ※本来、人間は筋力で力(加速度)を調整できるため、より自由度が高いが、簡単な処理で済ませている + { + // 加速度、速度、距離の関係式。 + // 2 6 2 3 + // sqrt(a t + 16 a h t - 8 a h) - a t + // v = -------------------------------------- + // 2 + // 4 t - 2 + // (t=1) + // 時刻tは、あらかじめ加速度、速度を1/60(フレームレート、単位なし)で + // 考えているので、t=1として消してよい(※未検証) + + const maxV: number = + 0.5 * + (CubismMath.sqrt(maxA * maxA + 16.0 * maxA * d - 8.0 * maxA * d) - + maxA); + const curV: number = CubismMath.sqrt( + this._faceVX * this._faceVX + this._faceVY * this._faceVY + ); + + if (curV > maxV) { + // 現在の速度 > 最高速度のとき、最高速度まで減速 + this._faceVX *= maxV / curV; + this._faceVY *= maxV / curV; + } + } + + this._faceX += this._faceVX; + this._faceY += this._faceVY; + } + + /** + * X軸の顔の向きの値を取得 + * + * @return X軸の顔の向きの値(-1.0 ~ 1.0) + */ + public getX(): number { + return this._faceX; + } + + /** + * Y軸の顔の向きの値を取得 + * + * @return Y軸の顔の向きの値(-1.0 ~ 1.0) + */ + public getY(): number { + return this._faceY; + } + + /** + * 顔の向きの目標値を設定 + * + * @param x X軸の顔の向きの値(-1.0 ~ 1.0) + * @param y Y軸の顔の向きの値(-1.0 ~ 1.0) + */ + public set(x: number, y: number): void { + this._faceTargetX = x; + this._faceTargetY = y; + } + + private _faceTargetX: number; // 顔の向きのX目標値(この値に近づいていく) + private _faceTargetY: number; // 顔の向きのY目標値(この値に近づいていく) + private _faceX: number; // 顔の向きX(-1.0 ~ 1.0) + private _faceY: number; // 顔の向きY(-1.0 ~ 1.0) + private _faceVX: number; // 顔の向きの変化速度X + private _faceVY: number; // 顔の向きの変化速度Y + private _lastTimeSeconds: number; // 最後の実行時間[秒] + private _userTimeSeconds: number; // デルタ時間の積算値[秒] +} + +// Namespace definition for compatibility. +import * as $ from './cubismtargetpoint'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismTargetPoint = $.CubismTargetPoint; + export type CubismTargetPoint = $.CubismTargetPoint; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismvector2.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismvector2.ts new file mode 100644 index 000000000..1a7dc3d49 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismvector2.ts @@ -0,0 +1,169 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * 2次元ベクトル型 + * + * 2次元ベクトル型の機能を提供する。 + */ +export class CubismVector2 { + /** + * コンストラクタ + */ + public constructor(public x?: number, public y?: number) { + this.x = x == undefined ? 0.0 : x; + + this.y = y == undefined ? 0.0 : y; + } + + /** + * ベクトルの加算 + * + * @param vector2 加算するベクトル値 + * @return 加算結果 ベクトル値 + */ + public add(vector2: CubismVector2): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0.0, 0.0); + ret.x = this.x + vector2.x; + ret.y = this.y + vector2.y; + return ret; + } + + /** + * ベクトルの減算 + * + * @param vector2 減算するベクトル値 + * @return 減算結果 ベクトル値 + */ + public substract(vector2: CubismVector2): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0.0, 0.0); + ret.x = this.x - vector2.x; + ret.y = this.y - vector2.y; + return ret; + } + + /** + * ベクトルの乗算 + * + * @param vector2 乗算するベクトル値 + * @return 乗算結果 ベクトル値 + */ + public multiply(vector2: CubismVector2): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0.0, 0.0); + ret.x = this.x * vector2.x; + ret.y = this.y * vector2.y; + return ret; + } + + /** + * ベクトルの乗算(スカラー) + * + * @param scalar 乗算するスカラー値 + * @return 乗算結果 ベクトル値 + */ + public multiplyByScaler(scalar: number): CubismVector2 { + return this.multiply(new CubismVector2(scalar, scalar)); + } + + /** + * ベクトルの除算 + * + * @param vector2 除算するベクトル値 + * @return 除算結果 ベクトル値 + */ + public division(vector2: CubismVector2): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0.0, 0.0); + ret.x = this.x / vector2.x; + ret.y = this.y / vector2.y; + return ret; + } + + /** + * ベクトルの除算(スカラー) + * + * @param scalar 除算するスカラー値 + * @return 除算結果 ベクトル値 + */ + public divisionByScalar(scalar: number): CubismVector2 { + return this.division(new CubismVector2(scalar, scalar)); + } + + /** + * ベクトルの長さを取得する + * + * @return ベクトルの長さ + */ + public getLength(): number { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + + /** + * ベクトルの距離の取得 + * + * @param a 点 + * @return ベクトルの距離 + */ + public getDistanceWith(a: CubismVector2): number { + return Math.sqrt( + (this.x - a.x) * (this.x - a.x) + (this.y - a.y) * (this.y - a.y) + ); + } + + /** + * ドット積の計算 + * + * @param a 値 + * @return 結果 + */ + public dot(a: CubismVector2): number { + return this.x * a.x + this.y * a.y; + } + + /** + * 正規化の適用 + */ + public normalize(): void { + const length: number = Math.pow(this.x * this.x + this.y * this.y, 0.5); + + this.x = this.x / length; + this.y = this.y / length; + } + + /** + * 等しさの確認(等しいか?) + * + * 値が等しいか? + * + * @param rhs 確認する値 + * @return true 値は等しい + * @return false 値は等しくない + */ + public isEqual(rhs: CubismVector2): boolean { + return this.x == rhs.x && this.y == rhs.y; + } + + /** + * 等しさの確認(等しくないか?) + * + * 値が等しくないか? + * + * @param rhs 確認する値 + * @return true 値は等しくない + * @return false 値は等しい + */ + public isNotEqual(rhs: CubismVector2): boolean { + return !this.isEqual(rhs); + } +} + +// Namespace definition for compatibility. +import * as $ from './cubismvector2'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismVector2 = $.CubismVector2; + export type CubismVector2 = $.CubismVector2; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismviewmatrix.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismviewmatrix.ts new file mode 100644 index 000000000..c81a05a9b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/math/cubismviewmatrix.ts @@ -0,0 +1,339 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMatrix44 } from './cubismmatrix44'; + +/** + * カメラの位置変更に使うと便利な4x4行列 + * + * カメラの位置変更に使うと便利な4x4行列のクラス。 + */ +export class CubismViewMatrix extends CubismMatrix44 { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._screenLeft = 0.0; + this._screenRight = 0.0; + this._screenTop = 0.0; + this._screenBottom = 0.0; + this._maxLeft = 0.0; + this._maxRight = 0.0; + this._maxTop = 0.0; + this._maxBottom = 0.0; + this._maxScale = 0.0; + this._minScale = 0.0; + } + + /** + * 移動を調整 + * + * @param x X軸の移動量 + * @param y Y軸の移動量 + */ + public adjustTranslate(x: number, y: number): void { + if (this._tr[0] * this._maxLeft + (this._tr[12] + x) > this._screenLeft) { + x = this._screenLeft - this._tr[0] * this._maxLeft - this._tr[12]; + } + + if (this._tr[0] * this._maxRight + (this._tr[12] + x) < this._screenRight) { + x = this._screenRight - this._tr[0] * this._maxRight - this._tr[12]; + } + + if (this._tr[5] * this._maxTop + (this._tr[13] + y) < this._screenTop) { + y = this._screenTop - this._tr[5] * this._maxTop - this._tr[13]; + } + + if ( + this._tr[5] * this._maxBottom + (this._tr[13] + y) > + this._screenBottom + ) { + y = this._screenBottom - this._tr[5] * this._maxBottom - this._tr[13]; + } + + const tr1: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + x, + y, + 0.0, + 1.0 + ]); + + CubismMatrix44.multiply(tr1, this._tr, this._tr); + } + + /** + * 拡大率を調整 + * + * @param cx 拡大を行うX軸の中心位置 + * @param cy 拡大を行うY軸の中心位置 + * @param scale 拡大率 + */ + public adjustScale(cx: number, cy: number, scale: number): void { + const maxScale: number = this.getMaxScale(); + const minScale: number = this.getMinScale(); + + const targetScale = scale * this._tr[0]; + + if (targetScale < minScale) { + if (this._tr[0] > 0.0) { + scale = minScale / this._tr[0]; + } + } else if (targetScale > maxScale) { + if (this._tr[0] > 0.0) { + scale = maxScale / this._tr[0]; + } + } + + const tr1: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + cx, + cy, + 0.0, + 1.0 + ]); + + const tr2: Float32Array = new Float32Array([ + scale, + 0.0, + 0.0, + 0.0, + 0.0, + scale, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ]); + + const tr3: Float32Array = new Float32Array([ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + -cx, + -cy, + 0.0, + 1.0 + ]); + + CubismMatrix44.multiply(tr3, this._tr, this._tr); + CubismMatrix44.multiply(tr2, this._tr, this._tr); + CubismMatrix44.multiply(tr1, this._tr, this._tr); + } + + /** + * デバイスに対応する論理座養生の範囲の設定 + * + * @param left 左辺のX軸の位置 + * @param right 右辺のX軸の位置 + * @param bottom 下辺のY軸の位置 + * @param top 上辺のY軸の位置 + */ + public setScreenRect( + left: number, + right: number, + bottom: number, + top: number + ): void { + this._screenLeft = left; + this._screenRight = right; + this._screenBottom = bottom; + this._screenTop = top; + } + + /** + * デバイスに対応する論理座標上の移動可能範囲の設定 + * @param left 左辺のX軸の位置 + * @param right 右辺のX軸の位置 + * @param bottom 下辺のY軸の位置 + * @param top 上辺のY軸の位置 + */ + public setMaxScreenRect( + left: number, + right: number, + bottom: number, + top: number + ): void { + this._maxLeft = left; + this._maxRight = right; + this._maxTop = top; + this._maxBottom = bottom; + } + + /** + * 最大拡大率の設定 + * @param maxScale 最大拡大率 + */ + public setMaxScale(maxScale: number): void { + this._maxScale = maxScale; + } + + /** + * 最小拡大率の設定 + * @param minScale 最小拡大率 + */ + public setMinScale(minScale: number): void { + this._minScale = minScale; + } + + /** + * 最大拡大率の取得 + * @return 最大拡大率 + */ + public getMaxScale(): number { + return this._maxScale; + } + + /** + * 最小拡大率の取得 + * @return 最小拡大率 + */ + public getMinScale(): number { + return this._minScale; + } + + /** + * 拡大率が最大になっているかを確認する + * + * @return true 拡大率は最大 + * @return false 拡大率は最大ではない + */ + public isMaxScale(): boolean { + return this.getScaleX() >= this._maxScale; + } + + /** + * 拡大率が最小になっているかを確認する + * + * @return true 拡大率は最小 + * @return false 拡大率は最小ではない + */ + public isMinScale(): boolean { + return this.getScaleX() <= this._minScale; + } + + /** + * デバイスに対応する論理座標の左辺のX軸位置を取得する + * @return デバイスに対応する論理座標の左辺のX軸位置 + */ + public getScreenLeft(): number { + return this._screenLeft; + } + + /** + * デバイスに対応する論理座標の右辺のX軸位置を取得する + * @return デバイスに対応する論理座標の右辺のX軸位置 + */ + public getScreenRight(): number { + return this._screenRight; + } + + /** + * デバイスに対応する論理座標の下辺のY軸位置を取得する + * @return デバイスに対応する論理座標の下辺のY軸位置 + */ + public getScreenBottom(): number { + return this._screenBottom; + } + + /** + * デバイスに対応する論理座標の上辺のY軸位置を取得する + * @return デバイスに対応する論理座標の上辺のY軸位置 + */ + public getScreenTop(): number { + return this._screenTop; + } + + /** + * 左辺のX軸位置の最大値の取得 + * @return 左辺のX軸位置の最大値 + */ + public getMaxLeft(): number { + return this._maxLeft; + } + + /** + * 右辺のX軸位置の最大値の取得 + * @return 右辺のX軸位置の最大値 + */ + public getMaxRight(): number { + return this._maxRight; + } + + /** + * 下辺のY軸位置の最大値の取得 + * @return 下辺のY軸位置の最大値 + */ + public getMaxBottom(): number { + return this._maxBottom; + } + + /** + * 上辺のY軸位置の最大値の取得 + * @return 上辺のY軸位置の最大値 + */ + public getMaxTop(): number { + return this._maxTop; + } + + private _screenLeft: number; // デバイスに対応する論理座標上の範囲(左辺X軸位置) + private _screenRight: number; // デバイスに対応する論理座標上の範囲(右辺X軸位置) + private _screenTop: number; // デバイスに対応する論理座標上の範囲(上辺Y軸位置) + private _screenBottom: number; // デバイスに対応する論理座標上の範囲(下辺Y軸位置) + private _maxLeft: number; // 論理座標上の移動可能範囲(左辺X軸位置) + private _maxRight: number; // 論理座標上の移動可能範囲(右辺X軸位置) + private _maxTop: number; // 論理座標上の移動可能範囲(上辺Y軸位置) + private _maxBottom: number; // 論理座標上の移動可能範囲(下辺Y軸位置) + private _maxScale: number; // 拡大率の最大値 + private _minScale: number; // 拡大率の最小値 +} + +// Namespace definition for compatibility. +import * as $ from './cubismviewmatrix'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismViewMatrix = $.CubismViewMatrix; + export type CubismViewMatrix = $.CubismViewMatrix; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmoc.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmoc.ts new file mode 100644 index 000000000..5e2acecfd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmoc.ts @@ -0,0 +1,105 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CSM_ASSERT } from '../utils/cubismdebug'; +import { CubismModel } from './cubismmodel'; + +/** + * Mocデータの管理 + * + * Mocデータの管理を行うクラス。 + */ +export class CubismMoc { + /** + * Mocデータの作成 + */ + public static create(mocBytes: ArrayBuffer): CubismMoc { + let cubismMoc: CubismMoc = null; + const moc: Live2DCubismCore.Moc = Live2DCubismCore.Moc.fromArrayBuffer( + mocBytes + ); + + if (moc) { + cubismMoc = new CubismMoc(moc); + } + + return cubismMoc; + } + + /** + * Mocデータを削除 + * + * Mocデータを削除する + */ + public static delete(moc: CubismMoc): void { + moc._moc._release(); + moc._moc = null; + moc = null; + } + + /** + * モデルを作成する + * + * @return Mocデータから作成されたモデル + */ + createModel(): CubismModel { + let cubismModel: CubismModel = null; + + const model: Live2DCubismCore.Model = Live2DCubismCore.Model.fromMoc( + this._moc + ); + + if (model) { + cubismModel = new CubismModel(model); + cubismModel.initialize(); + + ++this._modelCount; + } + + return cubismModel; + } + + /** + * モデルを削除する + */ + deleteModel(model: CubismModel): void { + if (model != null) { + model.release(); + model = null; + --this._modelCount; + } + } + + /** + * コンストラクタ + */ + private constructor(moc: Live2DCubismCore.Moc) { + this._moc = moc; + this._modelCount = 0; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CSM_ASSERT(this._modelCount == 0); + + this._moc._release(); + this._moc = null; + } + + _moc: Live2DCubismCore.Moc; // Mocデータ + _modelCount: number; // Mocデータから作られたモデルの個数 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmoc'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMoc = $.CubismMoc; + export type CubismMoc = $.CubismMoc; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmodel.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmodel.ts new file mode 100644 index 000000000..5cfb14a1b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmodel.ts @@ -0,0 +1,818 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CubismBlendMode } from '../rendering/cubismrenderer'; +import { csmMap } from '../type/csmmap'; +import { csmVector } from '../type/csmvector'; +import { CSM_ASSERT } from '../utils/cubismdebug'; + +/** + * モデル + * + * Mocデータから生成されるモデルのクラス。 + */ +export class CubismModel { + /** + * モデルのパラメータの更新 + */ + public update(): void { + // Update model + this._model.update(); + + this._model.drawables.resetDynamicFlags(); + } + + /** + * キャンバスの幅を取得する + */ + public getCanvasWidth(): number { + if (this._model == null) { + return 0.0; + } + + return ( + this._model.canvasinfo.CanvasWidth / this._model.canvasinfo.PixelsPerUnit + ); + } + + /** + * キャンバスの高さを取得する + */ + public getCanvasHeight(): number { + if (this._model == null) { + return 0.0; + } + + return ( + this._model.canvasinfo.CanvasHeight / this._model.canvasinfo.PixelsPerUnit + ); + } + + /** + * パラメータを保存する + */ + public saveParameters(): void { + const parameterCount: number = this._model.parameters.count; + const savedParameterCount: number = this._savedParameters.getSize(); + + for (let i = 0; i < parameterCount; ++i) { + if (i < savedParameterCount) { + this._savedParameters.set(i, this._parameterValues[i]); + } else { + this._savedParameters.pushBack(this._parameterValues[i]); + } + } + } + + /** + * モデルを取得 + */ + public getModel(): Live2DCubismCore.Model { + return this._model; + } + + /** + * パーツのインデックスを取得 + * @param partId パーツのID + * @return パーツのインデックス + */ + public getPartIndex(partId: CubismIdHandle): number { + let partIndex: number; + const partCount: number = this._model.parts.count; + + for (partIndex = 0; partIndex < partCount; ++partIndex) { + if (partId == this._partIds.at(partIndex)) { + return partIndex; + } + } + + // モデルに存在していない場合、非存在パーツIDリスト内にあるかを検索し、そのインデックスを返す + if (this._notExistPartId.isExist(partId)) { + return this._notExistPartId.getValue(partId); + } + + // 非存在パーツIDリストにない場合、新しく要素を追加する + partIndex = partCount + this._notExistPartId.getSize(); + this._notExistPartId.setValue(partId, partIndex); + this._notExistPartOpacities.appendKey(partIndex); + + return partIndex; + } + + /** + * パーツの個数の取得 + * @return パーツの個数 + */ + public getPartCount(): number { + const partCount: number = this._model.parts.count; + return partCount; + } + + /** + * パーツの不透明度の設定(Index) + * @param partIndex パーツのインデックス + * @param opacity 不透明度 + */ + public setPartOpacityByIndex(partIndex: number, opacity: number): void { + if (this._notExistPartOpacities.isExist(partIndex)) { + this._notExistPartOpacities.setValue(partIndex, opacity); + return; + } + + // インデックスの範囲内検知 + CSM_ASSERT(0 <= partIndex && partIndex < this.getPartCount()); + + this._partOpacities[partIndex] = opacity; + } + + /** + * パーツの不透明度の設定(Id) + * @param partId パーツのID + * @param opacity パーツの不透明度 + */ + public setPartOpacityById(partId: CubismIdHandle, opacity: number): void { + // 高速化のためにPartIndexを取得できる機構になっているが、外部からの設定の時は呼び出し頻度が低いため不要 + const index: number = this.getPartIndex(partId); + + if (index < 0) { + return; // パーツがないのでスキップ + } + + this.setPartOpacityByIndex(index, opacity); + } + + /** + * パーツの不透明度の取得(index) + * @param partIndex パーツのインデックス + * @return パーツの不透明度 + */ + public getPartOpacityByIndex(partIndex: number): number { + if (this._notExistPartOpacities.isExist(partIndex)) { + // モデルに存在しないパーツIDの場合、非存在パーツリストから不透明度を返す。 + return this._notExistPartOpacities.getValue(partIndex); + } + + // インデックスの範囲内検知 + CSM_ASSERT(0 <= partIndex && partIndex < this.getPartCount()); + + return this._partOpacities[partIndex]; + } + + /** + * パーツの不透明度の取得(id) + * @param partId パーツのId + * @return パーツの不透明度 + */ + public getPartOpacityById(partId: CubismIdHandle): number { + // 高速化のためにPartIndexを取得できる機構になっているが、外部からの設定の時は呼び出し頻度が低いため不要 + const index: number = this.getPartIndex(partId); + + if (index < 0) { + return 0; // パーツが無いのでスキップ + } + + return this.getPartOpacityByIndex(index); + } + + /** + * パラメータのインデックスの取得 + * @param パラメータID + * @return パラメータのインデックス + */ + public getParameterIndex(parameterId: CubismIdHandle): number { + let parameterIndex: number; + const idCount: number = this._model.parameters.count; + + for (parameterIndex = 0; parameterIndex < idCount; ++parameterIndex) { + if (parameterId != this._parameterIds.at(parameterIndex)) { + continue; + } + + return parameterIndex; + } + + // モデルに存在していない場合、非存在パラメータIDリスト内を検索し、そのインデックスを返す + if (this._notExistParameterId.isExist(parameterId)) { + return this._notExistParameterId.getValue(parameterId); + } + + // 非存在パラメータIDリストにない場合新しく要素を追加する + parameterIndex = + this._model.parameters.count + this._notExistParameterId.getSize(); + + this._notExistParameterId.setValue(parameterId, parameterIndex); + this._notExistParameterValues.appendKey(parameterIndex); + + return parameterIndex; + } + + /** + * パラメータの個数の取得 + * @return パラメータの個数 + */ + public getParameterCount(): number { + return this._model.parameters.count; + } + + /** + * パラメータの最大値の取得 + * @param parameterIndex パラメータのインデックス + * @return パラメータの最大値 + */ + public getParameterMaximumValue(parameterIndex: number): number { + return this._model.parameters.maximumValues[parameterIndex]; + } + + /** + * パラメータの最小値の取得 + * @param parameterIndex パラメータのインデックス + * @return パラメータの最小値 + */ + public getParameterMinimumValue(parameterIndex: number): number { + return this._model.parameters.minimumValues[parameterIndex]; + } + + /** + * パラメータのデフォルト値の取得 + * @param parameterIndex パラメータのインデックス + * @return パラメータのデフォルト値 + */ + public getParameterDefaultValue(parameterIndex: number): number { + return this._model.parameters.defaultValues[parameterIndex]; + } + + /** + * パラメータの値の取得 + * @param parameterIndex パラメータのインデックス + * @return パラメータの値 + */ + public getParameterValueByIndex(parameterIndex: number): number { + if (this._notExistParameterValues.isExist(parameterIndex)) { + return this._notExistParameterValues.getValue(parameterIndex); + } + + // インデックスの範囲内検知 + CSM_ASSERT( + 0 <= parameterIndex && parameterIndex < this.getParameterCount() + ); + + return this._parameterValues[parameterIndex]; + } + + /** + * パラメータの値の取得 + * @param parameterId パラメータのID + * @return パラメータの値 + */ + public getParameterValueById(parameterId: CubismIdHandle): number { + // 高速化のためにparameterIndexを取得できる機構になっているが、外部からの設定の時は呼び出し頻度が低いため不要 + const parameterIndex: number = this.getParameterIndex(parameterId); + return this.getParameterValueByIndex(parameterIndex); + } + + /** + * パラメータの値の設定 + * @param parameterIndex パラメータのインデックス + * @param value パラメータの値 + * @param weight 重み + */ + public setParameterValueByIndex( + parameterIndex: number, + value: number, + weight = 1.0 + ): void { + if (this._notExistParameterValues.isExist(parameterIndex)) { + this._notExistParameterValues.setValue( + parameterIndex, + weight == 1 + ? value + : this._notExistParameterValues.getValue(parameterIndex) * + (1 - weight) + + value * weight + ); + + return; + } + + // インデックスの範囲内検知 + CSM_ASSERT( + 0 <= parameterIndex && parameterIndex < this.getParameterCount() + ); + + if (this._model.parameters.maximumValues[parameterIndex] < value) { + value = this._model.parameters.maximumValues[parameterIndex]; + } + if (this._model.parameters.minimumValues[parameterIndex] > value) { + value = this._model.parameters.minimumValues[parameterIndex]; + } + + this._parameterValues[parameterIndex] = + weight == 1 + ? value + : (this._parameterValues[parameterIndex] = + this._parameterValues[parameterIndex] * (1 - weight) + + value * weight); + } + + /** + * パラメータの値の設定 + * @param parameterId パラメータのID + * @param value パラメータの値 + * @param weight 重み + */ + public setParameterValueById( + parameterId: CubismIdHandle, + value: number, + weight = 1.0 + ): void { + const index: number = this.getParameterIndex(parameterId); + this.setParameterValueByIndex(index, value, weight); + } + + /** + * パラメータの値の加算(index) + * @param parameterIndex パラメータインデックス + * @param value 加算する値 + * @param weight 重み + */ + public addParameterValueByIndex( + parameterIndex: number, + value: number, + weight = 1.0 + ): void { + this.setParameterValueByIndex( + parameterIndex, + this.getParameterValueByIndex(parameterIndex) + value * weight + ); + } + + /** + * パラメータの値の加算(id) + * @param parameterId パラメータID + * @param value 加算する値 + * @param weight 重み + */ + public addParameterValueById( + parameterId: any, + value: number, + weight = 1.0 + ): void { + const index: number = this.getParameterIndex(parameterId); + this.addParameterValueByIndex(index, value, weight); + } + + /** + * パラメータの値の乗算 + * @param parameterId パラメータのID + * @param value 乗算する値 + * @param weight 重み + */ + public multiplyParameterValueById( + parameterId: CubismIdHandle, + value: number, + weight = 1.0 + ): void { + const index: number = this.getParameterIndex(parameterId); + this.multiplyParameterValueByIndex(index, value, weight); + } + + /** + * パラメータの値の乗算 + * @param parameterIndex パラメータのインデックス + * @param value 乗算する値 + * @param weight 重み + */ + public multiplyParameterValueByIndex( + parameterIndex: number, + value: number, + weight = 1.0 + ): void { + this.setParameterValueByIndex( + parameterIndex, + this.getParameterValueByIndex(parameterIndex) * + (1.0 + (value - 1.0) * weight) + ); + } + + /** + * Drawableのインデックスの取得 + * @param drawableId DrawableのID + * @return Drawableのインデックス + */ + public getDrawableIndex(drawableId: CubismIdHandle): number { + const drawableCount = this._model.drawables.count; + + for ( + let drawableIndex = 0; + drawableIndex < drawableCount; + ++drawableIndex + ) { + if (this._drawableIds.at(drawableIndex) == drawableId) { + return drawableIndex; + } + } + + return -1; + } + + /** + * Drawableの個数の取得 + * @return drawableの個数 + */ + public getDrawableCount(): number { + const drawableCount = this._model.drawables.count; + return drawableCount; + } + + /** + * DrawableのIDを取得する + * @param drawableIndex Drawableのインデックス + * @return drawableのID + */ + public getDrawableId(drawableIndex: number): CubismIdHandle { + const parameterIds: string[] = this._model.drawables.ids; + return CubismFramework.getIdManager().getId(parameterIds[drawableIndex]); + } + + /** + * Drawableの描画順リストの取得 + * @return Drawableの描画順リスト + */ + public getDrawableRenderOrders(): Int32Array { + const renderOrders: Int32Array = this._model.drawables.renderOrders; + return renderOrders; + } + + /** + * Drawableのテクスチャインデックスリストの取得 + * @param drawableIndex Drawableのインデックス + * @return drawableのテクスチャインデックスリスト + */ + public getDrawableTextureIndices(drawableIndex: number): number { + const textureIndices: Int32Array = this._model.drawables.textureIndices; + return textureIndices[drawableIndex]; + } + + /** + * DrawableのVertexPositionsの変化情報の取得 + * + * 直近のCubismModel.update関数でDrawableの頂点情報が変化したかを取得する。 + * + * @param drawableIndex Drawableのインデックス + * @retval true Drawableの頂点情報が直近のCubismModel.update関数で変化した + * @retval false Drawableの頂点情報が直近のCubismModel.update関数で変化していない + */ + public getDrawableDynamicFlagVertexPositionsDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasVertexPositionsDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * Drawableの頂点インデックスの個数の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点インデックスの個数 + */ + public getDrawableVertexIndexCount(drawableIndex: number): number { + const indexCounts: Int32Array = this._model.drawables.indexCounts; + return indexCounts[drawableIndex]; + } + + /** + * Drawableの頂点の個数の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点の個数 + */ + public getDrawableVertexCount(drawableIndex: number): number { + const vertexCounts = this._model.drawables.vertexCounts; + return vertexCounts[drawableIndex]; + } + + /** + * Drawableの頂点リストの取得 + * @param drawableIndex drawableのインデックス + * @return drawableの頂点リスト + */ + public getDrawableVertices(drawableIndex: number): Float32Array { + return this.getDrawableVertexPositions(drawableIndex); + } + + /** + * Drawableの頂点インデックスリストの取得 + * @param drarableIndex Drawableのインデックス + * @return drawableの頂点インデックスリスト + */ + public getDrawableVertexIndices(drawableIndex: number): Uint16Array { + const indicesArray: Uint16Array[] = this._model.drawables.indices; + return indicesArray[drawableIndex]; + } + + /** + * Drawableの頂点リストの取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点リスト + */ + public getDrawableVertexPositions(drawableIndex: number): Float32Array { + const verticesArray: Float32Array[] = this._model.drawables.vertexPositions; + return verticesArray[drawableIndex]; + } + + /** + * Drawableの頂点のUVリストの取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの頂点UVリスト + */ + public getDrawableVertexUvs(drawableIndex: number): Float32Array { + const uvsArray: Float32Array[] = this._model.drawables.vertexUvs; + return uvsArray[drawableIndex]; + } + + /** + * Drawableの不透明度の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableの不透明度 + */ + public getDrawableOpacity(drawableIndex: number): number { + const opacities: Float32Array = this._model.drawables.opacities; + return opacities[drawableIndex]; + } + + /** + * Drawableのカリング情報の取得 + * @param drawableIndex Drawableのインデックス + * @return drawableのカリング情報 + */ + public getDrawableCulling(drawableIndex: number): boolean { + const constantFlags = this._model.drawables.constantFlags; + + return !Live2DCubismCore.Utils.hasIsDoubleSidedBit( + constantFlags[drawableIndex] + ); + } + + /** + * Drawableのブレンドモードを取得 + * @param drawableIndex Drawableのインデックス + * @return drawableのブレンドモード + */ + public getDrawableBlendMode(drawableIndex: number): CubismBlendMode { + const constantFlags = this._model.drawables.constantFlags; + + return Live2DCubismCore.Utils.hasBlendAdditiveBit( + constantFlags[drawableIndex] + ) + ? CubismBlendMode.CubismBlendMode_Additive + : Live2DCubismCore.Utils.hasBlendMultiplicativeBit( + constantFlags[drawableIndex] + ) + ? CubismBlendMode.CubismBlendMode_Multiplicative + : CubismBlendMode.CubismBlendMode_Normal; + } + + /** + * Drawableのマスクの反転使用の取得 + * + * Drawableのマスク使用時の反転設定を取得する。 + * マスクを使用しない場合は無視される。 + * + * @param drawableIndex Drawableのインデックス + * @return Drawableの反転設定 + */ + public getDrawableInvertedMaskBit(drawableIndex: number): boolean { + const constantFlags: Uint8Array = this._model.drawables.constantFlags; + + return Live2DCubismCore.Utils.hasIsInvertedMaskBit( + constantFlags[drawableIndex] + ); + } + + /** + * Drawableのクリッピングマスクリストの取得 + * @return Drawableのクリッピングマスクリスト + */ + public getDrawableMasks(): Int32Array[] { + const masks: Int32Array[] = this._model.drawables.masks; + return masks; + } + + /** + * Drawableのクリッピングマスクの個数リストの取得 + * @return Drawableのクリッピングマスクの個数リスト + */ + public getDrawableMaskCounts(): Int32Array { + const maskCounts: Int32Array = this._model.drawables.maskCounts; + return maskCounts; + } + + /** + * クリッピングマスクの使用状態 + * + * @return true クリッピングマスクを使用している + * @return false クリッピングマスクを使用していない + */ + public isUsingMasking(): boolean { + for (let d = 0; d < this._model.drawables.count; ++d) { + if (this._model.drawables.maskCounts[d] <= 0) { + continue; + } + return true; + } + return false; + } + + /** + * Drawableの表示情報を取得する + * + * @param drawableIndex Drawableのインデックス + * @return true Drawableが表示 + * @return false Drawableが非表示 + */ + public getDrawableDynamicFlagIsVisible(drawableIndex: number): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasIsVisibleBit(dynamicFlags[drawableIndex]); + } + + /** + * DrawableのDrawOrderの変化情報の取得 + * + * 直近のCubismModel.update関数でdrawableのdrawOrderが変化したかを取得する。 + * drawOrderはartMesh上で指定する0から1000の情報 + * @param drawableIndex drawableのインデックス + * @return true drawableの不透明度が直近のCubismModel.update関数で変化した + * @return false drawableの不透明度が直近のCubismModel.update関数で変化している + */ + public getDrawableDynamicFlagVisibilityDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasVisibilityDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * Drawableの不透明度の変化情報の取得 + * + * 直近のCubismModel.update関数でdrawableの不透明度が変化したかを取得する。 + * + * @param drawableIndex drawableのインデックス + * @return true Drawableの不透明度が直近のCubismModel.update関数で変化した + * @return false Drawableの不透明度が直近のCubismModel.update関数で変化してない + */ + public getDrawableDynamicFlagOpacityDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasOpacityDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * Drawableの描画順序の変化情報の取得 + * + * 直近のCubismModel.update関数でDrawableの描画の順序が変化したかを取得する。 + * + * @param drawableIndex Drawableのインデックス + * @return true Drawableの描画の順序が直近のCubismModel.update関数で変化した + * @return false Drawableの描画の順序が直近のCubismModel.update関数で変化してない + */ + public getDrawableDynamicFlagRenderOrderDidChange( + drawableIndex: number + ): boolean { + const dynamicFlags: Uint8Array = this._model.drawables.dynamicFlags; + return Live2DCubismCore.Utils.hasRenderOrderDidChangeBit( + dynamicFlags[drawableIndex] + ); + } + + /** + * 保存されたパラメータの読み込み + */ + public loadParameters(): void { + let parameterCount: number = this._model.parameters.count; + const savedParameterCount: number = this._savedParameters.getSize(); + + if (parameterCount > savedParameterCount) { + parameterCount = savedParameterCount; + } + + for (let i = 0; i < parameterCount; ++i) { + this._parameterValues[i] = this._savedParameters.at(i); + } + } + + /** + * 初期化する + */ + public initialize(): void { + CSM_ASSERT(this._model); + + this._parameterValues = this._model.parameters.values; + this._partOpacities = this._model.parts.opacities; + this._parameterMaximumValues = this._model.parameters.maximumValues; + this._parameterMinimumValues = this._model.parameters.minimumValues; + + { + const parameterIds: string[] = this._model.parameters.ids; + const parameterCount: number = this._model.parameters.count; + + this._parameterIds.prepareCapacity(parameterCount); + for (let i = 0; i < parameterCount; ++i) { + this._parameterIds.pushBack( + CubismFramework.getIdManager().getId(parameterIds[i]) + ); + } + } + + { + const partIds: string[] = this._model.parts.ids; + const partCount: number = this._model.parts.count; + + this._partIds.prepareCapacity(partCount); + for (let i = 0; i < partCount; ++i) { + this._partIds.pushBack( + CubismFramework.getIdManager().getId(partIds[i]) + ); + } + } + + { + const drawableIds: string[] = this._model.drawables.ids; + const drawableCount: number = this._model.drawables.count; + + this._drawableIds.prepareCapacity(drawableCount); + for (let i = 0; i < drawableCount; ++i) { + this._drawableIds.pushBack( + CubismFramework.getIdManager().getId(drawableIds[i]) + ); + } + } + } + + /** + * コンストラクタ + * @param model モデル + */ + public constructor(model: Live2DCubismCore.Model) { + this._model = model; + this._parameterValues = null; + this._parameterMaximumValues = null; + this._parameterMinimumValues = null; + this._partOpacities = null; + this._savedParameters = new csmVector(); + this._parameterIds = new csmVector(); + this._drawableIds = new csmVector(); + this._partIds = new csmVector(); + + this._notExistPartId = new csmMap(); + this._notExistParameterId = new csmMap(); + this._notExistParameterValues = new csmMap(); + this._notExistPartOpacities = new csmMap(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._model.release(); + this._model = null; + } + + private _notExistPartOpacities: csmMap; // 存在していないパーツの不透明度のリスト + private _notExistPartId: csmMap; // 存在していないパーツIDのリスト + + private _notExistParameterValues: csmMap; // 存在していないパラメータの値のリスト + private _notExistParameterId: csmMap; // 存在していないパラメータIDのリスト + + private _savedParameters: csmVector; // 保存されたパラメータ + + private _model: Live2DCubismCore.Model; // モデル + + private _parameterValues: Float32Array; // パラメータの値のリスト + private _parameterMaximumValues: Float32Array; // パラメータの最大値のリスト + private _parameterMinimumValues: Float32Array; // パラメータの最小値のリスト + + private _partOpacities: Float32Array; // パーツの不透明度のリスト + + private _parameterIds: csmVector; + private _partIds: csmVector; + private _drawableIds: csmVector; +} + +// Namespace definition for compatibility. +import * as $ from './cubismmodel'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismModel = $.CubismModel; + export type CubismModel = $.CubismModel; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmodeluserdata.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmodeluserdata.ts new file mode 100644 index 000000000..407fb5574 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmodeluserdata.ts @@ -0,0 +1,136 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { csmString } from '../type/csmstring'; +import { csmVector } from '../type/csmvector'; +import { CubismModelUserDataJson } from './cubismmodeluserdatajson'; + +const ArtMesh = 'ArtMesh'; + +/** + * ユーザーデータインターフェース + * + * Jsonから読み込んだユーザーデータを記録しておくための構造体 + */ +export class CubismModelUserDataNode { + targetType: CubismIdHandle; // ユーザーデータターゲットタイプ + targetId: CubismIdHandle; // ユーザーデータターゲットのID + value: csmString; // ユーザーデータ +} + +/** + * ユーザデータの管理クラス + * + * ユーザデータをロード、管理、検索インターフェイス、解放までを行う。 + */ +export class CubismModelUserData { + /** + * インスタンスの作成 + * + * @param buffer userdata3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + * @return 作成されたインスタンス + */ + public static create(buffer: ArrayBuffer, size: number): CubismModelUserData { + const ret: CubismModelUserData = new CubismModelUserData(); + + ret.parseUserData(buffer, size); + + return ret; + } + + /** + * インスタンスを破棄する + * + * @param modelUserData 破棄するインスタンス + */ + public static delete(modelUserData: CubismModelUserData): void { + if (modelUserData != null) { + modelUserData.release(); + modelUserData = null; + } + } + + /** + * ArtMeshのユーザーデータのリストの取得 + * + * @return ユーザーデータリスト + */ + public getArtMeshUserDatas(): csmVector { + return this._artMeshUserDataNode; + } + + /** + * userdata3.jsonのパース + * + * @param buffer userdata3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public parseUserData(buffer: ArrayBuffer, size: number): void { + let json: CubismModelUserDataJson = new CubismModelUserDataJson( + buffer, + size + ); + + const typeOfArtMesh = CubismFramework.getIdManager().getId(ArtMesh); + const nodeCount: number = json.getUserDataCount(); + + for (let i = 0; i < nodeCount; i++) { + const addNode: CubismModelUserDataNode = new CubismModelUserDataNode(); + + addNode.targetId = json.getUserDataId(i); + addNode.targetType = CubismFramework.getIdManager().getId( + json.getUserDataTargetType(i) + ); + addNode.value = new csmString(json.getUserDataValue(i)); + this._userDataNodes.pushBack(addNode); + + if (addNode.targetType == typeOfArtMesh) { + this._artMeshUserDataNode.pushBack(addNode); + } + } + + json.release(); + json = void 0; + } + + /** + * コンストラクタ + */ + public constructor() { + this._userDataNodes = new csmVector(); + this._artMeshUserDataNode = new csmVector(); + } + + /** + * デストラクタ相当の処理 + * + * ユーザーデータ構造体配列を解放する + */ + public release(): void { + for (let i = 0; i < this._userDataNodes.getSize(); ++i) { + this._userDataNodes.set(i, null); + } + + this._userDataNodes = null; + } + + private _userDataNodes: csmVector; // ユーザーデータ構造体配列 + private _artMeshUserDataNode: csmVector; // 閲覧リストの保持 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmodeluserdata'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismModelUserData = $.CubismModelUserData; + export type CubismModelUserData = $.CubismModelUserData; + export const CubismModelUserDataNode = $.CubismModelUserDataNode; + export type CubismModelUserDataNode = $.CubismModelUserDataNode; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmodeluserdatajson.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmodeluserdatajson.ts new file mode 100644 index 000000000..a691a70fa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismmodeluserdatajson.ts @@ -0,0 +1,117 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CubismJson } from '../utils/cubismjson'; + +const Meta = 'Meta'; +const UserDataCount = 'UserDataCount'; +const TotalUserDataSize = 'TotalUserDataSize'; +const UserData = 'UserData'; +const Target = 'Target'; +const Id = 'Id'; +const Value = 'Value'; + +export class CubismModelUserDataJson { + /** + * コンストラクタ + * @param buffer userdata3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public constructor(buffer: ArrayBuffer, size: number) { + this._json = CubismJson.create(buffer, size); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CubismJson.delete(this._json); + } + + /** + * ユーザーデータ個数の取得 + * @return ユーザーデータの個数 + */ + public getUserDataCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(UserDataCount) + .toInt(); + } + + /** + * ユーザーデータ総文字列数の取得 + * + * @return ユーザーデータ総文字列数 + */ + public getTotalUserDataSize(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalUserDataSize) + .toInt(); + } + + /** + * ユーザーデータのタイプの取得 + * + * @return ユーザーデータのタイプ + */ + public getUserDataTargetType(i: number): string { + return this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(i) + .getValueByString(Target) + .getRawString(); + } + + /** + * ユーザーデータのターゲットIDの取得 + * + * @param i インデックス + * @return ユーザーデータターゲットID + */ + public getUserDataId(i: number): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(i) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * ユーザーデータの文字列の取得 + * + * @param i インデックス + * @return ユーザーデータ + */ + public getUserDataValue(i: number): string { + return this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(i) + .getValueByString(Value) + .getRawString(); + } + + private _json: CubismJson; +} + +// Namespace definition for compatibility. +import * as $ from './cubismmodeluserdatajson'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismModelUserDataJson = $.CubismModelUserDataJson; + export type CubismModelUserDataJson = $.CubismModelUserDataJson; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismusermodel.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismusermodel.ts new file mode 100644 index 000000000..d6e4cdff7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/model/cubismusermodel.ts @@ -0,0 +1,440 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismBreath } from '../effect/cubismbreath'; +import { CubismEyeBlink } from '../effect/cubismeyeblink'; +import { CubismPose } from '../effect/cubismpose'; +import { CubismIdHandle } from '../id/cubismid'; +import { Constant } from '../live2dcubismframework'; +import { CubismModelMatrix } from '../math/cubismmodelmatrix'; +import { CubismTargetPoint } from '../math/cubismtargetpoint'; +import { ACubismMotion, FinishedMotionCallback } from '../motion/acubismmotion'; +import { CubismExpressionMotion } from '../motion/cubismexpressionmotion'; +import { CubismMotion } from '../motion/cubismmotion'; +import { CubismMotionManager } from '../motion/cubismmotionmanager'; +import { CubismMotionQueueManager } from '../motion/cubismmotionqueuemanager'; +import { CubismPhysics } from '../physics/cubismphysics'; +import { CubismRenderer_WebGL } from '../rendering/cubismrenderer_webgl'; +import { csmString } from '../type/csmstring'; +import { CubismLogError, CubismLogInfo } from '../utils/cubismdebug'; +import { CubismMoc } from './cubismmoc'; +import { CubismModel } from './cubismmodel'; +import { CubismModelUserData } from './cubismmodeluserdata'; + +/** + * ユーザーが実際に使用するモデル + * + * ユーザーが実際に使用するモデルの基底クラス。これを継承してユーザーが実装する。 + */ +export class CubismUserModel { + /** + * 初期化状態の取得 + * + * 初期化されている状態か? + * + * @return true 初期化されている + * @return false 初期化されていない + */ + public isInitialized(): boolean { + return this._initialized; + } + + /** + * 初期化状態の設定 + * + * 初期化状態を設定する。 + * + * @param v 初期化状態 + */ + public setInitialized(v: boolean): void { + this._initialized = v; + } + + /** + * 更新状態の取得 + * + * 更新されている状態か? + * + * @return true 更新されている + * @return false 更新されていない + */ + public isUpdating(): boolean { + return this._updating; + } + + /** + * 更新状態の設定 + * + * 更新状態を設定する + * + * @param v 更新状態 + */ + public setUpdating(v: boolean): void { + this._updating = v; + } + + /** + * マウスドラッグ情報の設定 + * @param ドラッグしているカーソルのX位置 + * @param ドラッグしているカーソルのY位置 + */ + public setDragging(x: number, y: number): void { + this._dragManager.set(x, y); + } + + /** + * 加速度の情報を設定する + * @param x X軸方向の加速度 + * @param y Y軸方向の加速度 + * @param z Z軸方向の加速度 + */ + public setAcceleration(x: number, y: number, z: number): void { + this._accelerationX = x; + this._accelerationY = y; + this._accelerationZ = z; + } + + /** + * モデル行列を取得する + * @return モデル行列 + */ + public getModelMatrix(): CubismModelMatrix { + return this._modelMatrix; + } + + /** + * 不透明度の設定 + * @param a 不透明度 + */ + public setOpacity(a: number): void { + this._opacity = a; + } + + /** + * 不透明度の取得 + * @return 不透明度 + */ + public getOpacity(): number { + return this._opacity; + } + + /** + * モデルデータを読み込む + * + * @param buffer moc3ファイルが読み込まれているバッファ + */ + public loadModel(buffer: ArrayBuffer) { + this._moc = CubismMoc.create(buffer); + this._model = this._moc.createModel(); + this._model.saveParameters(); + + if (this._moc == null || this._model == null) { + CubismLogError('Failed to CreateModel().'); + return; + } + + this._modelMatrix = new CubismModelMatrix( + this._model.getCanvasWidth(), + this._model.getCanvasHeight() + ); + } + + /** + * モーションデータを読み込む + * @param buffer motion3.jsonファイルが読み込まれているバッファ + * @param size バッファのサイズ + * @param name モーションの名前 + * @param onFinishedMotionHandler モーション再生終了時に呼び出されるコールバック関数 + * @return モーションクラス + */ + public loadMotion = ( + buffer: ArrayBuffer, + size: number, + name: string, + onFinishedMotionHandler?: FinishedMotionCallback + ) => CubismMotion.create(buffer, size, onFinishedMotionHandler); + + /** + * 表情データの読み込み + * @param buffer expファイルが読み込まれているバッファ + * @param size バッファのサイズ + * @param name 表情の名前 + */ + public loadExpression( + buffer: ArrayBuffer, + size: number, + name: string + ): ACubismMotion { + return CubismExpressionMotion.create(buffer, size); + } + + /** + * ポーズデータの読み込み + * @param buffer pose3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public loadPose(buffer: ArrayBuffer, size: number): void { + this._pose = CubismPose.create(buffer, size); + } + + /** + * モデルに付属するユーザーデータを読み込む + * @param buffer userdata3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public loadUserData(buffer: ArrayBuffer, size: number): void { + this._modelUserData = CubismModelUserData.create(buffer, size); + } + + /** + * 物理演算データの読み込み + * @param buffer physics3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public loadPhysics(buffer: ArrayBuffer, size: number): void { + this._physics = CubismPhysics.create(buffer, size); + } + + /** + * 当たり判定の取得 + * @param drawableId 検証したいDrawableのID + * @param pointX X位置 + * @param pointY Y位置 + * @return true ヒットしている + * @return false ヒットしていない + */ + public isHit( + drawableId: CubismIdHandle, + pointX: number, + pointY: number + ): boolean { + const drawIndex: number = this._model.getDrawableIndex(drawableId); + + if (drawIndex < 0) { + return false; // 存在しない場合はfalse + } + + const count: number = this._model.getDrawableVertexCount(drawIndex); + const vertices: Float32Array = this._model.getDrawableVertices(drawIndex); + + let left: number = vertices[0]; + let right: number = vertices[0]; + let top: number = vertices[1]; + let bottom: number = vertices[1]; + + for (let j = 1; j < count; ++j) { + const x = vertices[Constant.vertexOffset + j * Constant.vertexStep]; + const y = vertices[Constant.vertexOffset + j * Constant.vertexStep + 1]; + + if (x < left) { + left = x; // Min x + } + + if (x > right) { + right = x; // Max x + } + + if (y < top) { + top = y; // Min y + } + + if (y > bottom) { + bottom = y; // Max y + } + } + + const tx: number = this._modelMatrix.invertTransformX(pointX); + const ty: number = this._modelMatrix.invertTransformY(pointY); + + return left <= tx && tx <= right && top <= ty && ty <= bottom; + } + + /** + * モデルの取得 + * @return モデル + */ + public getModel(): CubismModel { + return this._model; + } + + /** + * レンダラの取得 + * @return レンダラ + */ + public getRenderer(): CubismRenderer_WebGL { + return this._renderer; + } + + /** + * レンダラを作成して初期化を実行する + */ + public createRenderer(): void { + if (this._renderer) { + this.deleteRenderer(); + } + + this._renderer = new CubismRenderer_WebGL(); + this._renderer.initialize(this._model); + } + + /** + * レンダラの解放 + */ + public deleteRenderer(): void { + if (this._renderer != null) { + this._renderer.release(); + this._renderer = null; + } + } + + /** + * イベント発火時の標準処理 + * + * Eventが再生処理時にあった場合の処理をする。 + * 継承で上書きすることを想定している。 + * 上書きしない場合はログ出力をする。 + * + * @param eventValue 発火したイベントの文字列データ + */ + public motionEventFired(eventValue: csmString): void { + CubismLogInfo('{0}', eventValue.s); + } + + /** + * イベント用のコールバック + * + * CubismMotionQueueManagerにイベント用に登録するためのCallback。 + * CubismUserModelの継承先のEventFiredを呼ぶ。 + * + * @param caller 発火したイベントを管理していたモーションマネージャー、比較用 + * @param eventValue 発火したイベントの文字列データ + * @param customData CubismUserModelを継承したインスタンスを想定 + */ + public static cubismDefaultMotionEventCallback( + caller: CubismMotionQueueManager, + eventValue: csmString, + customData: CubismUserModel + ): void { + const model: CubismUserModel = customData; + + if (model != null) { + model.motionEventFired(eventValue); + } + } + + /** + * コンストラクタ + */ + public constructor() { + // 各変数初期化 + this._moc = null; + this._model = null; + this._motionManager = null; + this._expressionManager = null; + this._eyeBlink = null; + this._breath = null; + this._modelMatrix = null; + this._pose = null; + this._dragManager = null; + this._physics = null; + this._modelUserData = null; + this._initialized = false; + this._updating = false; + this._opacity = 1.0; + this._lipsync = true; + this._lastLipSyncValue = 0.0; + this._dragX = 0.0; + this._dragY = 0.0; + this._accelerationX = 0.0; + this._accelerationY = 0.0; + this._accelerationZ = 0.0; + this._debugMode = false; + this._renderer = null; + + // モーションマネージャーを作成 + this._motionManager = new CubismMotionManager(); + this._motionManager.setEventCallback( + CubismUserModel.cubismDefaultMotionEventCallback, + this + ); + + // 表情マネージャーを作成 + this._expressionManager = new CubismMotionManager(); + + // ドラッグによるアニメーション + this._dragManager = new CubismTargetPoint(); + } + + /** + * デストラクタに相当する処理 + */ + public release() { + if (this._motionManager != null) { + this._motionManager.release(); + this._motionManager = null; + } + + if (this._expressionManager != null) { + this._expressionManager.release(); + this._expressionManager = null; + } + + if (this._moc != null) { + this._moc.deleteModel(this._model); + this._moc.release(); + this._moc = null; + } + + this._modelMatrix = null; + + CubismPose.delete(this._pose); + CubismEyeBlink.delete(this._eyeBlink); + CubismBreath.delete(this._breath); + + this._dragManager = null; + + CubismPhysics.delete(this._physics); + CubismModelUserData.delete(this._modelUserData); + + this.deleteRenderer(); + } + + protected _moc: CubismMoc; // Mocデータ + protected _model: CubismModel; // Modelインスタンス + + protected _motionManager: CubismMotionManager; // モーション管理 + protected _expressionManager: CubismMotionManager; // 表情管理 + protected _eyeBlink: CubismEyeBlink; // 自動まばたき + protected _breath: CubismBreath; // 呼吸 + protected _modelMatrix: CubismModelMatrix; // モデル行列 + protected _pose: CubismPose; // ポーズ管理 + protected _dragManager: CubismTargetPoint; // マウスドラッグ + protected _physics: CubismPhysics; // 物理演算 + protected _modelUserData: CubismModelUserData; // ユーザーデータ + + protected _initialized: boolean; // 初期化されたかどうか + protected _updating: boolean; // 更新されたかどうか + protected _opacity: number; // 不透明度 + protected _lipsync: boolean; // リップシンクするかどうか + protected _lastLipSyncValue: number; // 最後のリップシンクの制御地 + protected _dragX: number; // マウスドラッグのX位置 + protected _dragY: number; // マウスドラッグのY位置 + protected _accelerationX: number; // X軸方向の加速度 + protected _accelerationY: number; // Y軸方向の加速度 + protected _accelerationZ: number; // Z軸方向の加速度 + protected _debugMode: boolean; // デバッグモードかどうか + + private _renderer: CubismRenderer_WebGL; // レンダラ +} + +// Namespace definition for compatibility. +import * as $ from './cubismusermodel'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismUserModel = $.CubismUserModel; + export type CubismUserModel = $.CubismUserModel; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/acubismmotion.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/acubismmotion.ts new file mode 100644 index 000000000..3afcad2af --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/acubismmotion.ts @@ -0,0 +1,279 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMath } from '../math/cubismmath'; +import { CubismModel } from '../model/cubismmodel'; +import { csmString } from '../type/csmstring'; +import { csmVector } from '../type/csmvector'; +import { CSM_ASSERT } from '../utils/cubismdebug'; +import { CubismMotionQueueEntry } from './cubismmotionqueueentry'; + +/** モーション再生終了コールバック関数定義 */ +export type FinishedMotionCallback = (self: ACubismMotion) => void; + +/** + * モーションの抽象基底クラス + * + * モーションの抽象基底クラス。MotionQueueManagerによってモーションの再生を管理する。 + */ +export abstract class ACubismMotion { + /** + * インスタンスの破棄 + */ + public static delete(motion: ACubismMotion): void { + motion.release(); + motion = null; + } + + /** + * コンストラクタ + */ + public constructor() { + this._fadeInSeconds = -1.0; + this._fadeOutSeconds = -1.0; + this._weight = 1.0; + this._offsetSeconds = 0.0; // 再生の開始時刻 + this._firedEventValues = new csmVector(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._weight = 0.0; + } + + /** + * モデルのパラメータ + * @param model 対象のモデル + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + * @param userTimeSeconds デルタ時間の積算値[秒] + */ + public updateParameters( + model: CubismModel, + motionQueueEntry: CubismMotionQueueEntry, + userTimeSeconds: number + ): void { + if (!motionQueueEntry.isAvailable() || motionQueueEntry.isFinished()) { + return; + } + + if (!motionQueueEntry.isStarted()) { + motionQueueEntry.setIsStarted(true); + motionQueueEntry.setStartTime(userTimeSeconds - this._offsetSeconds); // モーションの開始時刻を記録 + motionQueueEntry.setFadeInStartTime(userTimeSeconds); // フェードインの開始時刻 + + const duration: number = this.getDuration(); + + if (motionQueueEntry.getEndTime() < 0) { + // 開始していないうちに終了設定している場合がある。 + motionQueueEntry.setEndTime( + duration <= 0 ? -1 : motionQueueEntry.getStartTime() + duration + ); + // duration == -1 の場合はループする + } + } + + let fadeWeight: number = this._weight; // 現在の値と掛け合わせる割合 + + //---- フェードイン・アウトの処理 ---- + // 単純なサイン関数でイージングする + const fadeIn: number = + this._fadeInSeconds == 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (userTimeSeconds - motionQueueEntry.getFadeInStartTime()) / + this._fadeInSeconds + ); + + const fadeOut: number = + this._fadeOutSeconds == 0.0 || motionQueueEntry.getEndTime() < 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (motionQueueEntry.getEndTime() - userTimeSeconds) / + this._fadeOutSeconds + ); + + fadeWeight = fadeWeight * fadeIn * fadeOut; + + motionQueueEntry.setState(userTimeSeconds, fadeWeight); + + CSM_ASSERT(0.0 <= fadeWeight && fadeWeight <= 1.0); + + //---- 全てのパラメータIDをループする ---- + this.doUpdateParameters( + model, + userTimeSeconds, + fadeWeight, + motionQueueEntry + ); + + // 後処理 + // 終了時刻を過ぎたら終了フラグを立てる(CubismMotionQueueManager) + if ( + motionQueueEntry.getEndTime() > 0 && + motionQueueEntry.getEndTime() < userTimeSeconds + ) { + motionQueueEntry.setIsFinished(true); // 終了 + } + } + + /** + * フェードインの時間を設定する + * @param fadeInSeconds フェードインにかかる時間[秒] + */ + public setFadeInTime(fadeInSeconds: number): void { + this._fadeInSeconds = fadeInSeconds; + } + + /** + * フェードアウトの時間を設定する + * @param fadeOutSeconds フェードアウトにかかる時間[秒] + */ + public setFadeOutTime(fadeOutSeconds: number): void { + this._fadeOutSeconds = fadeOutSeconds; + } + + /** + * フェードアウトにかかる時間の取得 + * @return フェードアウトにかかる時間[秒] + */ + public getFadeOutTime(): number { + return this._fadeOutSeconds; + } + + /** + * フェードインにかかる時間の取得 + * @return フェードインにかかる時間[秒] + */ + public getFadeInTime(): number { + return this._fadeInSeconds; + } + + /** + * モーション適用の重みの設定 + * @param weight 重み(0.0 - 1.0) + */ + public setWeight(weight: number): void { + this._weight = weight; + } + + /** + * モーション適用の重みの取得 + * @return 重み(0.0 - 1.0) + */ + public getWeight(): number { + return this._weight; + } + + /** + * モーションの長さの取得 + * @return モーションの長さ[秒] + * + * @note ループの時は「-1」。 + * ループでない場合は、オーバーライドする。 + * 正の値の時は取得される時間で終了する。 + * 「-1」の時は外部から停止命令がない限り終わらない処理となる。 + */ + public getDuration(): number { + return -1.0; + } + + /** + * モーションのループ1回分の長さの取得 + * @return モーションのループ一回分の長さ[秒] + * + * @note ループしない場合は、getDuration()と同じ値を返す + * ループ一回分の長さが定義できない場合(プログラム的に動き続けるサブクラスなど)の場合は「-1」を返す + */ + public getLoopDuration(): number { + return -1.0; + } + + /** + * モーション再生の開始時刻の設定 + * @param offsetSeconds モーション再生の開始時刻[秒] + */ + public setOffsetTime(offsetSeconds: number): void { + this._offsetSeconds = offsetSeconds; + } + + /** + * モデルのパラメータ更新 + * + * イベント発火のチェック。 + * 入力する時間は呼ばれるモーションタイミングを0とした秒数で行う。 + * + * @param beforeCheckTimeSeconds 前回のイベントチェック時間[秒] + * @param motionTimeSeconds 今回の再生時間[秒] + */ + public getFiredEvent( + beforeCheckTimeSeconds: number, + motionTimeSeconds: number + ): csmVector { + return this._firedEventValues; + } + + /** + * モーションを更新して、モデルにパラメータ値を反映する + * @param model 対象のモデル + * @param userTimeSeconds デルタ時間の積算値[秒] + * @param weight モーションの重み + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + * @return true モデルへパラメータ値の反映あり + * @return false モデルへのパラメータ値の反映なし(モーションの変化なし) + */ + public abstract doUpdateParameters( + model: CubismModel, + userTimeSeconds: number, + weight: number, + motionQueueEntry: CubismMotionQueueEntry + ): void; + + /** + * モーション再生終了コールバックの登録 + * + * モーション再生終了コールバックを登録する。 + * isFinishedフラグを設定するタイミングで呼び出される。 + * 以下の状態の際には呼び出されない: + * 1. 再生中のモーションが「ループ」として設定されているとき + * 2. コールバックが登録されていない時 + * + * @param onFinishedMotionHandler モーション再生終了コールバック関数 + */ + public setFinishedMotionHandler = ( + onFinishedMotionHandler: FinishedMotionCallback + ) => (this._onFinishedMotion = onFinishedMotionHandler); + + /** + * モーション再生終了コールバックの取得 + * + * モーション再生終了コールバックを取得する。 + * + * @return 登録されているモーション再生終了コールバック関数 + */ + public getFinishedMotionHandler = () => this._onFinishedMotion; + + public _fadeInSeconds: number; // フェードインにかかる時間[秒] + public _fadeOutSeconds: number; // フェードアウトにかかる時間[秒] + public _weight: number; // モーションの重み + public _offsetSeconds: number; // モーション再生の開始時間[秒] + + public _firedEventValues: csmVector; + + // モーション再生終了コールバック関数 + public _onFinishedMotion?: FinishedMotionCallback; +} + +// Namespace definition for compatibility. +import * as $ from './acubismmotion'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const ACubismMotion = $.ACubismMotion; + export type ACubismMotion = $.ACubismMotion; + export type FinishedMotionCallback = $.FinishedMotionCallback; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismexpressionmotion.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismexpressionmotion.ts new file mode 100644 index 000000000..9ffa94e04 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismexpressionmotion.ts @@ -0,0 +1,199 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CubismModel } from '../model/cubismmodel'; +import { csmVector } from '../type/csmvector'; +import { CubismJson, Value } from '../utils/cubismjson'; +import { ACubismMotion } from './acubismmotion'; +import { CubismMotionQueueEntry } from './cubismmotionqueueentry'; + +// exp3.jsonのキーとデフォルト +const ExpressionKeyFadeIn = 'FadeInTime'; +const ExpressionKeyFadeOut = 'FadeOutTime'; +const ExpressionKeyParameters = 'Parameters'; +const ExpressionKeyId = 'Id'; +const ExpressionKeyValue = 'Value'; +const ExpressionKeyBlend = 'Blend'; +const BlendValueAdd = 'Add'; +const BlendValueMultiply = 'Multiply'; +const BlendValueOverwrite = 'Overwrite'; +const DefaultFadeTime = 1.0; + +/** + * 表情のモーション + * + * 表情のモーションクラス。 + */ +export class CubismExpressionMotion extends ACubismMotion { + /** + * インスタンスを作成する。 + * @param buffer expファイルが読み込まれているバッファ + * @param size バッファのサイズ + * @return 作成されたインスタンス + */ + public static create( + buffer: ArrayBuffer, + size: number + ): CubismExpressionMotion { + const expression: CubismExpressionMotion = new CubismExpressionMotion(); + + const json: CubismJson = CubismJson.create(buffer, size); + const root: Value = json.getRoot(); + + expression.setFadeInTime( + root.getValueByString(ExpressionKeyFadeIn).toFloat(DefaultFadeTime) + ); // フェードイン + expression.setFadeOutTime( + root.getValueByString(ExpressionKeyFadeOut).toFloat(DefaultFadeTime) + ); // フェードアウト + + // 各パラメータについて + const parameterCount = root + .getValueByString(ExpressionKeyParameters) + .getSize(); + expression._parameters.prepareCapacity(parameterCount); + + for (let i = 0; i < parameterCount; ++i) { + const param: Value = root + .getValueByString(ExpressionKeyParameters) + .getValueByIndex(i); + const parameterId: CubismIdHandle = CubismFramework.getIdManager().getId( + param.getValueByString(ExpressionKeyId).getRawString() + ); // パラメータID + + const value: number = param + .getValueByString(ExpressionKeyValue) + .toFloat(); // 値 + + // 計算方法の設定 + let blendType: ExpressionBlendType; + + if ( + param.getValueByString(ExpressionKeyBlend).isNull() || + param.getValueByString(ExpressionKeyBlend).getString() == BlendValueAdd + ) { + blendType = ExpressionBlendType.ExpressionBlendType_Add; + } else if ( + param.getValueByString(ExpressionKeyBlend).getString() == + BlendValueMultiply + ) { + blendType = ExpressionBlendType.ExpressionBlendType_Multiply; + } else if ( + param.getValueByString(ExpressionKeyBlend).getString() == + BlendValueOverwrite + ) { + blendType = ExpressionBlendType.ExpressionBlendType_Overwrite; + } else { + // その他 仕様にない値を設定した時は加算モードにすることで復旧 + blendType = ExpressionBlendType.ExpressionBlendType_Add; + } + + // 設定オブジェクトを作成してリストに追加する + const item: ExpressionParameter = new ExpressionParameter(); + + item.parameterId = parameterId; + item.blendType = blendType; + item.value = value; + + expression._parameters.pushBack(item); + } + + CubismJson.delete(json); // JSONデータは不要になったら削除する + return expression; + } + + /** + * モデルのパラメータの更新の実行 + * @param model 対象のモデル + * @param userTimeSeconds デルタ時間の積算値[秒] + * @param weight モーションの重み + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + */ + public doUpdateParameters( + model: CubismModel, + userTimeSeconds: number, + weight: number, + motionQueueEntry: CubismMotionQueueEntry + ): void { + for (let i = 0; i < this._parameters.getSize(); ++i) { + const parameter: ExpressionParameter = this._parameters.at(i); + + switch (parameter.blendType) { + case ExpressionBlendType.ExpressionBlendType_Add: { + model.addParameterValueById( + parameter.parameterId, + parameter.value, + weight + ); + break; + } + case ExpressionBlendType.ExpressionBlendType_Multiply: { + model.multiplyParameterValueById( + parameter.parameterId, + parameter.value, + weight + ); + break; + } + case ExpressionBlendType.ExpressionBlendType_Overwrite: { + model.setParameterValueById( + parameter.parameterId, + parameter.value, + weight + ); + break; + } + default: + // 仕様にない値を設定した時はすでに加算モードになっている + break; + } + } + } + + /** + * コンストラクタ + */ + constructor() { + super(); + + this._parameters = new csmVector(); + } + + _parameters: csmVector; // 表情のパラメータ情報リスト +} + +/** + * 表情パラメータ値の計算方式 + */ +export enum ExpressionBlendType { + ExpressionBlendType_Add = 0, // 加算 + ExpressionBlendType_Multiply = 1, // 乗算 + ExpressionBlendType_Overwrite = 2 // 上書き +} + +/** + * 表情のパラメータ情報 + */ +export class ExpressionParameter { + parameterId: CubismIdHandle; // パラメータID + blendType: ExpressionBlendType; // パラメータの演算種類 + value: number; // 値 +} + +// Namespace definition for compatibility. +import * as $ from './cubismexpressionmotion'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismExpressionMotion = $.CubismExpressionMotion; + export type CubismExpressionMotion = $.CubismExpressionMotion; + export const ExpressionBlendType = $.ExpressionBlendType; + export type ExpressionBlendType = $.ExpressionBlendType; + export const ExpressionParameter = $.ExpressionParameter; + export type ExpressionParameter = $.ExpressionParameter; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotion.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotion.ts new file mode 100644 index 000000000..e690c52b0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotion.ts @@ -0,0 +1,1060 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CubismMath } from '../math/cubismmath'; +import { CubismModel } from '../model/cubismmodel'; +import { csmString } from '../type/csmstring'; +import { csmVector } from '../type/csmvector'; +import { + CSM_ASSERT, + CubismLogDebug, + CubismLogWarning +} from '../utils/cubismdebug'; +import { ACubismMotion, FinishedMotionCallback } from './acubismmotion'; +import { + CubismMotionCurve, + CubismMotionCurveTarget, + CubismMotionData, + CubismMotionEvent, + CubismMotionPoint, + CubismMotionSegment, + CubismMotionSegmentType +} from './cubismmotioninternal'; +import { CubismMotionJson, EvaluationOptionFlag } from './cubismmotionjson'; +import { CubismMotionQueueEntry } from './cubismmotionqueueentry'; + +const EffectNameEyeBlink = 'EyeBlink'; +const EffectNameLipSync = 'LipSync'; +const TargetNameModel = 'Model'; +const TargetNameParameter = 'Parameter'; +const TargetNamePartOpacity = 'PartOpacity'; + +/** + * Cubism SDK R2 以前のモーションを再現させるなら true 、アニメータのモーションを正しく再現するなら false 。 + */ +const UseOldBeziersCurveMotion = false; + +function lerpPoints( + a: CubismMotionPoint, + b: CubismMotionPoint, + t: number +): CubismMotionPoint { + const result: CubismMotionPoint = new CubismMotionPoint(); + + result.time = a.time + (b.time - a.time) * t; + result.value = a.value + (b.value - a.value) * t; + + return result; +} + +function linearEvaluate(points: CubismMotionPoint[], time: number): number { + let t: number = (time - points[0].time) / (points[1].time - points[0].time); + + if (t < 0.0) { + t = 0.0; + } + + return points[0].value + (points[1].value - points[0].value) * t; +} + +function bezierEvaluate(points: CubismMotionPoint[], time: number): number { + let t: number = (time - points[0].time) / (points[3].time - points[0].time); + + if (t < 0.0) { + t = 0.0; + } + + const p01: CubismMotionPoint = lerpPoints(points[0], points[1], t); + const p12: CubismMotionPoint = lerpPoints(points[1], points[2], t); + const p23: CubismMotionPoint = lerpPoints(points[2], points[3], t); + + const p012: CubismMotionPoint = lerpPoints(p01, p12, t); + const p123: CubismMotionPoint = lerpPoints(p12, p23, t); + + return lerpPoints(p012, p123, t).value; +} + +function bezierEvaluateBinarySearch( + points: CubismMotionPoint[], + time: number +): number { + const x_error = 0.01; + + const x: number = time; + let x1: number = points[0].time; + let x2: number = points[3].time; + let cx1: number = points[1].time; + let cx2: number = points[2].time; + + let ta = 0.0; + let tb = 1.0; + let t = 0.0; + let i = 0; + + for (let var33 = true; i < 20; ++i) { + if (x < x1 + x_error) { + t = ta; + break; + } + + if (x2 - x_error < x) { + t = tb; + break; + } + + let centerx: number = (cx1 + cx2) * 0.5; + cx1 = (x1 + cx1) * 0.5; + cx2 = (x2 + cx2) * 0.5; + const ctrlx12: number = (cx1 + centerx) * 0.5; + const ctrlx21: number = (cx2 + centerx) * 0.5; + centerx = (ctrlx12 + ctrlx21) * 0.5; + if (x < centerx) { + tb = (ta + tb) * 0.5; + if (centerx - x_error < x) { + t = tb; + break; + } + + x2 = centerx; + cx2 = ctrlx12; + } else { + ta = (ta + tb) * 0.5; + if (x < centerx + x_error) { + t = ta; + break; + } + + x1 = centerx; + cx1 = ctrlx21; + } + } + + if (i == 20) { + t = (ta + tb) * 0.5; + } + + if (t < 0.0) { + t = 0.0; + } + if (t > 1.0) { + t = 1.0; + } + + const p01: CubismMotionPoint = lerpPoints(points[0], points[1], t); + const p12: CubismMotionPoint = lerpPoints(points[1], points[2], t); + const p23: CubismMotionPoint = lerpPoints(points[2], points[3], t); + + const p012: CubismMotionPoint = lerpPoints(p01, p12, t); + const p123: CubismMotionPoint = lerpPoints(p12, p23, t); + + return lerpPoints(p012, p123, t).value; +} + +function bezierEvaluateCardanoInterpretation( + points: CubismMotionPoint[], + time: number +): number { + const x: number = time; + const x1: number = points[0].time; + const x2: number = points[3].time; + const cx1: number = points[1].time; + const cx2: number = points[2].time; + + const a: number = x2 - 3.0 * cx2 + 3.0 * cx1 - x1; + const b: number = 3.0 * cx2 - 6.0 * cx1 + 3.0 * x1; + const c: number = 3.0 * cx1 - 3.0 * x1; + const d: number = x1 - x; + + const t: number = CubismMath.cardanoAlgorithmForBezier(a, b, c, d); + + const p01: CubismMotionPoint = lerpPoints(points[0], points[1], t); + const p12: CubismMotionPoint = lerpPoints(points[1], points[2], t); + const p23: CubismMotionPoint = lerpPoints(points[2], points[3], t); + + const p012: CubismMotionPoint = lerpPoints(p01, p12, t); + const p123: CubismMotionPoint = lerpPoints(p12, p23, t); + + return lerpPoints(p012, p123, t).value; +} + +function steppedEvaluate(points: CubismMotionPoint[], time: number): number { + return points[0].value; +} + +function inverseSteppedEvaluate( + points: CubismMotionPoint[], + time: number +): number { + return points[1].value; +} + +function evaluateCurve( + motionData: CubismMotionData, + index: number, + time: number +): number { + // Find segment to evaluate. + const curve: CubismMotionCurve = motionData.curves.at(index); + + let target = -1; + const totalSegmentCount: number = curve.baseSegmentIndex + curve.segmentCount; + let pointPosition = 0; + for (let i: number = curve.baseSegmentIndex; i < totalSegmentCount; ++i) { + // Get first point of next segment. + pointPosition = + motionData.segments.at(i).basePointIndex + + (motionData.segments.at(i).segmentType == + CubismMotionSegmentType.CubismMotionSegmentType_Bezier + ? 3 + : 1); + + // Break if time lies within current segment. + if (motionData.points.at(pointPosition).time > time) { + target = i; + break; + } + } + + if (target == -1) { + return motionData.points.at(pointPosition).value; + } + + const segment: CubismMotionSegment = motionData.segments.at(target); + + return segment.evaluate(motionData.points.get(segment.basePointIndex), time); +} + +/** + * モーションクラス + * + * モーションのクラス。 + */ +export class CubismMotion extends ACubismMotion { + /** + * インスタンスを作成する + * + * @param buffer motion3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + * @param onFinishedMotionHandler モーション再生終了時に呼び出されるコールバック関数 + * @return 作成されたインスタンス + */ + public static create( + buffer: ArrayBuffer, + size: number, + onFinishedMotionHandler?: FinishedMotionCallback + ): CubismMotion { + const ret = new CubismMotion(); + + ret.parse(buffer, size); + ret._sourceFrameRate = ret._motionData.fps; + ret._loopDurationSeconds = ret._motionData.duration; + ret._onFinishedMotion = onFinishedMotionHandler; + + // NOTE: Editorではループありのモーション書き出しは非対応 + // ret->_loop = (ret->_motionData->Loop > 0); + return ret; + } + + /** + * モデルのパラメータの更新の実行 + * @param model 対象のモデル + * @param userTimeSeconds 現在の時刻[秒] + * @param fadeWeight モーションの重み + * @param motionQueueEntry CubismMotionQueueManagerで管理されているモーション + */ + public doUpdateParameters( + model: CubismModel, + userTimeSeconds: number, + fadeWeight: number, + motionQueueEntry: CubismMotionQueueEntry + ): void { + if (this._modelCurveIdEyeBlink == null) { + this._modelCurveIdEyeBlink = CubismFramework.getIdManager().getId( + EffectNameEyeBlink + ); + } + + if (this._modelCurveIdLipSync == null) { + this._modelCurveIdLipSync = CubismFramework.getIdManager().getId( + EffectNameLipSync + ); + } + + let timeOffsetSeconds: number = + userTimeSeconds - motionQueueEntry.getStartTime(); + + if (timeOffsetSeconds < 0.0) { + timeOffsetSeconds = 0.0; // エラー回避 + } + + let lipSyncValue: number = Number.MAX_VALUE; + let eyeBlinkValue: number = Number.MAX_VALUE; + + //まばたき、リップシンクのうちモーションの適用を検出するためのビット(maxFlagCount個まで + const MaxTargetSize = 64; + let lipSyncFlags = 0; + let eyeBlinkFlags = 0; + + //瞬き、リップシンクのターゲット数が上限を超えている場合 + if (this._eyeBlinkParameterIds.getSize() > MaxTargetSize) { + CubismLogDebug( + 'too many eye blink targets : {0}', + this._eyeBlinkParameterIds.getSize() + ); + } + if (this._lipSyncParameterIds.getSize() > MaxTargetSize) { + CubismLogDebug( + 'too many lip sync targets : {0}', + this._lipSyncParameterIds.getSize() + ); + } + + const tmpFadeIn: number = + this._fadeInSeconds <= 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (userTimeSeconds - motionQueueEntry.getFadeInStartTime()) / + this._fadeInSeconds + ); + + const tmpFadeOut: number = + this._fadeOutSeconds <= 0.0 || motionQueueEntry.getEndTime() < 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (motionQueueEntry.getEndTime() - userTimeSeconds) / + this._fadeOutSeconds + ); + let value: number; + let c: number, parameterIndex: number; + + // 'Repeat' time as necessary. + let time: number = timeOffsetSeconds; + + if (this._isLoop) { + while (time > this._motionData.duration) { + time -= this._motionData.duration; + } + } + + const curves: csmVector = this._motionData.curves; + + // Evaluate model curves. + for ( + c = 0; + c < this._motionData.curveCount && + curves.at(c).type == + CubismMotionCurveTarget.CubismMotionCurveTarget_Model; + ++c + ) { + // Evaluate curve and call handler. + value = evaluateCurve(this._motionData, c, time); + + if (curves.at(c).id == this._modelCurveIdEyeBlink) { + eyeBlinkValue = value; + } else if (curves.at(c).id == this._modelCurveIdLipSync) { + lipSyncValue = value; + } + } + + let parameterMotionCurveCount = 0; + + for ( + ; + c < this._motionData.curveCount && + curves.at(c).type == + CubismMotionCurveTarget.CubismMotionCurveTarget_Parameter; + ++c + ) { + parameterMotionCurveCount++; + + // Find parameter index. + parameterIndex = model.getParameterIndex(curves.at(c).id); + + // Skip curve evaluation if no value in sink. + if (parameterIndex == -1) { + continue; + } + + const sourceValue: number = model.getParameterValueByIndex( + parameterIndex + ); + + // Evaluate curve and apply value. + value = evaluateCurve(this._motionData, c, time); + + if (eyeBlinkValue != Number.MAX_VALUE) { + for ( + let i = 0; + i < this._eyeBlinkParameterIds.getSize() && i < MaxTargetSize; + ++i + ) { + if (this._eyeBlinkParameterIds.at(i) == curves.at(c).id) { + value *= eyeBlinkValue; + eyeBlinkFlags |= 1 << i; + break; + } + } + } + + if (lipSyncValue != Number.MAX_VALUE) { + for ( + let i = 0; + i < this._lipSyncParameterIds.getSize() && i < MaxTargetSize; + ++i + ) { + if (this._lipSyncParameterIds.at(i) == curves.at(c).id) { + value += lipSyncValue; + lipSyncFlags |= 1 << i; + break; + } + } + } + + let v: number; + + // パラメータごとのフェード + if (curves.at(c).fadeInTime < 0.0 && curves.at(c).fadeOutTime < 0.0) { + // モーションのフェードを適用 + v = sourceValue + (value - sourceValue) * fadeWeight; + } else { + // パラメータに対してフェードインかフェードアウトが設定してある場合はそちらを適用 + let fin: number; + let fout: number; + + if (curves.at(c).fadeInTime < 0.0) { + fin = tmpFadeIn; + } else { + fin = + curves.at(c).fadeInTime == 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (userTimeSeconds - motionQueueEntry.getFadeInStartTime()) / + curves.at(c).fadeInTime + ); + } + + if (curves.at(c).fadeOutTime < 0.0) { + fout = tmpFadeOut; + } else { + fout = + curves.at(c).fadeOutTime == 0.0 || + motionQueueEntry.getEndTime() < 0.0 + ? 1.0 + : CubismMath.getEasingSine( + (motionQueueEntry.getEndTime() - userTimeSeconds) / + curves.at(c).fadeOutTime + ); + } + + const paramWeight: number = this._weight * fin * fout; + + // パラメータごとのフェードを適用 + v = sourceValue + (value - sourceValue) * paramWeight; + } + + model.setParameterValueByIndex(parameterIndex, v, 1.0); + } + + { + if (eyeBlinkValue != Number.MAX_VALUE) { + for ( + let i = 0; + i < this._eyeBlinkParameterIds.getSize() && i < MaxTargetSize; + ++i + ) { + const sourceValue: number = model.getParameterValueById( + this._eyeBlinkParameterIds.at(i) + ); + + // モーションでの上書きがあった時にはまばたきは適用しない + if ((eyeBlinkFlags >> i) & 0x01) { + continue; + } + + const v: number = + sourceValue + (eyeBlinkValue - sourceValue) * fadeWeight; + + model.setParameterValueById(this._eyeBlinkParameterIds.at(i), v); + } + } + + if (lipSyncValue != Number.MAX_VALUE) { + for ( + let i = 0; + i < this._lipSyncParameterIds.getSize() && i < MaxTargetSize; + ++i + ) { + const sourceValue: number = model.getParameterValueById( + this._lipSyncParameterIds.at(i) + ); + + // モーションでの上書きがあった時にはリップシンクは適用しない + if ((lipSyncFlags >> i) & 0x01) { + continue; + } + + const v: number = + sourceValue + (lipSyncValue - sourceValue) * fadeWeight; + + model.setParameterValueById(this._lipSyncParameterIds.at(i), v); + } + } + } + + for ( + ; + c < this._motionData.curveCount && + curves.at(c).type == + CubismMotionCurveTarget.CubismMotionCurveTarget_PartOpacity; + ++c + ) { + // Find parameter index. + parameterIndex = model.getParameterIndex(curves.at(c).id); + + // Skip curve evaluation if no value in sink. + if (parameterIndex == -1) { + continue; + } + + // Evaluate curve and apply value. + value = evaluateCurve(this._motionData, c, time); + + model.setParameterValueByIndex(parameterIndex, value); + } + + if (timeOffsetSeconds >= this._motionData.duration) { + if (this._isLoop) { + motionQueueEntry.setStartTime(userTimeSeconds); // 最初の状態へ + if (this._isLoopFadeIn) { + // ループ内でループ用フェードインが有効の時は、フェードイン設定し直し + motionQueueEntry.setFadeInStartTime(userTimeSeconds); + } + } else { + if (this._onFinishedMotion) { + this._onFinishedMotion(this); + } + + motionQueueEntry.setIsFinished(true); + } + } + this._lastWeight = fadeWeight; + } + + /** + * ループ情報の設定 + * @param loop ループ情報 + */ + public setIsLoop(loop: boolean): void { + this._isLoop = loop; + } + + /** + * ループ情報の取得 + * @return true ループする + * @return false ループしない + */ + public isLoop(): boolean { + return this._isLoop; + } + + /** + * ループ時のフェードイン情報の設定 + * @param loopFadeIn ループ時のフェードイン情報 + */ + public setIsLoopFadeIn(loopFadeIn: boolean): void { + this._isLoopFadeIn = loopFadeIn; + } + + /** + * ループ時のフェードイン情報の取得 + * + * @return true する + * @return false しない + */ + public isLoopFadeIn(): boolean { + return this._isLoopFadeIn; + } + + /** + * モーションの長さを取得する。 + * + * @return モーションの長さ[秒] + */ + public getDuration(): number { + return this._isLoop ? -1.0 : this._loopDurationSeconds; + } + + /** + * モーションのループ時の長さを取得する。 + * + * @return モーションのループ時の長さ[秒] + */ + public getLoopDuration(): number { + return this._loopDurationSeconds; + } + + /** + * パラメータに対するフェードインの時間を設定する。 + * + * @param parameterId パラメータID + * @param value フェードインにかかる時間[秒] + */ + public setParameterFadeInTime( + parameterId: CubismIdHandle, + value: number + ): void { + const curves: csmVector = this._motionData.curves; + + for (let i = 0; i < this._motionData.curveCount; ++i) { + if (parameterId == curves.at(i).id) { + curves.at(i).fadeInTime = value; + return; + } + } + } + + /** + * パラメータに対するフェードアウトの時間の設定 + * @param parameterId パラメータID + * @param value フェードアウトにかかる時間[秒] + */ + public setParameterFadeOutTime( + parameterId: CubismIdHandle, + value: number + ): void { + const curves: csmVector = this._motionData.curves; + + for (let i = 0; i < this._motionData.curveCount; ++i) { + if (parameterId == curves.at(i).id) { + curves.at(i).fadeOutTime = value; + return; + } + } + } + + /** + * パラメータに対するフェードインの時間の取得 + * @param parameterId パラメータID + * @return フェードインにかかる時間[秒] + */ + public getParameterFadeInTime(parameterId: CubismIdHandle): number { + const curves: csmVector = this._motionData.curves; + + for (let i = 0; i < this._motionData.curveCount; ++i) { + if (parameterId == curves.at(i).id) { + return curves.at(i).fadeInTime; + } + } + + return -1; + } + + /** + * パラメータに対するフェードアウトの時間を取得 + * + * @param parameterId パラメータID + * @return フェードアウトにかかる時間[秒] + */ + public getParameterFadeOutTime(parameterId: CubismIdHandle): number { + const curves: csmVector = this._motionData.curves; + + for (let i = 0; i < this._motionData.curveCount; ++i) { + if (parameterId == curves.at(i).id) { + return curves.at(i).fadeOutTime; + } + } + + return -1; + } + + /** + * 自動エフェクトがかかっているパラメータIDリストの設定 + * @param eyeBlinkParameterIds 自動まばたきがかかっているパラメータIDのリスト + * @param lipSyncParameterIds リップシンクがかかっているパラメータIDのリスト + */ + public setEffectIds( + eyeBlinkParameterIds: csmVector, + lipSyncParameterIds: csmVector + ): void { + this._eyeBlinkParameterIds = eyeBlinkParameterIds; + this._lipSyncParameterIds = lipSyncParameterIds; + } + + /** + * コンストラクタ + */ + public constructor() { + super(); + this._sourceFrameRate = 30.0; + this._loopDurationSeconds = -1.0; + this._isLoop = false; // trueから false へデフォルトを変更 + this._isLoopFadeIn = true; // ループ時にフェードインが有効かどうかのフラグ + this._lastWeight = 0.0; + this._motionData = null; + this._modelCurveIdEyeBlink = null; + this._modelCurveIdLipSync = null; + this._eyeBlinkParameterIds = null; + this._lipSyncParameterIds = null; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._motionData = void 0; + this._motionData = null; + } + + /** + * motion3.jsonをパースする。 + * + * @param motionJson motion3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public parse(motionJson: ArrayBuffer, size: number): void { + this._motionData = new CubismMotionData(); + + let json: CubismMotionJson = new CubismMotionJson(motionJson, size); + + this._motionData.duration = json.getMotionDuration(); + this._motionData.loop = json.isMotionLoop(); + this._motionData.curveCount = json.getMotionCurveCount(); + this._motionData.fps = json.getMotionFps(); + this._motionData.eventCount = json.getEventCount(); + + const areBeziersRestructed: boolean = json.getEvaluationOptionFlag( + EvaluationOptionFlag.EvaluationOptionFlag_AreBeziersRistricted + ); + + if (json.isExistMotionFadeInTime()) { + this._fadeInSeconds = + json.getMotionFadeInTime() < 0.0 ? 1.0 : json.getMotionFadeInTime(); + } else { + this._fadeInSeconds = 1.0; + } + + if (json.isExistMotionFadeOutTime()) { + this._fadeOutSeconds = + json.getMotionFadeOutTime() < 0.0 ? 1.0 : json.getMotionFadeOutTime(); + } else { + this._fadeOutSeconds = 1.0; + } + + this._motionData.curves.updateSize( + this._motionData.curveCount, + CubismMotionCurve, + true + ); + this._motionData.segments.updateSize( + json.getMotionTotalSegmentCount(), + CubismMotionSegment, + true + ); + this._motionData.points.updateSize( + json.getMotionTotalPointCount(), + CubismMotionPoint, + true + ); + this._motionData.events.updateSize( + this._motionData.eventCount, + CubismMotionEvent, + true + ); + + let totalPointCount = 0; + let totalSegmentCount = 0; + + // Curves + for ( + let curveCount = 0; + curveCount < this._motionData.curveCount; + ++curveCount + ) { + if (json.getMotionCurveTarget(curveCount) == TargetNameModel) { + this._motionData.curves.at(curveCount).type = + CubismMotionCurveTarget.CubismMotionCurveTarget_Model; + } else if (json.getMotionCurveTarget(curveCount) == TargetNameParameter) { + this._motionData.curves.at(curveCount).type = + CubismMotionCurveTarget.CubismMotionCurveTarget_Parameter; + } else if ( + json.getMotionCurveTarget(curveCount) == TargetNamePartOpacity + ) { + this._motionData.curves.at(curveCount).type = + CubismMotionCurveTarget.CubismMotionCurveTarget_PartOpacity; + } else { + CubismLogWarning( + 'Warning : Unable to get segment type from Curve! The number of "CurveCount" may be incorrect!' + ); + } + + this._motionData.curves.at(curveCount).id = json.getMotionCurveId( + curveCount + ); + + this._motionData.curves.at( + curveCount + ).baseSegmentIndex = totalSegmentCount; + + this._motionData.curves.at( + curveCount + ).fadeInTime = json.isExistMotionCurveFadeInTime(curveCount) + ? json.getMotionCurveFadeInTime(curveCount) + : -1.0; + this._motionData.curves.at( + curveCount + ).fadeOutTime = json.isExistMotionCurveFadeOutTime(curveCount) + ? json.getMotionCurveFadeOutTime(curveCount) + : -1.0; + + // Segments + for ( + let segmentPosition = 0; + segmentPosition < json.getMotionCurveSegmentCount(curveCount); + + ) { + if (segmentPosition == 0) { + this._motionData.segments.at( + totalSegmentCount + ).basePointIndex = totalPointCount; + + this._motionData.points.at( + totalPointCount + ).time = json.getMotionCurveSegment(curveCount, segmentPosition); + this._motionData.points.at( + totalPointCount + ).value = json.getMotionCurveSegment(curveCount, segmentPosition + 1); + + totalPointCount += 1; + segmentPosition += 2; + } else { + this._motionData.segments.at(totalSegmentCount).basePointIndex = + totalPointCount - 1; + } + + const segment: number = json.getMotionCurveSegment( + curveCount, + segmentPosition + ); + switch (segment) { + case CubismMotionSegmentType.CubismMotionSegmentType_Linear: { + this._motionData.segments.at(totalSegmentCount).segmentType = + CubismMotionSegmentType.CubismMotionSegmentType_Linear; + this._motionData.segments.at( + totalSegmentCount + ).evaluate = linearEvaluate; + + this._motionData.points.at( + totalPointCount + ).time = json.getMotionCurveSegment( + curveCount, + segmentPosition + 1 + ); + this._motionData.points.at( + totalPointCount + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 2 + ); + + totalPointCount += 1; + segmentPosition += 3; + + break; + } + case CubismMotionSegmentType.CubismMotionSegmentType_Bezier: { + this._motionData.segments.at(totalSegmentCount).segmentType = + CubismMotionSegmentType.CubismMotionSegmentType_Bezier; + + if (areBeziersRestructed || UseOldBeziersCurveMotion) { + this._motionData.segments.at( + totalSegmentCount + ).evaluate = bezierEvaluate; + } else { + this._motionData.segments.at( + totalSegmentCount + ).evaluate = bezierEvaluateCardanoInterpretation; + } + + this._motionData.points.at( + totalPointCount + ).time = json.getMotionCurveSegment( + curveCount, + segmentPosition + 1 + ); + this._motionData.points.at( + totalPointCount + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 2 + ); + + this._motionData.points.at( + totalPointCount + 1 + ).time = json.getMotionCurveSegment( + curveCount, + segmentPosition + 3 + ); + this._motionData.points.at( + totalPointCount + 1 + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 4 + ); + + this._motionData.points.at( + totalPointCount + 2 + ).time = json.getMotionCurveSegment( + curveCount, + segmentPosition + 5 + ); + this._motionData.points.at( + totalPointCount + 2 + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 6 + ); + + totalPointCount += 3; + segmentPosition += 7; + + break; + } + + case CubismMotionSegmentType.CubismMotionSegmentType_Stepped: { + this._motionData.segments.at(totalSegmentCount).segmentType = + CubismMotionSegmentType.CubismMotionSegmentType_Stepped; + this._motionData.segments.at( + totalSegmentCount + ).evaluate = steppedEvaluate; + + this._motionData.points.at( + totalPointCount + ).time = json.getMotionCurveSegment( + curveCount, + segmentPosition + 1 + ); + this._motionData.points.at( + totalPointCount + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 2 + ); + + totalPointCount += 1; + segmentPosition += 3; + + break; + } + + case CubismMotionSegmentType.CubismMotionSegmentType_InverseStepped: { + this._motionData.segments.at(totalSegmentCount).segmentType = + CubismMotionSegmentType.CubismMotionSegmentType_InverseStepped; + this._motionData.segments.at( + totalSegmentCount + ).evaluate = inverseSteppedEvaluate; + + this._motionData.points.at( + totalPointCount + ).time = json.getMotionCurveSegment( + curveCount, + segmentPosition + 1 + ); + this._motionData.points.at( + totalPointCount + ).value = json.getMotionCurveSegment( + curveCount, + segmentPosition + 2 + ); + + totalPointCount += 1; + segmentPosition += 3; + + break; + } + default: { + CSM_ASSERT(0); + break; + } + } + + ++this._motionData.curves.at(curveCount).segmentCount; + ++totalSegmentCount; + } + } + + for ( + let userdatacount = 0; + userdatacount < json.getEventCount(); + ++userdatacount + ) { + this._motionData.events.at(userdatacount).fireTime = json.getEventTime( + userdatacount + ); + this._motionData.events.at(userdatacount).value = json.getEventValue( + userdatacount + ); + } + + json.release(); + json = void 0; + json = null; + } + + /** + * モデルのパラメータ更新 + * + * イベント発火のチェック。 + * 入力する時間は呼ばれるモーションタイミングを0とした秒数で行う。 + * + * @param beforeCheckTimeSeconds 前回のイベントチェック時間[秒] + * @param motionTimeSeconds 今回の再生時間[秒] + */ + public getFiredEvent( + beforeCheckTimeSeconds: number, + motionTimeSeconds: number + ): csmVector { + this._firedEventValues.updateSize(0); + + // イベントの発火チェック + for (let u = 0; u < this._motionData.eventCount; ++u) { + if ( + this._motionData.events.at(u).fireTime > beforeCheckTimeSeconds && + this._motionData.events.at(u).fireTime <= motionTimeSeconds + ) { + this._firedEventValues.pushBack( + new csmString(this._motionData.events.at(u).value.s) + ); + } + } + + return this._firedEventValues; + } + + public _sourceFrameRate: number; // ロードしたファイルのFPS。記述が無ければデフォルト値15fpsとなる + public _loopDurationSeconds: number; // mtnファイルで定義される一連のモーションの長さ + public _isLoop: boolean; // ループするか? + public _isLoopFadeIn: boolean; // ループ時にフェードインが有効かどうかのフラグ。初期値では有効。 + public _lastWeight: number; // 最後に設定された重み + + public _motionData: CubismMotionData; // 実際のモーションデータ本体 + + public _eyeBlinkParameterIds: csmVector; // 自動まばたきを適用するパラメータIDハンドルのリスト。 モデル(モデルセッティング)とパラメータを対応付ける。 + public _lipSyncParameterIds: csmVector; // リップシンクを適用するパラメータIDハンドルのリスト。 モデル(モデルセッティング)とパラメータを対応付ける。 + + public _modelCurveIdEyeBlink: CubismIdHandle; // モデルが持つ自動まばたき用パラメータIDのハンドル。 モデルとモーションを対応付ける。 + public _modelCurveIdLipSync: CubismIdHandle; // モデルが持つリップシンク用パラメータIDのハンドル。 モデルとモーションを対応付ける。 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmotion'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMotion = $.CubismMotion; + export type CubismMotion = $.CubismMotion; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotioninternal.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotioninternal.ts new file mode 100644 index 000000000..8b48faa4c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotioninternal.ts @@ -0,0 +1,156 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { csmString } from '../type/csmstring'; +import { csmVector } from '../type/csmvector'; + +/** + * @brief モーションカーブの種類 + * + * モーションカーブの種類。 + */ +export enum CubismMotionCurveTarget { + CubismMotionCurveTarget_Model, // モデルに対して + CubismMotionCurveTarget_Parameter, // パラメータに対して + CubismMotionCurveTarget_PartOpacity // パーツの不透明度に対して +} + +/** + * @brief モーションカーブのセグメントの種類 + * + * モーションカーブのセグメントの種類。 + */ +export enum CubismMotionSegmentType { + CubismMotionSegmentType_Linear = 0, // リニア + CubismMotionSegmentType_Bezier = 1, // ベジェ曲線 + CubismMotionSegmentType_Stepped = 2, // ステップ + CubismMotionSegmentType_InverseStepped = 3 // インバースステップ +} + +/** + * @brief モーションカーブの制御点 + * + * モーションカーブの制御点。 + */ +export class CubismMotionPoint { + time = 0.0; // 時間[秒] + value = 0.0; // 値 +} + +/** + * モーションカーブのセグメントの評価関数 + * + * @param points モーションカーブの制御点リスト + * @param time 評価する時間[秒] + */ +export interface csmMotionSegmentEvaluationFunction { + (points: CubismMotionPoint[], time: number): number; +} + +/** + * @brief モーションカーブのセグメント + * + * モーションカーブのセグメント。 + */ +export class CubismMotionSegment { + /** + * @brief コンストラクタ + * + * コンストラクタ。 + */ + public constructor() { + this.evaluate = null; + this.basePointIndex = 0; + this.segmentType = 0; + } + + evaluate: csmMotionSegmentEvaluationFunction; // 使用する評価関数 + basePointIndex: number; // 最初のセグメントへのインデックス + segmentType: number; // セグメントの種類 +} + +/** + * @brief モーションカーブ + * + * モーションカーブ。 + */ +export class CubismMotionCurve { + public constructor() { + this.type = CubismMotionCurveTarget.CubismMotionCurveTarget_Model; + this.segmentCount = 0; + this.baseSegmentIndex = 0; + this.fadeInTime = 0.0; + this.fadeOutTime = 0.0; + } + + type: CubismMotionCurveTarget; // カーブの種類 + id: CubismIdHandle; // カーブのID + segmentCount: number; // セグメントの個数 + baseSegmentIndex: number; // 最初のセグメントのインデックス + fadeInTime: number; // フェードインにかかる時間[秒] + fadeOutTime: number; // フェードアウトにかかる時間[秒] +} + +/** + * イベント。 + */ +export class CubismMotionEvent { + fireTime = 0.0; + value: csmString; +} + +/** + * @brief モーションデータ + * + * モーションデータ。 + */ +export class CubismMotionData { + public constructor() { + this.duration = 0.0; + this.loop = false; + this.curveCount = 0; + this.eventCount = 0; + this.fps = 0.0; + + this.curves = new csmVector(); + this.segments = new csmVector(); + this.points = new csmVector(); + this.events = new csmVector(); + } + + duration: number; // モーションの長さ[秒] + loop: boolean; // ループするかどうか + curveCount: number; // カーブの個数 + eventCount: number; // UserDataの個数 + fps: number; // フレームレート + curves: csmVector; // カーブのリスト + segments: csmVector; // セグメントのリスト + points: csmVector; // ポイントのリスト + events: csmVector; // イベントのリスト +} + +// Namespace definition for compatibility. +import * as $ from './cubismmotioninternal'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMotionCurve = $.CubismMotionCurve; + export type CubismMotionCurve = $.CubismMotionCurve; + export const CubismMotionCurveTarget = $.CubismMotionCurveTarget; + export type CubismMotionCurveTarget = $.CubismMotionCurveTarget; + export const CubismMotionData = $.CubismMotionData; + export type CubismMotionData = $.CubismMotionData; + export const CubismMotionEvent = $.CubismMotionEvent; + export type CubismMotionEvent = $.CubismMotionEvent; + export const CubismMotionPoint = $.CubismMotionPoint; + export type CubismMotionPoint = $.CubismMotionPoint; + export const CubismMotionSegment = $.CubismMotionSegment; + export type CubismMotionSegment = $.CubismMotionSegment; + export const CubismMotionSegmentType = $.CubismMotionSegmentType; + export type CubismMotionSegmentType = $.CubismMotionSegmentType; + export type csmMotionSegmentEvaluationFunction = $.csmMotionSegmentEvaluationFunction; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionjson.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionjson.ts new file mode 100644 index 000000000..eace15b12 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionjson.ts @@ -0,0 +1,383 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { csmString } from '../type/csmstring'; +import { CubismJson } from '../utils/cubismjson'; + +// JSON keys +const Meta = 'Meta'; +const Duration = 'Duration'; +const Loop = 'Loop'; +const AreBeziersRestricted = 'AreBeziersRestricted'; +const CurveCount = 'CurveCount'; +const Fps = 'Fps'; +const TotalSegmentCount = 'TotalSegmentCount'; +const TotalPointCount = 'TotalPointCount'; +const Curves = 'Curves'; +const Target = 'Target'; +const Id = 'Id'; +const FadeInTime = 'FadeInTime'; +const FadeOutTime = 'FadeOutTime'; +const Segments = 'Segments'; +const UserData = 'UserData'; +const UserDataCount = 'UserDataCount'; +const TotalUserDataSize = 'TotalUserDataSize'; +const Time = 'Time'; +const Value = 'Value'; + +/** + * motion3.jsonのコンテナ。 + */ +export class CubismMotionJson { + /** + * コンストラクタ + * @param buffer motion3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public constructor(buffer: ArrayBuffer, size: number) { + this._json = CubismJson.create(buffer, size); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CubismJson.delete(this._json); + } + + /** + * モーションの長さを取得する + * @return モーションの長さ[秒] + */ + public getMotionDuration(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(Duration) + .toFloat(); + } + + /** + * モーションのループ情報の取得 + * @return true ループする + * @return false ループしない + */ + public isMotionLoop(): boolean { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(Loop) + .toBoolean(); + } + + public getEvaluationOptionFlag(flagType: number): boolean { + if ( + EvaluationOptionFlag.EvaluationOptionFlag_AreBeziersRistricted == flagType + ) { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(AreBeziersRestricted) + .toBoolean(); + } + + return false; + } + + /** + * モーションカーブの個数の取得 + * @return モーションカーブの個数 + */ + public getMotionCurveCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(CurveCount) + .toInt(); + } + + /** + * モーションのフレームレートの取得 + * @return フレームレート[FPS] + */ + public getMotionFps(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(Fps) + .toFloat(); + } + + /** + * モーションのセグメントの総合計の取得 + * @return モーションのセグメントの取得 + */ + public getMotionTotalSegmentCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalSegmentCount) + .toInt(); + } + + /** + * モーションのカーブの制御店の総合計の取得 + * @return モーションのカーブの制御点の総合計 + */ + public getMotionTotalPointCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalPointCount) + .toInt(); + } + + /** + * モーションのフェードイン時間の存在 + * @return true 存在する + * @return false 存在しない + */ + public isExistMotionFadeInTime(): boolean { + return !this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(FadeInTime) + .isNull(); + } + + /** + * モーションのフェードアウト時間の存在 + * @return true 存在する + * @return false 存在しない + */ + public isExistMotionFadeOutTime(): boolean { + return !this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(FadeOutTime) + .isNull(); + } + + /** + * モーションのフェードイン時間の取得 + * @return フェードイン時間[秒] + */ + public getMotionFadeInTime(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(FadeInTime) + .toFloat(); + } + + /** + * モーションのフェードアウト時間の取得 + * @return フェードアウト時間[秒] + */ + public getMotionFadeOutTime(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(FadeOutTime) + .toFloat(); + } + + /** + * モーションのカーブの種類の取得 + * @param curveIndex カーブのインデックス + * @return カーブの種類 + */ + public getMotionCurveTarget(curveIndex: number): string { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(Target) + .getRawString(); + } + + /** + * モーションのカーブのIDの取得 + * @param curveIndex カーブのインデックス + * @return カーブのID + */ + public getMotionCurveId(curveIndex: number): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * モーションのカーブのフェードイン時間の存在 + * @param curveIndex カーブのインデックス + * @return true 存在する + * @return false 存在しない + */ + public isExistMotionCurveFadeInTime(curveIndex: number): boolean { + return !this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(FadeInTime) + .isNull(); + } + + /** + * モーションのカーブのフェードアウト時間の存在 + * @param curveIndex カーブのインデックス + * @return true 存在する + * @return false 存在しない + */ + public isExistMotionCurveFadeOutTime(curveIndex: number): boolean { + return !this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(FadeOutTime) + .isNull(); + } + + /** + * モーションのカーブのフェードイン時間の取得 + * @param curveIndex カーブのインデックス + * @return フェードイン時間[秒] + */ + public getMotionCurveFadeInTime(curveIndex: number): number { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(FadeInTime) + .toFloat(); + } + + /** + * モーションのカーブのフェードアウト時間の取得 + * @param curveIndex カーブのインデックス + * @return フェードアウト時間[秒] + */ + public getMotionCurveFadeOutTime(curveIndex: number): number { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(FadeOutTime) + .toFloat(); + } + + /** + * モーションのカーブのセグメントの個数を取得する + * @param curveIndex カーブのインデックス + * @return モーションのカーブのセグメントの個数 + */ + public getMotionCurveSegmentCount(curveIndex: number): number { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(Segments) + .getVector() + .getSize(); + } + + /** + * モーションのカーブのセグメントの値の取得 + * @param curveIndex カーブのインデックス + * @param segmentIndex セグメントのインデックス + * @return セグメントの値 + */ + public getMotionCurveSegment( + curveIndex: number, + segmentIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(Curves) + .getValueByIndex(curveIndex) + .getValueByString(Segments) + .getValueByIndex(segmentIndex) + .toFloat(); + } + + /** + * イベントの個数の取得 + * @return イベントの個数 + */ + public getEventCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(UserDataCount) + .toInt(); + } + + /** + * イベントの総文字数の取得 + * @return イベントの総文字数 + */ + public getTotalEventValueSize(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalUserDataSize) + .toInt(); + } + + /** + * イベントの時間の取得 + * @param userDataIndex イベントのインデックス + * @return イベントの時間[秒] + */ + public getEventTime(userDataIndex: number): number { + return this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(userDataIndex) + .getValueByString(Time) + .toFloat(); + } + + /** + * イベントの取得 + * @param userDataIndex イベントのインデックス + * @return イベントの文字列 + */ + public getEventValue(userDataIndex: number): csmString { + return new csmString( + this._json + .getRoot() + .getValueByString(UserData) + .getValueByIndex(userDataIndex) + .getValueByString(Value) + .getRawString() + ); + } + + _json: CubismJson; // motion3.jsonのデータ +} + +/** + * @brief ベジェカーブの解釈方法のフラグタイプ + */ +export enum EvaluationOptionFlag { + EvaluationOptionFlag_AreBeziersRistricted = 0 ///< ベジェハンドルの規制状態 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmotionjson'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMotionJson = $.CubismMotionJson; + export type CubismMotionJson = $.CubismMotionJson; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionmanager.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionmanager.ts new file mode 100644 index 000000000..9d1ea486f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionmanager.ts @@ -0,0 +1,126 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismModel } from '../model/cubismmodel'; +import { ACubismMotion } from './acubismmotion'; +import { + CubismMotionQueueEntryHandle, + CubismMotionQueueManager +} from './cubismmotionqueuemanager'; + +/** + * モーションの管理 + * + * モーションの管理を行うクラス + */ +export class CubismMotionManager extends CubismMotionQueueManager { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._currentPriority = 0; + this._reservePriority = 0; + } + + /** + * 再生中のモーションの優先度の取得 + * @return モーションの優先度 + */ + public getCurrentPriority(): number { + return this._currentPriority; + } + + /** + * 予約中のモーションの優先度を取得する。 + * @return モーションの優先度 + */ + public getReservePriority(): number { + return this._reservePriority; + } + + /** + * 予約中のモーションの優先度を設定する。 + * @param val 優先度 + */ + public setReservePriority(val: number): void { + this._reservePriority = val; + } + + /** + * 優先度を設定してモーションを開始する。 + * + * @param motion モーション + * @param autoDelete 再生が狩猟したモーションのインスタンスを削除するならtrue + * @param priority 優先度 + * @return 開始したモーションの識別番号を返す。個別のモーションが終了したか否かを判定するIsFinished()の引数で使用する。開始できない時は「-1」 + */ + public startMotionPriority( + motion: ACubismMotion, + autoDelete: boolean, + priority: number + ): CubismMotionQueueEntryHandle { + if (priority == this._reservePriority) { + this._reservePriority = 0; // 予約を解除 + } + + this._currentPriority = priority; // 再生中モーションの優先度を設定 + + return super.startMotion(motion, autoDelete, this._userTimeSeconds); + } + + /** + * モーションを更新して、モデルにパラメータ値を反映する。 + * + * @param model 対象のモデル + * @param deltaTimeSeconds デルタ時間[秒] + * @return true 更新されている + * @return false 更新されていない + */ + public updateMotion(model: CubismModel, deltaTimeSeconds: number): boolean { + this._userTimeSeconds += deltaTimeSeconds; + + const updated: boolean = super.doUpdateMotion(model, this._userTimeSeconds); + + if (this.isFinished()) { + this._currentPriority = 0; // 再生中のモーションの優先度を解除 + } + + return updated; + } + + /** + * モーションを予約する。 + * + * @param priority 優先度 + * @return true 予約できた + * @return false 予約できなかった + */ + public reserveMotion(priority: number): boolean { + if ( + priority <= this._reservePriority || + priority <= this._currentPriority + ) { + return false; + } + + this._reservePriority = priority; + + return true; + } + + _currentPriority: number; // 現在再生中のモーションの優先度 + _reservePriority: number; // 再生予定のモーションの優先度。再生中は0になる。モーションファイルを別スレッドで読み込むときの機能。 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmotionmanager'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMotionManager = $.CubismMotionManager; + export type CubismMotionManager = $.CubismMotionManager; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionqueueentry.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionqueueentry.ts new file mode 100644 index 000000000..3fa75d572 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionqueueentry.ts @@ -0,0 +1,253 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ACubismMotion } from './acubismmotion'; +import { CubismMotionQueueEntryHandle } from './cubismmotionqueuemanager'; + +/** + * CubismMotionQueueManagerで再生している各モーションの管理クラス。 + */ +export class CubismMotionQueueEntry { + /** + * コンストラクタ + */ + public constructor() { + this._autoDelete = false; + this._motion = null; + this._available = true; + this._finished = false; + this._started = false; + this._startTimeSeconds = -1.0; + this._fadeInStartTimeSeconds = 0.0; + this._endTimeSeconds = -1.0; + this._stateTimeSeconds = 0.0; + this._stateWeight = 0.0; + this._lastEventCheckSeconds = 0.0; + this._motionQueueEntryHandle = this; + this._fadeOutSeconds = 0.0; + this._isTriggeredFadeOut = false; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + if (this._autoDelete && this._motion) { + ACubismMotion.delete(this._motion); // + } + } + + /** + * フェードアウト時間と開始判定の設定 + * @param fadeOutSeconds フェードアウトにかかる時間[秒] + */ + public setFadeOut(fadeOutSeconds: number): void { + this._fadeOutSeconds = fadeOutSeconds; + this._isTriggeredFadeOut = true; + } + + /** + * フェードアウトの開始 + * @param fadeOutSeconds フェードアウトにかかる時間[秒] + * @param userTimeSeconds デルタ時間の積算値[秒] + */ + public startFadeOut(fadeOutSeconds: number, userTimeSeconds: number): void { + const newEndTimeSeconds: number = userTimeSeconds + fadeOutSeconds; + this._isTriggeredFadeOut = true; + + if ( + this._endTimeSeconds < 0.0 || + newEndTimeSeconds < this._endTimeSeconds + ) { + this._endTimeSeconds = newEndTimeSeconds; + } + } + + /** + * モーションの終了の確認 + * + * @return true モーションが終了した + * @return false 終了していない + */ + public isFinished(): boolean { + return this._finished; + } + + /** + * モーションの開始の確認 + * @return true モーションが開始した + * @return false 開始していない + */ + public isStarted(): boolean { + return this._started; + } + + /** + * モーションの開始時刻の取得 + * @return モーションの開始時刻[秒] + */ + public getStartTime(): number { + return this._startTimeSeconds; + } + + /** + * フェードインの開始時刻の取得 + * @return フェードインの開始時刻[秒] + */ + public getFadeInStartTime(): number { + return this._fadeInStartTimeSeconds; + } + + /** + * フェードインの終了時刻の取得 + * @return フェードインの終了時刻の取得 + */ + public getEndTime(): number { + return this._endTimeSeconds; + } + + /** + * モーションの開始時刻の設定 + * @param startTime モーションの開始時刻 + */ + public setStartTime(startTime: number): void { + this._startTimeSeconds = startTime; + } + + /** + * フェードインの開始時刻の設定 + * @param startTime フェードインの開始時刻[秒] + */ + public setFadeInStartTime(startTime: number): void { + this._fadeInStartTimeSeconds = startTime; + } + + /** + * フェードインの終了時刻の設定 + * @param endTime フェードインの終了時刻[秒] + */ + public setEndTime(endTime: number): void { + this._endTimeSeconds = endTime; + } + + /** + * モーションの終了の設定 + * @param f trueならモーションの終了 + */ + public setIsFinished(f: boolean): void { + this._finished = f; + } + + /** + * モーション開始の設定 + * @param f trueならモーションの開始 + */ + public setIsStarted(f: boolean): void { + this._started = f; + } + + /** + * モーションの有効性の確認 + * @return true モーションは有効 + * @return false モーションは無効 + */ + public isAvailable(): boolean { + return this._available; + } + + /** + * モーションの有効性の設定 + * @param v trueならモーションは有効 + */ + public setIsAvailable(v: boolean): void { + this._available = v; + } + + /** + * モーションの状態の設定 + * @param timeSeconds 現在時刻[秒] + * @param weight モーション尾重み + */ + public setState(timeSeconds: number, weight: number): void { + this._stateTimeSeconds = timeSeconds; + this._stateWeight = weight; + } + + /** + * モーションの現在時刻の取得 + * @return モーションの現在時刻[秒] + */ + public getStateTime(): number { + return this._stateTimeSeconds; + } + + /** + * モーションの重みの取得 + * @return モーションの重み + */ + public getStateWeight(): number { + return this._stateWeight; + } + + /** + * 最後にイベントの発火をチェックした時間を取得 + * + * @return 最後にイベントの発火をチェックした時間[秒] + */ + public getLastCheckEventSeconds(): number { + return this._lastEventCheckSeconds; + } + + /** + * 最後にイベントをチェックした時間を設定 + * @param checkSeconds 最後にイベントをチェックした時間[秒] + */ + public setLastCheckEventSeconds(checkSeconds: number): void { + this._lastEventCheckSeconds = checkSeconds; + } + + /** + * フェードアウト開始判定の取得 + * @return フェードアウト開始するかどうか + */ + public isTriggeredFadeOut(): boolean { + return this._isTriggeredFadeOut; + } + + /** + * フェードアウト時間の取得 + * @return フェードアウト時間[秒] + */ + public getFadeOutSeconds(): number { + return this._fadeOutSeconds; + } + + _autoDelete: boolean; // 自動削除 + _motion: ACubismMotion; // モーション + + _available: boolean; // 有効化フラグ + _finished: boolean; // 終了フラグ + _started: boolean; // 開始フラグ + _startTimeSeconds: number; // モーション再生開始時刻[秒] + _fadeInStartTimeSeconds: number; // フェードイン開始時刻(ループの時は初回のみ)[秒] + _endTimeSeconds: number; // 終了予定時刻[秒] + _stateTimeSeconds: number; // 時刻の状態[秒] + _stateWeight: number; // 重みの状態 + _lastEventCheckSeconds: number; // 最終のMotion側のチェックした時間 + private _fadeOutSeconds: number; // フェードアウト時間[秒] + private _isTriggeredFadeOut: boolean; // フェードアウト開始フラグ + + _motionQueueEntryHandle: CubismMotionQueueEntryHandle; // インスタンスごとに一意の値を持つ識別番号 +} + +// Namespace definition for compatibility. +import * as $ from './cubismmotionqueueentry'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMotionQueueEntry = $.CubismMotionQueueEntry; + export type CubismMotionQueueEntry = $.CubismMotionQueueEntry; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionqueuemanager.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionqueuemanager.ts new file mode 100644 index 000000000..2aa5565b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/motion/cubismmotionqueuemanager.ts @@ -0,0 +1,342 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { ACubismMotion } from './acubismmotion'; +import { CubismMotionQueueEntry } from './cubismmotionqueueentry'; +import { csmVector, iterator } from '../type/csmvector'; +import { CubismModel } from '../model/cubismmodel'; +import { csmString } from '../type/csmstring'; + +/** + * モーション再生の管理 + * + * モーション再生の管理用クラス。CubismMotionモーションなどACubismMotionのサブクラスを再生するために使用する。 + * + * @note 再生中に別のモーションが StartMotion()された場合は、新しいモーションに滑らかに変化し旧モーションは中断する。 + * 表情用モーション、体用モーションなどを分けてモーション化した場合など、 + * 複数のモーションを同時に再生させる場合は、複数のCubismMotionQueueManagerインスタンスを使用する。 + */ +export class CubismMotionQueueManager { + /** + * コンストラクタ + */ + public constructor() { + this._userTimeSeconds = 0.0; + this._eventCallBack = null; + this._eventCustomData = null; + this._motions = new csmVector(); + } + + /** + * デストラクタ + */ + public release(): void { + for (let i = 0; i < this._motions.getSize(); ++i) { + if (this._motions.at(i)) { + this._motions.at(i).release(); + this._motions.set(i, null); + } + } + + this._motions = null; + } + + /** + * 指定したモーションの開始 + * + * 指定したモーションを開始する。同じタイプのモーションが既にある場合は、既存のモーションに終了フラグを立て、フェードアウトを開始させる。 + * + * @param motion 開始するモーション + * @param autoDelete 再生が終了したモーションのインスタンスを削除するなら true + * @param userTimeSeconds デルタ時間の積算値[秒] + * @return 開始したモーションの識別番号を返す。個別のモーションが終了したか否かを判定するIsFinished()の引数で使用する。開始できない時は「-1」 + */ + public startMotion( + motion: ACubismMotion, + autoDelete: boolean, + userTimeSeconds: number + ): CubismMotionQueueEntryHandle { + if (motion == null) { + return InvalidMotionQueueEntryHandleValue; + } + + let motionQueueEntry: CubismMotionQueueEntry = null; + + // 既にモーションがあれば終了フラグを立てる + for (let i = 0; i < this._motions.getSize(); ++i) { + motionQueueEntry = this._motions.at(i); + if (motionQueueEntry == null) { + continue; + } + + motionQueueEntry.setFadeOut(motionQueueEntry._motion.getFadeOutTime()); // フェードアウト設定 + } + + motionQueueEntry = new CubismMotionQueueEntry(); // 終了時に破棄する + motionQueueEntry._autoDelete = autoDelete; + motionQueueEntry._motion = motion; + + this._motions.pushBack(motionQueueEntry); + + return motionQueueEntry._motionQueueEntryHandle; + } + + /** + * 全てのモーションの終了の確認 + * @return true 全て終了している + * @return false 終了していない + */ + public isFinished(): boolean { + // ------- 処理を行う ------- + // 既にモーションがあれば終了フラグを立てる + + for ( + let ite: iterator = this._motions.begin(); + ite.notEqual(this._motions.end()); + + ) { + let motionQueueEntry: CubismMotionQueueEntry = ite.ptr(); + + if (motionQueueEntry == null) { + ite = this._motions.erase(ite); // 削除 + continue; + } + + const motion: ACubismMotion = motionQueueEntry._motion; + + if (motion == null) { + motionQueueEntry.release(); + motionQueueEntry = null; + ite = this._motions.erase(ite); // 削除 + continue; + } + + // ----- 終了済みの処理があれば削除する ------ + if (!motionQueueEntry.isFinished()) { + return false; + } else { + ite.preIncrement(); + } + } + + return true; + } + + /** + * 指定したモーションの終了の確認 + * @param motionQueueEntryNumber モーションの識別番号 + * @return true 全て終了している + * @return false 終了していない + */ + public isFinishedByHandle( + motionQueueEntryNumber: CubismMotionQueueEntryHandle + ): boolean { + for ( + let ite: iterator = this._motions.begin(); + ite.notEqual(this._motions.end()); + ite.increment() + ) { + const motionQueueEntry: CubismMotionQueueEntry = ite.ptr(); + + if (motionQueueEntry == null) { + continue; + } + + if ( + motionQueueEntry._motionQueueEntryHandle == motionQueueEntryNumber && + !motionQueueEntry.isFinished() + ) { + return false; + } + } + return true; + } + + /** + * 全てのモーションを停止する + */ + public stopAllMotions(): void { + // ------- 処理を行う ------- + // 既にモーションがあれば終了フラグを立てる + + for ( + let ite: iterator = this._motions.begin(); + ite.notEqual(this._motions.end()); + + ) { + let motionQueueEntry: CubismMotionQueueEntry = ite.ptr(); + + if (motionQueueEntry == null) { + ite = this._motions.erase(ite); + + continue; + } + + // ----- 終了済みの処理があれば削除する ------ + motionQueueEntry.release(); + motionQueueEntry = null; + ite = this._motions.erase(ite); // 削除 + } + } + + /** + * 指定したCubismMotionQueueEntryの取得 + + * @param motionQueueEntryNumber モーションの識別番号 + * @return 指定したCubismMotionQueueEntry + * @return null 見つからなかった + */ + public getCubismMotionQueueEntry( + motionQueueEntryNumber: any + ): CubismMotionQueueEntry { + //------- 処理を行う ------- + for ( + let ite: iterator = this._motions.begin(); + ite.notEqual(this._motions.end()); + ite.preIncrement() + ) { + const motionQueueEntry: CubismMotionQueueEntry = ite.ptr(); + + if (motionQueueEntry == null) { + continue; + } + + if (motionQueueEntry._motionQueueEntryHandle == motionQueueEntryNumber) { + return motionQueueEntry; + } + } + + return null; + } + + /** + * イベントを受け取るCallbackの登録 + * + * @param callback コールバック関数 + * @param customData コールバックに返されるデータ + */ + public setEventCallback( + callback: CubismMotionEventFunction, + customData: any = null + ): void { + this._eventCallBack = callback; + this._eventCustomData = customData; + } + + /** + * モーションを更新して、モデルにパラメータ値を反映する。 + * + * @param model 対象のモデル + * @param userTimeSeconds デルタ時間の積算値[秒] + * @return true モデルへパラメータ値の反映あり + * @return false モデルへパラメータ値の反映なし(モーションの変化なし) + */ + public doUpdateMotion(model: CubismModel, userTimeSeconds: number): boolean { + let updated = false; + + // ------- 処理を行う -------- + // 既にモーションがあれば終了フラグを立てる + + for ( + let ite: iterator = this._motions.begin(); + ite.notEqual(this._motions.end()); + + ) { + let motionQueueEntry: CubismMotionQueueEntry = ite.ptr(); + + if (motionQueueEntry == null) { + ite = this._motions.erase(ite); // 削除 + continue; + } + + const motion: ACubismMotion = motionQueueEntry._motion; + + if (motion == null) { + motionQueueEntry.release(); + motionQueueEntry = null; + ite = this._motions.erase(ite); // 削除 + + continue; + } + + // ------ 値を反映する ------ + motion.updateParameters(model, motionQueueEntry, userTimeSeconds); + updated = true; + + // ------ ユーザトリガーイベントを検査する ---- + const firedList: csmVector = motion.getFiredEvent( + motionQueueEntry.getLastCheckEventSeconds() - + motionQueueEntry.getStartTime(), + userTimeSeconds - motionQueueEntry.getStartTime() + ); + + for (let i = 0; i < firedList.getSize(); ++i) { + this._eventCallBack(this, firedList.at(i), this._eventCustomData); + } + + motionQueueEntry.setLastCheckEventSeconds(userTimeSeconds); + + // ------ 終了済みの処理があれば削除する ------ + if (motionQueueEntry.isFinished()) { + motionQueueEntry.release(); + motionQueueEntry = null; + ite = this._motions.erase(ite); // 削除 + } else { + if (motionQueueEntry.isTriggeredFadeOut()) { + motionQueueEntry.startFadeOut( + motionQueueEntry.getFadeOutSeconds(), + userTimeSeconds + ); + } + ite.preIncrement(); + } + } + + return updated; + } + _userTimeSeconds: number; // デルタ時間の積算値[秒] + + _motions: csmVector; // モーション + _eventCallBack: CubismMotionEventFunction; // コールバック関数 + _eventCustomData: any; // コールバックに戻されるデータ +} + +/** + * イベントのコールバック関数を定義 + * + * イベントのコールバックに登録できる関数の型情報 + * @param caller 発火したイベントを再生させたCubismMotionQueueManager + * @param eventValue 発火したイベントの文字列データ + * @param customData コールバックに返される登録時に指定されたデータ + */ +export interface CubismMotionEventFunction { + ( + caller: CubismMotionQueueManager, + eventValue: csmString, + customData: any + ): void; +} + +/** + * モーションの識別番号 + * + * モーションの識別番号の定義 + */ +export declare type CubismMotionQueueEntryHandle = any; +export const InvalidMotionQueueEntryHandleValue: CubismMotionQueueEntryHandle = -1; + +// Namespace definition for compatibility. +import * as $ from './cubismmotionqueuemanager'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismMotionQueueManager = $.CubismMotionQueueManager; + export type CubismMotionQueueManager = $.CubismMotionQueueManager; + export const InvalidMotionQueueEntryHandleValue = + $.InvalidMotionQueueEntryHandleValue; + export type CubismMotionQueueEntryHandle = $.CubismMotionQueueEntryHandle; + export type CubismMotionEventFunction = $.CubismMotionEventFunction; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/physics/cubismphysics.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/physics/cubismphysics.ts new file mode 100644 index 000000000..9fe9a5029 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/physics/cubismphysics.ts @@ -0,0 +1,934 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMath } from '../math/cubismmath'; +import { CubismVector2 } from '../math/cubismvector2'; +import { CubismModel } from '../model/cubismmodel'; +import { + CubismPhysicsInput, + CubismPhysicsNormalization, + CubismPhysicsOutput, + CubismPhysicsParticle, + CubismPhysicsRig, + CubismPhysicsSource, + CubismPhysicsSubRig, + CubismPhysicsTargetType +} from './cubismphysicsinternal'; +import { CubismPhysicsJson } from './cubismphysicsjson'; + +// physics types tags. +const PhysicsTypeTagX = 'X'; +const PhysicsTypeTagY = 'Y'; +const PhysicsTypeTagAngle = 'Angle'; + +// Constant of air resistance. +const AirResistance = 5.0; + +// Constant of maximum weight of input and output ratio. +const MaximumWeight = 100.0; + +// Constant of threshold of movement. +const MovementThreshold = 0.001; + +/** + * 物理演算クラス + */ +export class CubismPhysics { + /** + * インスタンスの作成 + * @param buffer physics3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + * @return 作成されたインスタンス + */ + public static create(buffer: ArrayBuffer, size: number): CubismPhysics { + const ret: CubismPhysics = new CubismPhysics(); + + ret.parse(buffer, size); + ret._physicsRig.gravity.y = 0; + + return ret; + } + + /** + * インスタンスを破棄する + * @param physics 破棄するインスタンス + */ + public static delete(physics: CubismPhysics): void { + if (physics != null) { + physics.release(); + physics = null; + } + } + + /** + * 物理演算の評価 + * @param model 物理演算の結果を適用するモデル + * @param deltaTimeSeconds デルタ時間[秒] + */ + public evaluate(model: CubismModel, deltaTimeSeconds: number): void { + let totalAngle: { angle: number }; + let weight: number; + let radAngle: number; + let outputValue: number; + const totalTranslation: CubismVector2 = new CubismVector2(); + let currentSetting: CubismPhysicsSubRig; + let currentInput: CubismPhysicsInput[]; + let currentOutput: CubismPhysicsOutput[]; + let currentParticles: CubismPhysicsParticle[]; + + let parameterValue: Float32Array; + let parameterMaximumValue: Float32Array; + let parameterMinimumValue: Float32Array; + let parameterDefaultValue: Float32Array; + + parameterValue = model.getModel().parameters.values; + parameterMaximumValue = model.getModel().parameters.maximumValues; + parameterMinimumValue = model.getModel().parameters.minimumValues; + parameterDefaultValue = model.getModel().parameters.defaultValues; + + for ( + let settingIndex = 0; + settingIndex < this._physicsRig.subRigCount; + ++settingIndex + ) { + totalAngle = { angle: 0.0 }; + totalTranslation.x = 0.0; + totalTranslation.y = 0.0; + currentSetting = this._physicsRig.settings.at(settingIndex); + currentInput = this._physicsRig.inputs.get(currentSetting.baseInputIndex); + currentOutput = this._physicsRig.outputs.get( + currentSetting.baseOutputIndex + ); + currentParticles = this._physicsRig.particles.get( + currentSetting.baseParticleIndex + ); + + // Load input parameters + for (let i = 0; i < currentSetting.inputCount; ++i) { + weight = currentInput[i].weight / MaximumWeight; + + if (currentInput[i].sourceParameterIndex == -1) { + currentInput[i].sourceParameterIndex = model.getParameterIndex( + currentInput[i].source.id + ); + } + + currentInput[i].getNormalizedParameterValue( + totalTranslation, + totalAngle, + parameterValue[currentInput[i].sourceParameterIndex], + parameterMinimumValue[currentInput[i].sourceParameterIndex], + parameterMaximumValue[currentInput[i].sourceParameterIndex], + parameterDefaultValue[currentInput[i].sourceParameterIndex], + currentSetting.normalizationPosition, + currentSetting.normalizationAngle, + currentInput[i].reflect, + weight + ); + } + + radAngle = CubismMath.degreesToRadian(-totalAngle.angle); + + totalTranslation.x = + totalTranslation.x * CubismMath.cos(radAngle) - + totalTranslation.y * CubismMath.sin(radAngle); + totalTranslation.y = + totalTranslation.x * CubismMath.sin(radAngle) + + totalTranslation.y * CubismMath.cos(radAngle); + + // Calculate particles position. + updateParticles( + currentParticles, + currentSetting.particleCount, + totalTranslation, + totalAngle.angle, + this._options.wind, + MovementThreshold * currentSetting.normalizationPosition.maximum, + deltaTimeSeconds, + AirResistance + ); + + // Update output parameters. + for (let i = 0; i < currentSetting.outputCount; ++i) { + const particleIndex = currentOutput[i].vertexIndex; + + if ( + particleIndex < 1 || + particleIndex >= currentSetting.particleCount + ) { + break; + } + + if (currentOutput[i].destinationParameterIndex == -1) { + currentOutput[i].destinationParameterIndex = model.getParameterIndex( + currentOutput[i].destination.id + ); + } + + const translation: CubismVector2 = new CubismVector2(); + translation.x = + currentParticles[particleIndex].position.x - + currentParticles[particleIndex - 1].position.x; + translation.y = + currentParticles[particleIndex].position.y - + currentParticles[particleIndex - 1].position.y; + + outputValue = currentOutput[i].getValue( + translation, + currentParticles, + particleIndex, + currentOutput[i].reflect, + this._options.gravity + ); + + const destinationParameterIndex: number = + currentOutput[i].destinationParameterIndex; + const outParameterValue: Float32Array = + !Float32Array.prototype.slice && 'subarray' in Float32Array.prototype + ? JSON.parse( + JSON.stringify( + parameterValue.subarray(destinationParameterIndex) + ) + ) // 値渡しするため、JSON.parse, JSON.stringify + : parameterValue.slice(destinationParameterIndex); + + updateOutputParameterValue( + outParameterValue, + parameterMinimumValue[destinationParameterIndex], + parameterMaximumValue[destinationParameterIndex], + outputValue, + currentOutput[i] + ); + + // 値を反映 + for ( + let offset: number = destinationParameterIndex, outParamIndex = 0; + offset < parameterValue.length; + offset++, outParamIndex++ + ) { + parameterValue[offset] = outParameterValue[outParamIndex]; + } + } + } + } + + /** + * オプションの設定 + * @param options オプション + */ + public setOptions(options: Options): void { + this._options = options; + } + + /** + * オプションの取得 + * @return オプション + */ + public getOption(): Options { + return this._options; + } + + /** + * コンストラクタ + */ + public constructor() { + this._physicsRig = null; + + // set default options + this._options = new Options(); + this._options.gravity.y = -1.0; + this._options.gravity.x = 0; + this._options.wind.x = 0; + this._options.wind.y = 0; + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._physicsRig = void 0; + this._physicsRig = null; + } + + /** + * physics3.jsonをパースする。 + * @param physicsJson physics3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public parse(physicsJson: ArrayBuffer, size: number): void { + this._physicsRig = new CubismPhysicsRig(); + + let json: CubismPhysicsJson = new CubismPhysicsJson(physicsJson, size); + + this._physicsRig.gravity = json.getGravity(); + this._physicsRig.wind = json.getWind(); + this._physicsRig.subRigCount = json.getSubRigCount(); + + this._physicsRig.settings.updateSize( + this._physicsRig.subRigCount, + CubismPhysicsSubRig, + true + ); + this._physicsRig.inputs.updateSize( + json.getTotalInputCount(), + CubismPhysicsInput, + true + ); + this._physicsRig.outputs.updateSize( + json.getTotalOutputCount(), + CubismPhysicsOutput, + true + ); + this._physicsRig.particles.updateSize( + json.getVertexCount(), + CubismPhysicsParticle, + true + ); + + let inputIndex = 0, + outputIndex = 0, + particleIndex = 0; + + for (let i = 0; i < this._physicsRig.settings.getSize(); ++i) { + this._physicsRig.settings.at( + i + ).normalizationPosition.minimum = json.getNormalizationPositionMinimumValue( + i + ); + this._physicsRig.settings.at( + i + ).normalizationPosition.maximum = json.getNormalizationPositionMaximumValue( + i + ); + this._physicsRig.settings.at( + i + ).normalizationPosition.defalut = json.getNormalizationPositionDefaultValue( + i + ); + + this._physicsRig.settings.at( + i + ).normalizationAngle.minimum = json.getNormalizationAngleMinimumValue(i); + this._physicsRig.settings.at( + i + ).normalizationAngle.maximum = json.getNormalizationAngleMaximumValue(i); + this._physicsRig.settings.at( + i + ).normalizationAngle.defalut = json.getNormalizationAngleDefaultValue(i); + + // Input + this._physicsRig.settings.at(i).inputCount = json.getInputCount(i); + this._physicsRig.settings.at(i).baseInputIndex = inputIndex; + + for (let j = 0; j < this._physicsRig.settings.at(i).inputCount; ++j) { + this._physicsRig.inputs.at(inputIndex + j).sourceParameterIndex = -1; + this._physicsRig.inputs.at(inputIndex + j).weight = json.getInputWeight( + i, + j + ); + this._physicsRig.inputs.at( + inputIndex + j + ).reflect = json.getInputReflect(i, j); + + if (json.getInputType(i, j) == PhysicsTypeTagX) { + this._physicsRig.inputs.at(inputIndex + j).type = + CubismPhysicsSource.CubismPhysicsSource_X; + this._physicsRig.inputs.at( + inputIndex + j + ).getNormalizedParameterValue = getInputTranslationXFromNormalizedParameterValue; + } else if (json.getInputType(i, j) == PhysicsTypeTagY) { + this._physicsRig.inputs.at(inputIndex + j).type = + CubismPhysicsSource.CubismPhysicsSource_Y; + this._physicsRig.inputs.at( + inputIndex + j + ).getNormalizedParameterValue = getInputTranslationYFromNormalizedParamterValue; + } else if (json.getInputType(i, j) == PhysicsTypeTagAngle) { + this._physicsRig.inputs.at(inputIndex + j).type = + CubismPhysicsSource.CubismPhysicsSource_Angle; + this._physicsRig.inputs.at( + inputIndex + j + ).getNormalizedParameterValue = getInputAngleFromNormalizedParameterValue; + } + + this._physicsRig.inputs.at(inputIndex + j).source.targetType = + CubismPhysicsTargetType.CubismPhysicsTargetType_Parameter; + this._physicsRig.inputs.at( + inputIndex + j + ).source.id = json.getInputSourceId(i, j); + } + inputIndex += this._physicsRig.settings.at(i).inputCount; + + // Output + this._physicsRig.settings.at(i).outputCount = json.getOutputCount(i); + this._physicsRig.settings.at(i).baseOutputIndex = outputIndex; + + for (let j = 0; j < this._physicsRig.settings.at(i).outputCount; ++j) { + this._physicsRig.outputs.at( + outputIndex + j + ).destinationParameterIndex = -1; + this._physicsRig.outputs.at( + outputIndex + j + ).vertexIndex = json.getOutputVertexIndex(i, j); + this._physicsRig.outputs.at( + outputIndex + j + ).angleScale = json.getOutputAngleScale(i, j); + this._physicsRig.outputs.at( + outputIndex + j + ).weight = json.getOutputWeight(i, j); + this._physicsRig.outputs.at(outputIndex + j).destination.targetType = + CubismPhysicsTargetType.CubismPhysicsTargetType_Parameter; + + this._physicsRig.outputs.at( + outputIndex + j + ).destination.id = json.getOutputDestinationId(i, j); + + if (json.getOutputType(i, j) == PhysicsTypeTagX) { + this._physicsRig.outputs.at(outputIndex + j).type = + CubismPhysicsSource.CubismPhysicsSource_X; + this._physicsRig.outputs.at( + outputIndex + j + ).getValue = getOutputTranslationX; + this._physicsRig.outputs.at( + outputIndex + j + ).getScale = getOutputScaleTranslationX; + } else if (json.getOutputType(i, j) == PhysicsTypeTagY) { + this._physicsRig.outputs.at(outputIndex + j).type = + CubismPhysicsSource.CubismPhysicsSource_Y; + this._physicsRig.outputs.at( + outputIndex + j + ).getValue = getOutputTranslationY; + this._physicsRig.outputs.at( + outputIndex + j + ).getScale = getOutputScaleTranslationY; + } else if (json.getOutputType(i, j) == PhysicsTypeTagAngle) { + this._physicsRig.outputs.at(outputIndex + j).type = + CubismPhysicsSource.CubismPhysicsSource_Angle; + this._physicsRig.outputs.at( + outputIndex + j + ).getValue = getOutputAngle; + this._physicsRig.outputs.at( + outputIndex + j + ).getScale = getOutputScaleAngle; + } + + this._physicsRig.outputs.at( + outputIndex + j + ).reflect = json.getOutputReflect(i, j); + } + outputIndex += this._physicsRig.settings.at(i).outputCount; + + // Particle + this._physicsRig.settings.at(i).particleCount = json.getParticleCount(i); + this._physicsRig.settings.at(i).baseParticleIndex = particleIndex; + + for (let j = 0; j < this._physicsRig.settings.at(i).particleCount; ++j) { + this._physicsRig.particles.at( + particleIndex + j + ).mobility = json.getParticleMobility(i, j); + this._physicsRig.particles.at( + particleIndex + j + ).delay = json.getParticleDelay(i, j); + this._physicsRig.particles.at( + particleIndex + j + ).acceleration = json.getParticleAcceleration(i, j); + this._physicsRig.particles.at( + particleIndex + j + ).radius = json.getParticleRadius(i, j); + this._physicsRig.particles.at( + particleIndex + j + ).position = json.getParticlePosition(i, j); + } + + particleIndex += this._physicsRig.settings.at(i).particleCount; + } + + this.initialize(); + + json.release(); + json = void 0; + json = null; + } + + /** + * 初期化する + */ + public initialize(): void { + let strand: CubismPhysicsParticle[]; + let currentSetting: CubismPhysicsSubRig; + let radius: CubismVector2; + + for ( + let settingIndex = 0; + settingIndex < this._physicsRig.subRigCount; + ++settingIndex + ) { + currentSetting = this._physicsRig.settings.at(settingIndex); + strand = this._physicsRig.particles.get(currentSetting.baseParticleIndex); + + // Initialize the top of particle. + strand[0].initialPosition = new CubismVector2(0.0, 0.0); + strand[0].lastPosition = new CubismVector2( + strand[0].initialPosition.x, + strand[0].initialPosition.y + ); + strand[0].lastGravity = new CubismVector2(0.0, -1.0); + strand[0].lastGravity.y *= -1.0; + strand[0].velocity = new CubismVector2(0.0, 0.0); + strand[0].force = new CubismVector2(0.0, 0.0); + + // Initialize paritcles. + for (let i = 1; i < currentSetting.particleCount; ++i) { + radius = new CubismVector2(0.0, 0.0); + radius.y = strand[i].radius; + strand[i].initialPosition = new CubismVector2( + strand[i - 1].initialPosition.x + radius.x, + strand[i - 1].initialPosition.y + radius.y + ); + strand[i].position = new CubismVector2( + strand[i].initialPosition.x, + strand[i].initialPosition.y + ); + strand[i].lastPosition = new CubismVector2( + strand[i].initialPosition.x, + strand[i].initialPosition.y + ); + strand[i].lastGravity = new CubismVector2(0.0, -1.0); + strand[i].lastGravity.y *= -1.0; + strand[i].velocity = new CubismVector2(0.0, 0.0); + strand[i].force = new CubismVector2(0.0, 0.0); + } + } + } + + _physicsRig: CubismPhysicsRig; // 物理演算のデータ + _options: Options; // オプション +} + +/** + * 物理演算のオプション + */ +export class Options { + constructor() { + this.gravity = new CubismVector2(0, 0); + this.wind = new CubismVector2(0, 0); + } + + gravity: CubismVector2; // 重力方向 + wind: CubismVector2; // 風の方向 +} + +/** + * Gets sign. + * + * @param value Evaluation target value. + * + * @return Sign of value. + */ +function sign(value: number): number { + let ret = 0; + + if (value > 0.0) { + ret = 1; + } else if (value < 0.0) { + ret = -1; + } + + return ret; +} + +function getInputTranslationXFromNormalizedParameterValue( + targetTranslation: CubismVector2, + targetAngle: { angle: number }, + value: number, + parameterMinimumValue: number, + parameterMaximumValue: number, + parameterDefaultValue: number, + normalizationPosition: CubismPhysicsNormalization, + normalizationAngle: CubismPhysicsNormalization, + isInverted: boolean, + weight: number +): void { + targetTranslation.x += + normalizeParameterValue( + value, + parameterMinimumValue, + parameterMaximumValue, + parameterDefaultValue, + normalizationPosition.minimum, + normalizationPosition.maximum, + normalizationPosition.defalut, + isInverted + ) * weight; +} + +function getInputTranslationYFromNormalizedParamterValue( + targetTranslation: CubismVector2, + targetAngle: { angle: number }, + value: number, + parameterMinimumValue: number, + parameterMaximumValue: number, + parameterDefaultValue: number, + normalizationPosition: CubismPhysicsNormalization, + normalizationAngle: CubismPhysicsNormalization, + isInverted: boolean, + weight: number +): void { + targetTranslation.y += + normalizeParameterValue( + value, + parameterMinimumValue, + parameterMaximumValue, + parameterDefaultValue, + normalizationPosition.minimum, + normalizationPosition.maximum, + normalizationPosition.defalut, + isInverted + ) * weight; +} + +function getInputAngleFromNormalizedParameterValue( + targetTranslation: CubismVector2, + targetAngle: { angle: number }, + value: number, + parameterMinimumValue: number, + parameterMaximumValue: number, + parameterDefaultValue: number, + normalizaitionPosition: CubismPhysicsNormalization, + normalizationAngle: CubismPhysicsNormalization, + isInverted: boolean, + weight: number +): void { + targetAngle.angle += + normalizeParameterValue( + value, + parameterMinimumValue, + parameterMaximumValue, + parameterDefaultValue, + normalizationAngle.minimum, + normalizationAngle.maximum, + normalizationAngle.defalut, + isInverted + ) * weight; +} + +function getOutputTranslationX( + translation: CubismVector2, + particles: CubismPhysicsParticle[], + particleIndex: number, + isInverted: boolean, + parentGravity: CubismVector2 +): number { + let outputValue: number = translation.x; + + if (isInverted) { + outputValue *= -1.0; + } + + return outputValue; +} + +function getOutputTranslationY( + translation: CubismVector2, + particles: CubismPhysicsParticle[], + particleIndex: number, + isInverted: boolean, + parentGravity: CubismVector2 +): number { + let outputValue: number = translation.y; + + if (isInverted) { + outputValue *= -1.0; + } + return outputValue; +} + +function getOutputAngle( + translation: CubismVector2, + particles: CubismPhysicsParticle[], + particleIndex: number, + isInverted: boolean, + parentGravity: CubismVector2 +): number { + let outputValue: number; + + if (particleIndex >= 2) { + parentGravity = particles[particleIndex - 1].position.substract( + particles[particleIndex - 2].position + ); + } else { + parentGravity = parentGravity.multiplyByScaler(-1.0); + } + + outputValue = CubismMath.directionToRadian(parentGravity, translation); + + if (isInverted) { + outputValue *= -1.0; + } + + return outputValue; +} + +function getRangeValue(min: number, max: number): number { + const maxValue: number = CubismMath.max(min, max); + const minValue: number = CubismMath.min(min, max); + + return CubismMath.abs(maxValue - minValue); +} + +function getDefaultValue(min: number, max: number): number { + const minValue: number = CubismMath.min(min, max); + return minValue + getRangeValue(min, max) / 2.0; +} + +function getOutputScaleTranslationX( + translationScale: CubismVector2, + angleScale: number +): number { + return JSON.parse(JSON.stringify(translationScale.x)); +} + +function getOutputScaleTranslationY( + translationScale: CubismVector2, + angleScale: number +): number { + return JSON.parse(JSON.stringify(translationScale.y)); +} + +function getOutputScaleAngle( + translationScale: CubismVector2, + angleScale: number +): number { + return JSON.parse(JSON.stringify(angleScale)); +} + +/** + * Updates particles. + * + * @param strand Target array of particle. + * @param strandCount Count of particle. + * @param totalTranslation Total translation value. + * @param totalAngle Total angle. + * @param windDirection Direction of Wind. + * @param thresholdValue Threshold of movement. + * @param deltaTimeSeconds Delta time. + * @param airResistance Air resistance. + */ +function updateParticles( + strand: CubismPhysicsParticle[], + strandCount: number, + totalTranslation: CubismVector2, + totalAngle: number, + windDirection: CubismVector2, + thresholdValue: number, + deltaTimeSeconds: number, + airResistance: number +) { + let totalRadian: number; + let delay: number; + let radian: number; + let currentGravity: CubismVector2; + let direction: CubismVector2 = new CubismVector2(0.0, 0.0); + let velocity: CubismVector2 = new CubismVector2(0.0, 0.0); + let force: CubismVector2 = new CubismVector2(0.0, 0.0); + let newDirection: CubismVector2 = new CubismVector2(0.0, 0.0); + + strand[0].position = new CubismVector2( + totalTranslation.x, + totalTranslation.y + ); + + totalRadian = CubismMath.degreesToRadian(totalAngle); + currentGravity = CubismMath.radianToDirection(totalRadian); + currentGravity.normalize(); + + for (let i = 1; i < strandCount; ++i) { + strand[i].force = currentGravity + .multiplyByScaler(strand[i].acceleration) + .add(windDirection); + + strand[i].lastPosition = new CubismVector2( + strand[i].position.x, + strand[i].position.y + ); + + delay = strand[i].delay * deltaTimeSeconds * 30.0; + + direction = strand[i].position.substract(strand[i - 1].position); + + radian = + CubismMath.directionToRadian(strand[i].lastGravity, currentGravity) / + airResistance; + + direction.x = + CubismMath.cos(radian) * direction.x - + direction.y * CubismMath.sin(radian); + direction.y = + CubismMath.sin(radian) * direction.x + + direction.y * CubismMath.cos(radian); + + strand[i].position = strand[i - 1].position.add(direction); + + velocity = strand[i].velocity.multiplyByScaler(delay); + force = strand[i].force.multiplyByScaler(delay).multiplyByScaler(delay); + + strand[i].position = strand[i].position.add(velocity).add(force); + + newDirection = strand[i].position.substract(strand[i - 1].position); + newDirection.normalize(); + + strand[i].position = strand[i - 1].position.add( + newDirection.multiplyByScaler(strand[i].radius) + ); + + if (CubismMath.abs(strand[i].position.x) < thresholdValue) { + strand[i].position.x = 0.0; + } + + if (delay != 0.0) { + strand[i].velocity = strand[i].position.substract(strand[i].lastPosition); + strand[i].velocity = strand[i].velocity.divisionByScalar(delay); + strand[i].velocity = strand[i].velocity.multiplyByScaler( + strand[i].mobility + ); + } + + strand[i].force = new CubismVector2(0.0, 0.0); + strand[i].lastGravity = new CubismVector2( + currentGravity.x, + currentGravity.y + ); + } +} + +/** + * Updates output parameter value. + * @param parameterValue Target parameter value. + * @param parameterValueMinimum Minimum of parameter value. + * @param parameterValueMaximum Maximum of parameter value. + * @param translation Translation value. + */ +function updateOutputParameterValue( + parameterValue: Float32Array, + parameterValueMinimum: number, + parameterValueMaximum: number, + translation: number, + output: CubismPhysicsOutput +): void { + let outputScale: number; + let value: number; + let weight: number; + + outputScale = output.getScale(output.translationScale, output.angleScale); + + value = translation * outputScale; + + if (value < parameterValueMinimum) { + if (value < output.valueBelowMinimum) { + output.valueBelowMinimum = value; + } + + value = parameterValueMinimum; + } else if (value > parameterValueMaximum) { + if (value > output.valueExceededMaximum) { + output.valueExceededMaximum = value; + } + + value = parameterValueMaximum; + } + + weight = output.weight / MaximumWeight; + + if (weight >= 1.0) { + parameterValue[0] = value; + } else { + value = parameterValue[0] * (1.0 - weight) + value * weight; + parameterValue[0] = value; + } +} + +function normalizeParameterValue( + value: number, + parameterMinimum: number, + parameterMaximum: number, + parameterDefault: number, + normalizedMinimum: number, + normalizedMaximum: number, + normalizedDefault: number, + isInverted: boolean +) { + let result = 0.0; + + const maxValue: number = CubismMath.max(parameterMaximum, parameterMinimum); + + if (maxValue < value) { + value = maxValue; + } + + const minValue: number = CubismMath.min(parameterMaximum, parameterMinimum); + + if (minValue > value) { + value = minValue; + } + + const minNormValue: number = CubismMath.min( + normalizedMinimum, + normalizedMaximum + ); + const maxNormValue: number = CubismMath.max( + normalizedMinimum, + normalizedMaximum + ); + const middleNormValue: number = normalizedDefault; + + const middleValue: number = getDefaultValue(minValue, maxValue); + const paramValue: number = value - middleValue; + + switch (sign(paramValue)) { + case 1: { + const nLength: number = maxNormValue - middleNormValue; + const pLength: number = maxValue - middleValue; + + if (pLength != 0.0) { + result = paramValue * (nLength / pLength); + result += middleNormValue; + } + + break; + } + case -1: { + const nLength: number = minNormValue - middleNormValue; + const pLength: number = minValue - middleValue; + + if (pLength != 0.0) { + result = paramValue * (nLength / pLength); + result += middleNormValue; + } + + break; + } + case 0: { + result = middleNormValue; + + break; + } + default: { + break; + } + } + + return isInverted ? result : result * -1.0; +} + +// Namespace definition for compatibility. +import * as $ from './cubismphysics'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismPhysics = $.CubismPhysics; + export type CubismPhysics = $.CubismPhysics; + export const Options = $.Options; + export type Options = $.Options; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/physics/cubismphysicsinternal.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/physics/cubismphysicsinternal.ts new file mode 100644 index 000000000..8c7372246 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/physics/cubismphysicsinternal.ts @@ -0,0 +1,249 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismVector2 } from '../math/cubismvector2'; +import { csmVector } from '../type/csmvector'; + +/** + * 物理演算の適用先の種類 + */ +export enum CubismPhysicsTargetType { + CubismPhysicsTargetType_Parameter // パラメータに対して適用 +} + +/** + * 物理演算の入力の種類 + */ +export enum CubismPhysicsSource { + CubismPhysicsSource_X, // X軸の位置から + CubismPhysicsSource_Y, // Y軸の位置から + CubismPhysicsSource_Angle // 角度から +} + +/** + * @brief 物理演算で使用する外部の力 + * + * 物理演算で使用する外部の力。 + */ +export class PhysicsJsonEffectiveForces { + constructor() { + this.gravity = new CubismVector2(0, 0); + this.wind = new CubismVector2(0, 0); + } + gravity: CubismVector2; // 重力 + wind: CubismVector2; // 風 +} + +/** + * 物理演算のパラメータ情報 + */ +export class CubismPhysicsParameter { + id: CubismIdHandle; // パラメータ + targetType: CubismPhysicsTargetType; // 適用先の種類 +} + +/** + * 物理演算の正規化情報 + */ +export class CubismPhysicsNormalization { + minimum: number; // 最大値 + maximum: number; // 最小値 + defalut: number; // デフォルト値 +} + +/** + * 物理演算の演算委使用する物理点の情報 + */ +export class CubismPhysicsParticle { + constructor() { + this.initialPosition = new CubismVector2(0, 0); + this.position = new CubismVector2(0, 0); + this.lastPosition = new CubismVector2(0, 0); + this.lastGravity = new CubismVector2(0, 0); + this.force = new CubismVector2(0, 0); + this.velocity = new CubismVector2(0, 0); + } + + initialPosition: CubismVector2; // 初期位置 + mobility: number; // 動きやすさ + delay: number; // 遅れ + acceleration: number; // 加速度 + radius: number; // 距離 + position: CubismVector2; // 現在の位置 + lastPosition: CubismVector2; // 最後の位置 + lastGravity: CubismVector2; // 最後の重力 + force: CubismVector2; // 現在かかっている力 + velocity: CubismVector2; // 現在の速度 +} + +/** + * 物理演算の物理点の管理 + */ +export class CubismPhysicsSubRig { + constructor() { + this.normalizationPosition = new CubismPhysicsNormalization(); + this.normalizationAngle = new CubismPhysicsNormalization(); + } + inputCount: number; // 入力の個数 + outputCount: number; // 出力の個数 + particleCount: number; // 物理点の個数 + baseInputIndex: number; // 入力の最初のインデックス + baseOutputIndex: number; // 出力の最初のインデックス + baseParticleIndex: number; // 物理点の最初のインデックス + normalizationPosition: CubismPhysicsNormalization; // 正規化された位置 + normalizationAngle: CubismPhysicsNormalization; // 正規化された角度 +} + +/** + * 正規化されたパラメータの取得関数の宣言 + * @param targetTranslation // 演算結果の移動値 + * @param targetAngle // 演算結果の角度 + * @param value // パラメータの値 + * @param parameterMinimunValue // パラメータの最小値 + * @param parameterMaximumValue // パラメータの最大値 + * @param parameterDefaultValue // パラメータのデフォルト値 + * @param normalizationPosition // 正規化された位置 + * @param normalizationAngle // 正規化された角度 + * @param isInverted // 値が反転されているか? + * @param weight // 重み + */ +export interface normalizedPhysicsParameterValueGetter { + ( + targetTranslation: CubismVector2, + targetAngle: { angle: number }, + value: number, + parameterMinimunValue: number, + parameterMaximumValue: number, + parameterDefaultValue: number, + normalizationPosition: CubismPhysicsNormalization, + normalizationAngle: CubismPhysicsNormalization, + isInverted: boolean, + weight: number + ): void; +} + +/** + * 物理演算の値の取得関数の宣言 + * @param translation 移動値 + * @param particles 物理点のリスト + * @param isInverted 値が反映されているか + * @param parentGravity 重力 + * @return 値 + */ +export interface physicsValueGetter { + ( + translation: CubismVector2, + particles: CubismPhysicsParticle[], + particleIndex: number, + isInverted: boolean, + parentGravity: CubismVector2 + ): number; +} + +/** + * 物理演算のスケールの取得関数の宣言 + * @param translationScale 移動値のスケール + * @param angleScale 角度のスケール + * @return スケール値 + */ +export interface physicsScaleGetter { + (translationScale: CubismVector2, angleScale: number): number; +} + +/** + * 物理演算の入力情報 + */ +export class CubismPhysicsInput { + constructor() { + this.source = new CubismPhysicsParameter(); + } + source: CubismPhysicsParameter; // 入力元のパラメータ + sourceParameterIndex: number; // 入力元のパラメータのインデックス + weight: number; // 重み + type: number; // 入力の種類 + reflect: boolean; // 値が反転されているかどうか + getNormalizedParameterValue: normalizedPhysicsParameterValueGetter; // 正規化されたパラメータ値の取得関数 +} + +/** + * @brief 物理演算の出力情報 + * + * 物理演算の出力情報。 + */ +export class CubismPhysicsOutput { + constructor() { + this.destination = new CubismPhysicsParameter(); + this.translationScale = new CubismVector2(0, 0); + } + + destination: CubismPhysicsParameter; // 出力先のパラメータ + destinationParameterIndex: number; // 出力先のパラメータのインデックス + vertexIndex: number; // 振り子のインデックス + translationScale: CubismVector2; // 移動値のスケール + angleScale: number; // 角度のスケール + weight: number; // 重み + type: CubismPhysicsSource; // 出力の種類 + reflect: boolean; // 値が反転されているかどうか + valueBelowMinimum: number; // 最小値を下回った時の値 + valueExceededMaximum: number; // 最大値をこえた時の値 + getValue: physicsValueGetter; // 物理演算の値の取得関数 + getScale: physicsScaleGetter; // 物理演算のスケール値の取得関数 +} + +/** + * @brief 物理演算のデータ + * + * 物理演算のデータ。 + */ +export class CubismPhysicsRig { + constructor() { + this.settings = new csmVector(); + this.inputs = new csmVector(); + this.outputs = new csmVector(); + this.particles = new csmVector(); + this.gravity = new CubismVector2(0, 0); + this.wind = new CubismVector2(0, 0); + } + + subRigCount: number; // 物理演算の物理点の個数 + settings: csmVector; // 物理演算の物理点の管理のリスト + inputs: csmVector; // 物理演算の入力のリスト + outputs: csmVector; // 物理演算の出力のリスト + particles: csmVector; // 物理演算の物理点のリスト + gravity: CubismVector2; // 重力 + wind: CubismVector2; // 風 +} + +// Namespace definition for compatibility. +import * as $ from './cubismphysicsinternal'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismPhysicsInput = $.CubismPhysicsInput; + export type CubismPhysicsInput = $.CubismPhysicsInput; + export const CubismPhysicsNormalization = $.CubismPhysicsNormalization; + export type CubismPhysicsNormalization = $.CubismPhysicsNormalization; + export const CubismPhysicsOutput = $.CubismPhysicsOutput; + export type CubismPhysicsOutput = $.CubismPhysicsOutput; + export const CubismPhysicsParameter = $.CubismPhysicsParameter; + export type CubismPhysicsParameter = $.CubismPhysicsParameter; + export const CubismPhysicsParticle = $.CubismPhysicsParticle; + export type CubismPhysicsParticle = $.CubismPhysicsParticle; + export const CubismPhysicsRig = $.CubismPhysicsRig; + export type CubismPhysicsRig = $.CubismPhysicsRig; + export const CubismPhysicsSource = $.CubismPhysicsSource; + export type CubismPhysicsSource = $.CubismPhysicsSource; + export const CubismPhysicsSubRig = $.CubismPhysicsSubRig; + export type CubismPhysicsSubRig = $.CubismPhysicsSubRig; + export const CubismPhysicsTargetType = $.CubismPhysicsTargetType; + export type CubismPhysicsTargetType = $.CubismPhysicsTargetType; + export const PhysicsJsonEffectiveForces = $.PhysicsJsonEffectiveForces; + export type PhysicsJsonEffectiveForces = $.PhysicsJsonEffectiveForces; + export type normalizedPhysicsParameterValueGetter = $.normalizedPhysicsParameterValueGetter; + export type physicsScaleGetter = $.physicsScaleGetter; + export type physicsValueGetter = $.physicsValueGetter; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/physics/cubismphysicsjson.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/physics/cubismphysicsjson.ts new file mode 100644 index 000000000..35c4529b4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/physics/cubismphysicsjson.ts @@ -0,0 +1,648 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismIdHandle } from '../id/cubismid'; +import { CubismFramework } from '../live2dcubismframework'; +import { CubismVector2 } from '../math/cubismvector2'; +import { CubismJson } from '../utils/cubismjson'; + +// JSON keys +const Position = 'Position'; +const X = 'X'; +const Y = 'Y'; +const Angle = 'Angle'; +const Type = 'Type'; +const Id = 'Id'; + +// Meta +const Meta = 'Meta'; +const EffectiveForces = 'EffectiveForces'; +const TotalInputCount = 'TotalInputCount'; +const TotalOutputCount = 'TotalOutputCount'; +const PhysicsSettingCount = 'PhysicsSettingCount'; +const Gravity = 'Gravity'; +const Wind = 'Wind'; +const VertexCount = 'VertexCount'; + +// PhysicsSettings +const PhysicsSettings = 'PhysicsSettings'; +const Normalization = 'Normalization'; +const Minimum = 'Minimum'; +const Maximum = 'Maximum'; +const Default = 'Default'; +const Reflect = 'Reflect'; +const Weight = 'Weight'; + +// Input +const Input = 'Input'; +const Source = 'Source'; + +// Output +const Output = 'Output'; +const Scale = 'Scale'; +const VertexIndex = 'VertexIndex'; +const Destination = 'Destination'; + +// Particle +const Vertices = 'Vertices'; +const Mobility = 'Mobility'; +const Delay = 'Delay'; +const Radius = 'Radius'; +const Acceleration = 'Acceleration'; + +/** + * physics3.jsonのコンテナ。 + */ +export class CubismPhysicsJson { + /** + * コンストラクタ + * @param buffer physics3.jsonが読み込まれているバッファ + * @param size バッファのサイズ + */ + public constructor(buffer: ArrayBuffer, size: number) { + this._json = CubismJson.create(buffer, size); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + CubismJson.delete(this._json); + } + + /** + * 重力の取得 + * @return 重力 + */ + public getGravity(): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0, 0); + ret.x = this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(EffectiveForces) + .getValueByString(Gravity) + .getValueByString(X) + .toFloat(); + ret.y = this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(EffectiveForces) + .getValueByString(Gravity) + .getValueByString(Y) + .toFloat(); + return ret; + } + + /** + * 風の取得 + * @return 風 + */ + public getWind(): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0, 0); + ret.x = this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(EffectiveForces) + .getValueByString(Wind) + .getValueByString(X) + .toFloat(); + ret.y = this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(EffectiveForces) + .getValueByString(Wind) + .getValueByString(Y) + .toFloat(); + return ret; + } + + /** + * 物理店の管理の個数の取得 + * @return 物理店の管理の個数 + */ + public getSubRigCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(PhysicsSettingCount) + .toInt(); + } + + /** + * 入力の総合計の取得 + * @return 入力の総合計 + */ + public getTotalInputCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalInputCount) + .toInt(); + } + + /** + * 出力の総合計の取得 + * @return 出力の総合計 + */ + public getTotalOutputCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(TotalOutputCount) + .toInt(); + } + + /** + * 物理点の個数の取得 + * @return 物理点の個数 + */ + public getVertexCount(): number { + return this._json + .getRoot() + .getValueByString(Meta) + .getValueByString(VertexCount) + .toInt(); + } + + /** + * 正規化された位置の最小値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された位置の最小値 + */ + public getNormalizationPositionMinimumValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Position) + .getValueByString(Minimum) + .toFloat(); + } + + /** + * 正規化された位置の最大値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された位置の最大値 + */ + public getNormalizationPositionMaximumValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Position) + .getValueByString(Maximum) + .toFloat(); + } + + /** + * 正規化された位置のデフォルト値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された位置のデフォルト値 + */ + public getNormalizationPositionDefaultValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Position) + .getValueByString(Default) + .toFloat(); + } + + /** + * 正規化された角度の最小値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された角度の最小値 + */ + public getNormalizationAngleMinimumValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Angle) + .getValueByString(Minimum) + .toFloat(); + } + + /** + * 正規化された角度の最大値の取得 + * @param physicsSettingIndex + * @return 正規化された角度の最大値 + */ + public getNormalizationAngleMaximumValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Angle) + .getValueByString(Maximum) + .toFloat(); + } + + /** + * 正規化された角度のデフォルト値の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 正規化された角度のデフォルト値 + */ + public getNormalizationAngleDefaultValue( + physicsSettingIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Normalization) + .getValueByString(Angle) + .getValueByString(Default) + .toFloat(); + } + + /** + * 入力の個数の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 入力の個数 + */ + public getInputCount(physicsSettingIndex: number): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getVector() + .getSize(); + } + + /** + * 入力の重みの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param inputIndex 入力のインデックス + * @return 入力の重み + */ + public getInputWeight( + physicsSettingIndex: number, + inputIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getValueByIndex(inputIndex) + .getValueByString(Weight) + .toFloat(); + } + + /** + * 入力の反転の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param inputIndex 入力のインデックス + * @return 入力の反転 + */ + public getInputReflect( + physicsSettingIndex: number, + inputIndex: number + ): boolean { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getValueByIndex(inputIndex) + .getValueByString(Reflect) + .toBoolean(); + } + + /** + * 入力の種類の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param inputIndex 入力のインデックス + * @return 入力の種類 + */ + public getInputType(physicsSettingIndex: number, inputIndex: number): string { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getValueByIndex(inputIndex) + .getValueByString(Type) + .getRawString(); + } + + /** + * 入力元のIDの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param inputIndex 入力のインデックス + * @return 入力元のID + */ + public getInputSourceId( + physicsSettingIndex: number, + inputIndex: number + ): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Input) + .getValueByIndex(inputIndex) + .getValueByString(Source) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * 出力の個数の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @return 出力の個数 + */ + public getOutputCount(physicsSettingIndex: number): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getVector() + .getSize(); + } + + /** + * 出力の物理点のインデックスの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の物理点のインデックス + */ + public getOutputVertexIndex( + physicsSettingIndex: number, + outputIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(VertexIndex) + .toInt(); + } + + /** + * 出力の角度のスケールを取得する + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の角度のスケール + */ + public getOutputAngleScale( + physicsSettingIndex: number, + outputIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Scale) + .toFloat(); + } + + /** + * 出力の重みの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の重み + */ + public getOutputWeight( + physicsSettingIndex: number, + outputIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Weight) + .toFloat(); + } + + /** + * 出力先のIDの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力先のID + */ + public getOutputDestinationId( + physicsSettingIndex: number, + outputIndex: number + ): CubismIdHandle { + return CubismFramework.getIdManager().getId( + this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Destination) + .getValueByString(Id) + .getRawString() + ); + } + + /** + * 出力の種類の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の種類 + */ + public getOutputType( + physicsSettingIndex: number, + outputIndex: number + ): string { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Type) + .getRawString(); + } + + /** + * 出力の反転の取得 + * @param physicsSettingIndex 物理演算のインデックス + * @param outputIndex 出力のインデックス + * @return 出力の反転 + */ + public getOutputReflect( + physicsSettingIndex: number, + outputIndex: number + ): boolean { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Output) + .getValueByIndex(outputIndex) + .getValueByString(Reflect) + .toBoolean(); + } + + /** + * 物理点の個数の取得 + * @param physicsSettingIndex 物理演算男設定のインデックス + * @return 物理点の個数 + */ + public getParticleCount(physicsSettingIndex: number): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getVector() + .getSize(); + } + + /** + * 物理点の動きやすさの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param vertexIndex 物理点のインデックス + * @return 物理点の動きやすさ + */ + public getParticleMobility( + physicsSettingIndex: number, + vertexIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Mobility) + .toFloat(); + } + + /** + * 物理点の遅れの取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param vertexIndex 物理点のインデックス + * @return 物理点の遅れ + */ + public getParticleDelay( + physicsSettingIndex: number, + vertexIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Delay) + .toFloat(); + } + + /** + * 物理点の加速度の取得 + * @param physicsSettingIndex 物理演算の設定 + * @param vertexIndex 物理点のインデックス + * @return 物理点の加速度 + */ + public getParticleAcceleration( + physicsSettingIndex: number, + vertexIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Acceleration) + .toFloat(); + } + + /** + * 物理点の距離の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param vertexIndex 物理点のインデックス + * @return 物理点の距離 + */ + public getParticleRadius( + physicsSettingIndex: number, + vertexIndex: number + ): number { + return this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Radius) + .toFloat(); + } + + /** + * 物理点の位置の取得 + * @param physicsSettingIndex 物理演算の設定のインデックス + * @param vertexInde 物理点のインデックス + * @return 物理点の位置 + */ + public getParticlePosition( + physicsSettingIndex: number, + vertexIndex: number + ): CubismVector2 { + const ret: CubismVector2 = new CubismVector2(0, 0); + ret.x = this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Position) + .getValueByString(X) + .toFloat(); + ret.y = this._json + .getRoot() + .getValueByString(PhysicsSettings) + .getValueByIndex(physicsSettingIndex) + .getValueByString(Vertices) + .getValueByIndex(vertexIndex) + .getValueByString(Position) + .getValueByString(Y) + .toFloat(); + return ret; + } + + _json: CubismJson; // physics3.jsonデータ +} + +// Namespace definition for compatibility. +import * as $ from './cubismphysicsjson'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismPhysicsJson = $.CubismPhysicsJson; + export type CubismPhysicsJson = $.CubismPhysicsJson; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/rendering/cubismrenderer.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/rendering/cubismrenderer.ts new file mode 100644 index 000000000..c69aaadec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/rendering/cubismrenderer.ts @@ -0,0 +1,275 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismMatrix44 } from '../math/cubismmatrix44'; +import { CubismModel } from '../model/cubismmodel'; + +/** + * モデル描画を処理するレンダラ + * + * サブクラスに環境依存の描画命令を記述する。 + */ +export abstract class CubismRenderer { + /** + * レンダラのインスタンスを生成して取得する + * + * @return レンダラのインスタンス + */ + public static create(): CubismRenderer { + return null; + } + + /** + * レンダラのインスタンスを解放する + */ + public static delete(renderer: CubismRenderer): void { + renderer = null; + } + + /** + * レンダラの初期化処理を実行する + * 引数に渡したモデルからレンダラの初期化処理に必要な情報を取り出すことができる + * @param model モデルのインスタンス + */ + public initialize(model: CubismModel): void { + this._model = model; + } + + /** + * モデルを描画する + */ + public drawModel(): void { + if (this.getModel() == null) return; + + this.doDrawModel(); + } + + /** + * Model-View-Projection 行列をセットする + * 配列は複製されるので、元の配列は外で破棄して良い + * @param matrix44 Model-View-Projection 行列 + */ + public setMvpMatrix(matrix44: CubismMatrix44): void { + this._mvpMatrix4x4.setMatrix(matrix44.getArray()); + } + + /** + * Model-View-Projection 行列を取得する + * @return Model-View-Projection 行列 + */ + public getMvpMatrix(): CubismMatrix44 { + return this._mvpMatrix4x4; + } + + /** + * モデルの色をセットする + * 各色0.0~1.0の間で指定する(1.0が標準の状態) + * @param red 赤チャンネルの値 + * @param green 緑チャンネルの値 + * @param blue 青チャンネルの値 + * @param alpha αチャンネルの値 + */ + public setModelColor( + red: number, + green: number, + blue: number, + alpha: number + ): void { + if (red < 0.0) { + red = 0.0; + } else if (red > 1.0) { + red = 1.0; + } + + if (green < 0.0) { + green = 0.0; + } else if (green > 1.0) { + green = 1.0; + } + + if (blue < 0.0) { + blue = 0.0; + } else if (blue > 1.0) { + blue = 1.0; + } + + if (alpha < 0.0) { + alpha = 0.0; + } else if (alpha > 1.0) { + alpha = 1.0; + } + + this._modelColor.R = red; + this._modelColor.G = green; + this._modelColor.B = blue; + this._modelColor.A = alpha; + } + + /** + * モデルの色を取得する + * 各色0.0~1.0の間で指定する(1.0が標準の状態) + * + * @return RGBAのカラー情報 + */ + public getModelColor(): CubismTextureColor { + return JSON.parse(JSON.stringify(this._modelColor)); + } + + /** + * 乗算済みαの有効・無効をセットする + * 有効にするならtrue、無効にするならfalseをセットする + */ + public setIsPremultipliedAlpha(enable: boolean): void { + this._isPremultipliedAlpha = enable; + } + + /** + * 乗算済みαの有効・無効を取得する + * @return true 乗算済みのα有効 + * @return false 乗算済みのα無効 + */ + public isPremultipliedAlpha(): boolean { + return this._isPremultipliedAlpha; + } + + /** + * カリング(片面描画)の有効・無効をセットする。 + * 有効にするならtrue、無効にするならfalseをセットする + */ + public setIsCulling(culling: boolean): void { + this._isCulling = culling; + } + + /** + * カリング(片面描画)の有効・無効を取得する。 + * @return true カリング有効 + * @return false カリング無効 + */ + public isCulling(): boolean { + return this._isCulling; + } + + /** + * テクスチャの異方性フィルタリングのパラメータをセットする + * パラメータ値の影響度はレンダラの実装に依存する + * @param n パラメータの値 + */ + public setAnisotropy(n: number): void { + this._anisortopy = n; + } + + /** + * テクスチャの異方性フィルタリングのパラメータをセットする + * @return 異方性フィルタリングのパラメータ + */ + public getAnisotropy(): number { + return this._anisortopy; + } + + /** + * レンダリングするモデルを取得する + * @return レンダリングするモデル + */ + public getModel(): CubismModel { + return this._model; + } + + /** + * コンストラクタ + */ + protected constructor() { + this._isCulling = false; + this._isPremultipliedAlpha = false; + this._anisortopy = 0.0; + this._model = null; + this._modelColor = new CubismTextureColor(); + + // 単位行列に初期化 + this._mvpMatrix4x4 = new CubismMatrix44(); + this._mvpMatrix4x4.loadIdentity(); + } + + /** + * モデル描画の実装 + */ + public abstract doDrawModel(): void; + + /** + * 描画オブジェクト(アートメッシュ)を描画する + * ポリゴンメッシュとテクスチャ番号をセットで渡す。 + * @param textureNo 描画するテクスチャ番号 + * @param indexCount 描画オブジェクトのインデックス値 + * @param vertexCount ポリゴンメッシュの頂点数 + * @param indexArray ポリゴンメッシュ頂点のインデックス配列 + * @param vertexArray ポリゴンメッシュの頂点配列 + * @param uvArray uv配列 + * @param opacity 不透明度 + * @param colorBlendMode カラーブレンディングのタイプ + * @param invertedMask マスク使用時のマスクの反転使用 + */ + public abstract drawMesh( + textureNo: number, + indexCount: number, + vertexCount: number, + indexArray: Uint16Array, + vertexArray: Float32Array, + uvArray: Float32Array, + opacity: number, + colorBlendMode: CubismBlendMode, + invertedMask: boolean + ): void; + + /** + * レンダラが保持する静的なリソースを開放する + */ + public static staticRelease: Function; + + protected _mvpMatrix4x4: CubismMatrix44; // Model-View-Projection 行列 + protected _modelColor: CubismTextureColor; // モデル自体のカラー(RGBA) + protected _isCulling: boolean; // カリングが有効ならtrue + protected _isPremultipliedAlpha: boolean; // 乗算済みαならtrue + protected _anisortopy: any; // テクスチャの異方性フィルタリングのパラメータ + protected _model: CubismModel; // レンダリング対象のモデル +} + +export enum CubismBlendMode { + CubismBlendMode_Normal = 0, // 通常 + CubismBlendMode_Additive = 1, // 加算 + CubismBlendMode_Multiplicative = 2 // 乗算 +} + +/** + * テクスチャの色をRGBAで扱うためのクラス + */ +export class CubismTextureColor { + /** + * コンストラクタ + */ + constructor() { + this.R = 1.0; + this.G = 1.0; + this.B = 1.0; + this.A = 1.0; + } + + R: number; // 赤チャンネル + G: number; // 緑チャンネル + B: number; // 青チャンネル + A: number; // αチャンネル +} + +// Namespace definition for compatibility. +import * as $ from './cubismrenderer'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismBlendMode = $.CubismBlendMode; + export type CubismBlendMode = $.CubismBlendMode; + export const CubismRenderer = $.CubismRenderer; + export type CubismRenderer = $.CubismRenderer; + export const CubismTextureColor = $.CubismTextureColor; + export type CubismTextureColor = $.CubismTextureColor; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/rendering/cubismrenderer_webgl.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/rendering/cubismrenderer_webgl.ts new file mode 100644 index 000000000..51290a19e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/rendering/cubismrenderer_webgl.ts @@ -0,0 +1,2222 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { Constant } from '../live2dcubismframework'; +import { CubismMatrix44 } from '../math/cubismmatrix44'; +import { CubismModel } from '../model/cubismmodel'; +import { csmMap } from '../type/csmmap'; +import { csmRect } from '../type/csmrectf'; +import { csmVector } from '../type/csmvector'; +import { CubismLogError } from '../utils/cubismdebug'; +import { + CubismBlendMode, + CubismRenderer, + CubismTextureColor +} from './cubismrenderer'; + +const ColorChannelCount = 4; // 実験時に1チャンネルの場合は1、RGBだけの場合は3、アルファも含める場合は4 + +const shaderCount = 10; // シェーダーの数 = マスク生成用 + (通常用 + 加算 + 乗算) * (マスク無の乗算済アルファ対応版 + マスク有の乗算済アルファ対応版 + マスク有反転の乗算済アルファ対応版) +let s_instance: CubismShader_WebGL; +let s_viewport: number[]; +let s_fbo: WebGLFramebuffer; + +/** + * クリッピングマスクの処理を実行するクラス + */ +export class CubismClippingManager_WebGL { + /** + * カラーチャンネル(RGBA)のフラグを取得する + * @param channelNo カラーチャンネル(RGBA)の番号(0:R, 1:G, 2:B, 3:A) + */ + public getChannelFlagAsColor(channelNo: number): CubismTextureColor { + return this._channelColors.at(channelNo); + } + + /** + * テンポラリのレンダーテクスチャのアドレスを取得する + * FrameBufferObjectが存在しない場合、新しく生成する + * + * @return レンダーテクスチャのアドレス + */ + public getMaskRenderTexture(): WebGLFramebuffer { + let ret: WebGLFramebuffer = 0; + + // テンポラリのRenderTextureを取得する + if (this._maskTexture && this._maskTexture.texture != 0) { + // 前回使ったものを返す + this._maskTexture.frameNo = this._currentFrameNo; + ret = this._maskTexture.texture; + } + + if (ret == 0) { + // FrameBufferObjectが存在しない場合、新しく生成する + + // クリッピングバッファサイズを取得 + const size: number = this._clippingMaskBufferSize; + + this._colorBuffer = this.gl.createTexture(); + this.gl.bindTexture(this.gl.TEXTURE_2D, this._colorBuffer); + this.gl.texImage2D( + this.gl.TEXTURE_2D, + 0, + this.gl.RGBA, + size, + size, + 0, + this.gl.RGBA, + this.gl.UNSIGNED_BYTE, + null + ); + this.gl.texParameteri( + this.gl.TEXTURE_2D, + this.gl.TEXTURE_WRAP_S, + this.gl.CLAMP_TO_EDGE + ); + this.gl.texParameteri( + this.gl.TEXTURE_2D, + this.gl.TEXTURE_WRAP_T, + this.gl.CLAMP_TO_EDGE + ); + this.gl.texParameteri( + this.gl.TEXTURE_2D, + this.gl.TEXTURE_MIN_FILTER, + this.gl.LINEAR + ); + this.gl.texParameteri( + this.gl.TEXTURE_2D, + this.gl.TEXTURE_MAG_FILTER, + this.gl.LINEAR + ); + this.gl.bindTexture(this.gl.TEXTURE_2D, null); + + ret = this.gl.createFramebuffer(); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, ret); + this.gl.framebufferTexture2D( + this.gl.FRAMEBUFFER, + this.gl.COLOR_ATTACHMENT0, + this.gl.TEXTURE_2D, + this._colorBuffer, + 0 + ); + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, s_fbo); + + this._maskTexture = new CubismRenderTextureResource( + this._currentFrameNo, + ret + ); + } + + return ret; + } + + /** + * WebGLレンダリングコンテキストを設定する + * @param gl WebGLレンダリングコンテキスト + */ + public setGL(gl: WebGLRenderingContext): void { + this.gl = gl; + } + + /** + * マスクされる描画オブジェクト群全体を囲む矩形(モデル座標系)を計算する + * @param model モデルのインスタンス + * @param clippingContext クリッピングマスクのコンテキスト + */ + public calcClippedDrawTotalBounds( + model: CubismModel, + clippingContext: CubismClippingContext + ): void { + // 被クリッピングマスク(マスクされる描画オブジェクト)の全体の矩形 + let clippedDrawTotalMinX: number = Number.MAX_VALUE; + let clippedDrawTotalMinY: number = Number.MAX_VALUE; + let clippedDrawTotalMaxX: number = Number.MIN_VALUE; + let clippedDrawTotalMaxY: number = Number.MIN_VALUE; + + // このマスクが実際に必要か判定する + // このクリッピングを利用する「描画オブジェクト」がひとつでも使用可能であればマスクを生成する必要がある + const clippedDrawCount: number = + clippingContext._clippedDrawableIndexList.length; + + for ( + let clippedDrawableIndex = 0; + clippedDrawableIndex < clippedDrawCount; + clippedDrawableIndex++ + ) { + // マスクを使用する描画オブジェクトの描画される矩形を求める + const drawableIndex: number = + clippingContext._clippedDrawableIndexList[clippedDrawableIndex]; + + const drawableVertexCount: number = model.getDrawableVertexCount( + drawableIndex + ); + const drawableVertexes: Float32Array = model.getDrawableVertices( + drawableIndex + ); + + let minX: number = Number.MAX_VALUE; + let minY: number = Number.MAX_VALUE; + let maxX: number = Number.MIN_VALUE; + let maxY: number = Number.MIN_VALUE; + + const loop: number = drawableVertexCount * Constant.vertexStep; + for ( + let pi: number = Constant.vertexOffset; + pi < loop; + pi += Constant.vertexStep + ) { + const x: number = drawableVertexes[pi]; + const y: number = drawableVertexes[pi + 1]; + + if (x < minX) { + minX = x; + } + if (x > maxX) { + maxX = x; + } + if (y < minY) { + minY = y; + } + if (y > maxY) { + maxY = y; + } + } + + // 有効な点が一つも取れなかったのでスキップ + if (minX == Number.MAX_VALUE) { + continue; + } + + // 全体の矩形に反映 + if (minX < clippedDrawTotalMinX) { + clippedDrawTotalMinX = minX; + } + if (minY < clippedDrawTotalMinY) { + clippedDrawTotalMinY = minY; + } + if (maxX > clippedDrawTotalMaxX) { + clippedDrawTotalMaxX = maxX; + } + if (maxY > clippedDrawTotalMaxY) { + clippedDrawTotalMaxY = maxY; + } + + if (clippedDrawTotalMinX == Number.MAX_VALUE) { + clippingContext._allClippedDrawRect.x = 0.0; + clippingContext._allClippedDrawRect.y = 0.0; + clippingContext._allClippedDrawRect.width = 0.0; + clippingContext._allClippedDrawRect.height = 0.0; + clippingContext._isUsing = false; + } else { + clippingContext._isUsing = true; + const w: number = clippedDrawTotalMaxX - clippedDrawTotalMinX; + const h: number = clippedDrawTotalMaxY - clippedDrawTotalMinY; + clippingContext._allClippedDrawRect.x = clippedDrawTotalMinX; + clippingContext._allClippedDrawRect.y = clippedDrawTotalMinY; + clippingContext._allClippedDrawRect.width = w; + clippingContext._allClippedDrawRect.height = h; + } + } + } + + /** + * コンストラクタ + */ + public constructor() { + this._maskRenderTexture = null; + this._colorBuffer = null; + this._currentFrameNo = 0; + this._clippingMaskBufferSize = 256; + this._clippingContextListForMask = new csmVector(); + this._clippingContextListForDraw = new csmVector(); + this._channelColors = new csmVector(); + this._tmpBoundsOnModel = new csmRect(); + this._tmpMatrix = new CubismMatrix44(); + this._tmpMatrixForMask = new CubismMatrix44(); + this._tmpMatrixForDraw = new CubismMatrix44(); + this._maskTexture = null; + + let tmp: CubismTextureColor = new CubismTextureColor(); + tmp.R = 1.0; + tmp.G = 0.0; + tmp.B = 0.0; + tmp.A = 0.0; + this._channelColors.pushBack(tmp); + + tmp = new CubismTextureColor(); + tmp.R = 0.0; + tmp.G = 1.0; + tmp.B = 0.0; + tmp.A = 0.0; + this._channelColors.pushBack(tmp); + + tmp = new CubismTextureColor(); + tmp.R = 0.0; + tmp.G = 0.0; + tmp.B = 1.0; + tmp.A = 0.0; + this._channelColors.pushBack(tmp); + + tmp = new CubismTextureColor(); + tmp.R = 0.0; + tmp.G = 0.0; + tmp.B = 0.0; + tmp.A = 1.0; + this._channelColors.pushBack(tmp); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + for (let i = 0; i < this._clippingContextListForMask.getSize(); i++) { + if (this._clippingContextListForMask.at(i)) { + this._clippingContextListForMask.at(i).release(); + this._clippingContextListForMask.set(i, void 0); + } + this._clippingContextListForMask.set(i, null); + } + this._clippingContextListForMask = null; + + // _clippingContextListForDrawは_clippingContextListForMaskにあるインスタンスを指している。上記の処理により要素ごとのDELETEは不要。 + for (let i = 0; i < this._clippingContextListForDraw.getSize(); i++) { + this._clippingContextListForDraw.set(i, null); + } + this._clippingContextListForDraw = null; + + if (this._maskTexture) { + this.gl.deleteFramebuffer(this._maskTexture.texture); + this._maskTexture = null; + } + + for (let i = 0; i < this._channelColors.getSize(); i++) { + this._channelColors.set(i, null); + } + + this._channelColors = null; + + // テクスチャ解放 + this.gl.deleteTexture(this._colorBuffer); + this._colorBuffer = null; + } + + /** + * マネージャの初期化処理 + * クリッピングマスクを使う描画オブジェクトの登録を行う + * @param model モデルのインスタンス + * @param drawableCount 描画オブジェクトの数 + * @param drawableMasks 描画オブジェクトをマスクする描画オブジェクトのインデックスのリスト + * @param drawableCounts 描画オブジェクトをマスクする描画オブジェクトの数 + */ + public initialize( + model: CubismModel, + drawableCount: number, + drawableMasks: Int32Array[], + drawableMaskCounts: Int32Array + ): void { + // クリッピングマスクを使う描画オブジェクトをすべて登録する + // クリッピングマスクは、通常数個程度に限定して使うものとする + for (let i = 0; i < drawableCount; i++) { + if (drawableMaskCounts[i] <= 0) { + // クリッピングマスクが使用されていないアートメッシュ(多くの場合使用しない) + this._clippingContextListForDraw.pushBack(null); + continue; + } + + // 既にあるClipContextと同じかチェックする + let clippingContext: CubismClippingContext = this.findSameClip( + drawableMasks[i], + drawableMaskCounts[i] + ); + if (clippingContext == null) { + // 同一のマスクが存在していない場合は生成する + clippingContext = new CubismClippingContext( + this, + drawableMasks[i], + drawableMaskCounts[i] + ); + this._clippingContextListForMask.pushBack(clippingContext); + } + + clippingContext.addClippedDrawable(i); + + this._clippingContextListForDraw.pushBack(clippingContext); + } + } + + /** + * クリッピングコンテキストを作成する。モデル描画時に実行する。 + * @param model モデルのインスタンス + * @param renderer レンダラのインスタンス + */ + public setupClippingContext( + model: CubismModel, + renderer: CubismRenderer_WebGL + ): void { + this._currentFrameNo++; + + // 全てのクリッピングを用意する + // 同じクリップ(複数の場合はまとめて一つのクリップ)を使う場合は1度だけ設定する + let usingClipCount = 0; + for ( + let clipIndex = 0; + clipIndex < this._clippingContextListForMask.getSize(); + clipIndex++ + ) { + // 1つのクリッピングマスクに関して + const cc: CubismClippingContext = this._clippingContextListForMask.at( + clipIndex + ); + + // このクリップを利用する描画オブジェクト群全体を囲む矩形を計算 + this.calcClippedDrawTotalBounds(model, cc); + + if (cc._isUsing) { + usingClipCount++; // 使用中としてカウント + } + } + + // マスク作成処理 + if (usingClipCount > 0) { + // 生成したFrameBufferと同じサイズでビューポートを設定 + this.gl.viewport( + 0, + 0, + this._clippingMaskBufferSize, + this._clippingMaskBufferSize + ); + + // マスクをactiveにする + this._maskRenderTexture = this.getMaskRenderTexture(); + + // モデル描画時にDrawMeshNowに渡される変換(モデルtoワールド座標変換) + const modelToWorldF: CubismMatrix44 = renderer.getMvpMatrix(); + + renderer.preDraw(); // バッファをクリアする + + // 各マスクのレイアウトを決定していく + this.setupLayoutBounds(usingClipCount); + + // ---------- マスク描画処理 ---------- + // マスク用RenderTextureをactiveにセット + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this._maskRenderTexture); + + // マスクをクリアする + // (仮仕様) 1が無効(描かれない)領域、0が有効(描かれる)領域。(シェーダーCd*Csで0に近い値をかけてマスクを作る。1をかけると何も起こらない) + this.gl.clearColor(1.0, 1.0, 1.0, 1.0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + + // 実際にマスクを生成する + // 全てのマスクをどのようにレイアウトして描くかを決定し、ClipContext, ClippedDrawContextに記憶する + for ( + let clipIndex = 0; + clipIndex < this._clippingContextListForMask.getSize(); + clipIndex++ + ) { + // --- 実際に1つのマスクを描く --- + const clipContext: CubismClippingContext = this._clippingContextListForMask.at( + clipIndex + ); + const allClipedDrawRect: csmRect = clipContext._allClippedDrawRect; // このマスクを使う、すべての描画オブジェクトの論理座標上の囲み矩形 + const layoutBoundsOnTex01: csmRect = clipContext._layoutBounds; // この中にマスクを収める + + // モデル座標上の矩形を、適宜マージンを付けて使う + const MARGIN = 0.05; + this._tmpBoundsOnModel.setRect(allClipedDrawRect); + this._tmpBoundsOnModel.expand( + allClipedDrawRect.width * MARGIN, + allClipedDrawRect.height * MARGIN + ); + //########## 本来は割り当てられた領域の全体を使わず必要最低限のサイズがよい + + // シェーダ用の計算式を求める。回転を考慮しない場合は以下のとおり + // movePeriod' = movePeriod * scaleX + offX [[ movePeriod' = (movePeriod - tmpBoundsOnModel.movePeriod)*scale + layoutBoundsOnTex01.movePeriod ]] + const scaleX: number = + layoutBoundsOnTex01.width / this._tmpBoundsOnModel.width; + const scaleY: number = + layoutBoundsOnTex01.height / this._tmpBoundsOnModel.height; + + // マスク生成時に使う行列を求める + { + // シェーダに渡す行列を求める <<<<<<<<<<<<<<<<<<<<<<<< 要最適化(逆順に計算すればシンプルにできる) + this._tmpMatrix.loadIdentity(); + { + // layout0..1 を -1..1に変換 + this._tmpMatrix.translateRelative(-1.0, -1.0); + this._tmpMatrix.scaleRelative(2.0, 2.0); + } + { + // view to layout0..1 + this._tmpMatrix.translateRelative( + layoutBoundsOnTex01.x, + layoutBoundsOnTex01.y + ); + this._tmpMatrix.scaleRelative(scaleX, scaleY); // new = [translate][scale] + this._tmpMatrix.translateRelative( + -this._tmpBoundsOnModel.x, + -this._tmpBoundsOnModel.y + ); + // new = [translate][scale][translate] + } + // tmpMatrixForMaskが計算結果 + this._tmpMatrixForMask.setMatrix(this._tmpMatrix.getArray()); + } + + //--------- draw時の mask 参照用行列を計算 + { + // シェーダに渡す行列を求める <<<<<<<<<<<<<<<<<<<<<<<< 要最適化(逆順に計算すればシンプルにできる) + this._tmpMatrix.loadIdentity(); + { + this._tmpMatrix.translateRelative( + layoutBoundsOnTex01.x, + layoutBoundsOnTex01.y + ); + this._tmpMatrix.scaleRelative(scaleX, scaleY); // new = [translate][scale] + this._tmpMatrix.translateRelative( + -this._tmpBoundsOnModel.x, + -this._tmpBoundsOnModel.y + ); + // new = [translate][scale][translate] + } + this._tmpMatrixForDraw.setMatrix(this._tmpMatrix.getArray()); + } + clipContext._matrixForMask.setMatrix(this._tmpMatrixForMask.getArray()); + clipContext._matrixForDraw.setMatrix(this._tmpMatrixForDraw.getArray()); + + const clipDrawCount: number = clipContext._clippingIdCount; + for (let i = 0; i < clipDrawCount; i++) { + const clipDrawIndex: number = clipContext._clippingIdList[i]; + + // 頂点情報が更新されておらず、信頼性がない場合は描画をパスする + if ( + !model.getDrawableDynamicFlagVertexPositionsDidChange(clipDrawIndex) + ) { + continue; + } + + renderer.setIsCulling( + model.getDrawableCulling(clipDrawIndex) != false + ); + + // 今回専用の変換を適用して描く + // チャンネルも切り替える必要がある(A,R,G,B) + renderer.setClippingContextBufferForMask(clipContext); + renderer.drawMesh( + model.getDrawableTextureIndices(clipDrawIndex), + model.getDrawableVertexIndexCount(clipDrawIndex), + model.getDrawableVertexCount(clipDrawIndex), + model.getDrawableVertexIndices(clipDrawIndex), + model.getDrawableVertices(clipDrawIndex), + model.getDrawableVertexUvs(clipDrawIndex), + model.getDrawableOpacity(clipDrawIndex), + CubismBlendMode.CubismBlendMode_Normal, // クリッピングは通常描画を強制 + false // マスク生成時はクリッピングの反転使用は全く関係がない + ); + } + } + + // --- 後処理 --- + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, s_fbo); // 描画対象を戻す + renderer.setClippingContextBufferForMask(null); + + this.gl.viewport( + s_viewport[0], + s_viewport[1], + s_viewport[2], + s_viewport[3] + ); + } + } + + /** + * 既にマスクを作っているかを確認 + * 作っている様であれば該当するクリッピングマスクのインスタンスを返す + * 作っていなければNULLを返す + * @param drawableMasks 描画オブジェクトをマスクする描画オブジェクトのリスト + * @param drawableMaskCounts 描画オブジェクトをマスクする描画オブジェクトの数 + * @return 該当するクリッピングマスクが存在すればインスタンスを返し、なければNULLを返す + */ + public findSameClip( + drawableMasks: Int32Array, + drawableMaskCounts: number + ): CubismClippingContext { + // 作成済みClippingContextと一致するか確認 + for (let i = 0; i < this._clippingContextListForMask.getSize(); i++) { + const clippingContext: CubismClippingContext = this._clippingContextListForMask.at( + i + ); + const count: number = clippingContext._clippingIdCount; + + // 個数が違う場合は別物 + if (count != drawableMaskCounts) { + continue; + } + + let sameCount = 0; + + // 同じIDを持つか確認。配列の数が同じなので、一致した個数が同じなら同じ物を持つとする + for (let j = 0; j < count; j++) { + const clipId: number = clippingContext._clippingIdList[j]; + + for (let k = 0; k < count; k++) { + if (drawableMasks[k] == clipId) { + sameCount++; + break; + } + } + } + + if (sameCount == count) { + return clippingContext; + } + } + + return null; // 見つからなかった + } + + /** + * クリッピングコンテキストを配置するレイアウト + * 一つのレンダーテクスチャを極力いっぱいに使ってマスクをレイアウトする + * マスクグループの数が4以下ならRGBA各チャンネルに一つずつマスクを配置し、5以上6以下ならRGBAを2,2,1,1と配置する。 + * + * @param usingClipCount 配置するクリッピングコンテキストの数 + */ + public setupLayoutBounds(usingClipCount: number): void { + // ひとつのRenderTextureを極力いっぱいに使ってマスクをレイアウトする + // マスクグループの数が4以下ならRGBA各チャンネルに1つずつマスクを配置し、5以上6以下ならRGBAを2,2,1,1と配置する + + // RGBAを順番に使っていく + let div: number = usingClipCount / ColorChannelCount; // 1チャンネルに配置する基本のマスク + let mod: number = usingClipCount % ColorChannelCount; // 余り、この番号のチャンネルまでに一つずつ配分する + + // 小数点は切り捨てる + div = ~~div; + mod = ~~mod; + + // RGBAそれぞれのチャンネルを用意していく(0:R, 1:G, 2:B, 3:A) + let curClipIndex = 0; // 順番に設定していく + + for (let channelNo = 0; channelNo < ColorChannelCount; channelNo++) { + // このチャンネルにレイアウトする数 + const layoutCount: number = div + (channelNo < mod ? 1 : 0); + + // 分割方法を決定する + if (layoutCount == 0) { + // 何もしない + } else if (layoutCount == 1) { + // 全てをそのまま使う + const clipContext: CubismClippingContext = this._clippingContextListForMask.at( + curClipIndex++ + ); + clipContext._layoutChannelNo = channelNo; + clipContext._layoutBounds.x = 0.0; + clipContext._layoutBounds.y = 0.0; + clipContext._layoutBounds.width = 1.0; + clipContext._layoutBounds.height = 1.0; + } else if (layoutCount == 2) { + for (let i = 0; i < layoutCount; i++) { + let xpos: number = i % 2; + + // 小数点は切り捨てる + xpos = ~~xpos; + + const cc: CubismClippingContext = this._clippingContextListForMask.at( + curClipIndex++ + ); + cc._layoutChannelNo = channelNo; + + cc._layoutBounds.x = xpos * 0.5; + cc._layoutBounds.y = 0.0; + cc._layoutBounds.width = 0.5; + cc._layoutBounds.height = 1.0; + // UVを2つに分解して使う + } + } else if (layoutCount <= 4) { + // 4分割して使う + for (let i = 0; i < layoutCount; i++) { + let xpos: number = i % 2; + let ypos: number = i / 2; + + // 小数点は切り捨てる + xpos = ~~xpos; + ypos = ~~ypos; + + const cc = this._clippingContextListForMask.at(curClipIndex++); + cc._layoutChannelNo = channelNo; + + cc._layoutBounds.x = xpos * 0.5; + cc._layoutBounds.y = ypos * 0.5; + cc._layoutBounds.width = 0.5; + cc._layoutBounds.height = 0.5; + } + } else if (layoutCount <= 9) { + // 9分割して使う + for (let i = 0; i < layoutCount; i++) { + let xpos = i % 3; + let ypos = i / 3; + + // 小数点は切り捨てる + xpos = ~~xpos; + ypos = ~~ypos; + + const cc: CubismClippingContext = this._clippingContextListForMask.at( + curClipIndex++ + ); + cc._layoutChannelNo = channelNo; + + cc._layoutBounds.x = xpos / 3.0; + cc._layoutBounds.y = ypos / 3.0; + cc._layoutBounds.width = 1.0 / 3.0; + cc._layoutBounds.height = 1.0 / 3.0; + } + } else { + CubismLogError('not supported mask count : {0}', layoutCount); + } + } + } + + /** + * カラーバッファを取得する + * @return カラーバッファ + */ + public getColorBuffer(): WebGLTexture { + return this._colorBuffer; + } + + /** + * 画面描画に使用するクリッピングマスクのリストを取得する + * @return 画面描画に使用するクリッピングマスクのリスト + */ + public getClippingContextListForDraw(): csmVector { + return this._clippingContextListForDraw; + } + + /** + * クリッピングマスクバッファのサイズを設定する + * @param size クリッピングマスクバッファのサイズ + */ + public setClippingMaskBufferSize(size: number): void { + this._clippingMaskBufferSize = size; + } + + /** + * クリッピングマスクバッファのサイズを取得する + * @return クリッピングマスクバッファのサイズ + */ + public getClippingMaskBufferSize(): number { + return this._clippingMaskBufferSize; + } + + public _maskRenderTexture: WebGLFramebuffer; // マスク用レンダーテクスチャのアドレス + public _colorBuffer: WebGLTexture; // マスク用カラーバッファーのアドレス + public _currentFrameNo: number; // マスクテクスチャに与えるフレーム番号 + + public _channelColors: csmVector; + public _maskTexture: CubismRenderTextureResource; // マスク用のテクスチャリソースのリスト + public _clippingContextListForMask: csmVector; // マスク用クリッピングコンテキストのリスト + public _clippingContextListForDraw: csmVector; // 描画用クリッピングコンテキストのリスト + public _clippingMaskBufferSize: number; // クリッピングマスクのバッファサイズ(初期値:256) + + private _tmpMatrix: CubismMatrix44; // マスク計算用の行列 + private _tmpMatrixForMask: CubismMatrix44; // マスク計算用の行列 + private _tmpMatrixForDraw: CubismMatrix44; // マスク計算用の行列 + private _tmpBoundsOnModel: csmRect; // マスク配置計算用の矩形 + + gl: WebGLRenderingContext; // WebGLレンダリングコンテキスト +} + +/** + * レンダーテクスチャのリソースを定義する構造体 + * クリッピングマスクで使用する + */ +export class CubismRenderTextureResource { + /** + * 引数付きコンストラクタ + * @param frameNo レンダラーのフレーム番号 + * @param texture テクスチャのアドレス + */ + public constructor(frameNo: number, texture: WebGLFramebuffer) { + this.frameNo = frameNo; + this.texture = texture; + } + + public frameNo: number; // レンダラのフレーム番号 + public texture: WebGLFramebuffer; // テクスチャのアドレス +} + +/** + * クリッピングマスクのコンテキスト + */ +export class CubismClippingContext { + /** + * 引数付きコンストラクタ + */ + public constructor( + manager: CubismClippingManager_WebGL, + clippingDrawableIndices: Int32Array, + clipCount: number + ) { + this._owner = manager; + + // クリップしている(=マスク用の)Drawableのインデックスリスト + this._clippingIdList = clippingDrawableIndices; + + // マスクの数 + this._clippingIdCount = clipCount; + + this._allClippedDrawRect = new csmRect(); + this._layoutBounds = new csmRect(); + + this._clippedDrawableIndexList = []; + + this._matrixForMask = new CubismMatrix44(); + this._matrixForDraw = new CubismMatrix44(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + if (this._layoutBounds != null) { + this._layoutBounds = null; + } + + if (this._allClippedDrawRect != null) { + this._allClippedDrawRect = null; + } + + if (this._clippedDrawableIndexList != null) { + this._clippedDrawableIndexList = null; + } + } + + /** + * このマスクにクリップされる描画オブジェクトを追加する + * + * @param drawableIndex クリッピング対象に追加する描画オブジェクトのインデックス + */ + public addClippedDrawable(drawableIndex: number) { + this._clippedDrawableIndexList.push(drawableIndex); + } + + /** + * このマスクを管理するマネージャのインスタンスを取得する + * @return クリッピングマネージャのインスタンス + */ + public getClippingManager(): CubismClippingManager_WebGL { + return this._owner; + } + + public setGl(gl: WebGLRenderingContext): void { + this._owner.setGL(gl); + } + + public _isUsing: boolean; // 現在の描画状態でマスクの準備が必要ならtrue + public readonly _clippingIdList: Int32Array; // クリッピングマスクのIDリスト + public _clippingIdCount: number; // クリッピングマスクの数 + public _layoutChannelNo: number; // RGBAのいずれのチャンネルにこのクリップを配置するか(0:R, 1:G, 2:B, 3:A) + public _layoutBounds: csmRect; // マスク用チャンネルのどの領域にマスクを入れるか(View座標-1~1, UVは0~1に直す) + public _allClippedDrawRect: csmRect; // このクリッピングで、クリッピングされるすべての描画オブジェクトの囲み矩形(毎回更新) + public _matrixForMask: CubismMatrix44; // マスクの位置計算結果を保持する行列 + public _matrixForDraw: CubismMatrix44; // 描画オブジェクトの位置計算結果を保持する行列 + public _clippedDrawableIndexList: number[]; // このマスクにクリップされる描画オブジェクトのリスト + + private _owner: CubismClippingManager_WebGL; // このマスクを管理しているマネージャのインスタンス +} + +/** + * WebGL用のシェーダープログラムを生成・破棄するクラス + * シングルトンなクラスであり、CubismShader_WebGL.getInstanceからアクセスする。 + */ +export class CubismShader_WebGL { + /** + * インスタンスを取得する(シングルトン) + * @return インスタンス + */ + public static getInstance(): CubismShader_WebGL { + if (s_instance == null) { + s_instance = new CubismShader_WebGL(); + + return s_instance; + } + return s_instance; + } + + /** + * インスタンスを開放する(シングルトン) + */ + public static deleteInstance(): void { + if (s_instance) { + s_instance.release(); + s_instance = null; + } + } + + /** + * privateなコンストラクタ + */ + private constructor() { + this._shaderSets = new csmVector(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this.releaseShaderProgram(); + } + + /** + * シェーダープログラムの一連のセットアップを実行する + * @param renderer レンダラのインスタンス + * @param textureId GPUのテクスチャID + * @param vertexCount ポリゴンメッシュの頂点数 + * @param vertexArray ポリゴンメッシュの頂点配列 + * @param indexArray インデックスバッファの頂点配列 + * @param uvArray uv配列 + * @param opacity 不透明度 + * @param colorBlendMode カラーブレンディングのタイプ + * @param baseColor ベースカラー + * @param isPremultipliedAlpha 乗算済みアルファかどうか + * @param matrix4x4 Model-View-Projection行列 + * @param invertedMask マスクを反転して使用するフラグ + */ + public setupShaderProgram( + renderer: CubismRenderer_WebGL, + textureId: WebGLTexture, + vertexCount: number, + vertexArray: Float32Array, + indexArray: Uint16Array, + uvArray: Float32Array, + bufferData: { + vertex: WebGLBuffer; + uv: WebGLBuffer; + index: WebGLBuffer; + }, + opacity: number, + colorBlendMode: CubismBlendMode, + baseColor: CubismTextureColor, + isPremultipliedAlpha: boolean, + matrix4x4: CubismMatrix44, + invertedMask: boolean + ): void { + if (!isPremultipliedAlpha) { + CubismLogError('NoPremultipliedAlpha is not allowed'); + } + + if (this._shaderSets.getSize() == 0) { + this.generateShaders(); + } + + // Blending + let SRC_COLOR: number; + let DST_COLOR: number; + let SRC_ALPHA: number; + let DST_ALPHA: number; + + if (renderer.getClippingContextBufferForMask() != null) { + // マスク生成時 + const shaderSet: CubismShaderSet = this._shaderSets.at( + ShaderNames.ShaderNames_SetupMask + ); + this.gl.useProgram(shaderSet.shaderProgram); + + // テクスチャ設定 + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, textureId); + this.gl.uniform1i(shaderSet.samplerTexture0Location, 0); + + // 頂点配列の設定(VBO) + if (bufferData.vertex == null) { + bufferData.vertex = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, bufferData.vertex); + this.gl.bufferData( + this.gl.ARRAY_BUFFER, + vertexArray, + this.gl.DYNAMIC_DRAW + ); + this.gl.enableVertexAttribArray(shaderSet.attributePositionLocation); + this.gl.vertexAttribPointer( + shaderSet.attributePositionLocation, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + + // テクスチャ頂点の設定 + if (bufferData.uv == null) { + bufferData.uv = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, bufferData.uv); + this.gl.bufferData(this.gl.ARRAY_BUFFER, uvArray, this.gl.DYNAMIC_DRAW); + this.gl.enableVertexAttribArray(shaderSet.attributeTexCoordLocation); + this.gl.vertexAttribPointer( + shaderSet.attributeTexCoordLocation, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + + // チャンネル + const channelNo: number = renderer.getClippingContextBufferForMask() + ._layoutChannelNo; + const colorChannel: CubismTextureColor = renderer + .getClippingContextBufferForMask() + .getClippingManager() + .getChannelFlagAsColor(channelNo); + this.gl.uniform4f( + shaderSet.uniformChannelFlagLocation, + colorChannel.R, + colorChannel.G, + colorChannel.B, + colorChannel.A + ); + + this.gl.uniformMatrix4fv( + shaderSet.uniformClipMatrixLocation, + false, + renderer.getClippingContextBufferForMask()._matrixForMask.getArray() + ); + + const rect: csmRect = renderer.getClippingContextBufferForMask() + ._layoutBounds; + + this.gl.uniform4f( + shaderSet.uniformBaseColorLocation, + rect.x * 2.0 - 1.0, + rect.y * 2.0 - 1.0, + rect.getRight() * 2.0 - 1.0, + rect.getBottom() * 2.0 - 1.0 + ); + + SRC_COLOR = this.gl.ZERO; + DST_COLOR = this.gl.ONE_MINUS_SRC_COLOR; + SRC_ALPHA = this.gl.ZERO; + DST_ALPHA = this.gl.ONE_MINUS_SRC_ALPHA; + } // マスク生成以外の場合 + else { + const masked: boolean = + renderer.getClippingContextBufferForDraw() != null; // この描画オブジェクトはマスク対象か + const offset: number = masked ? (invertedMask ? 2 : 1) : 0; + + let shaderSet: CubismShaderSet = new CubismShaderSet(); + + switch (colorBlendMode) { + case CubismBlendMode.CubismBlendMode_Normal: + default: + shaderSet = this._shaderSets.at( + ShaderNames.ShaderNames_NormalPremultipliedAlpha + offset + ); + SRC_COLOR = this.gl.ONE; + DST_COLOR = this.gl.ONE_MINUS_SRC_ALPHA; + SRC_ALPHA = this.gl.ONE; + DST_ALPHA = this.gl.ONE_MINUS_SRC_ALPHA; + break; + + case CubismBlendMode.CubismBlendMode_Additive: + shaderSet = this._shaderSets.at( + ShaderNames.ShaderNames_AddPremultipliedAlpha + offset + ); + SRC_COLOR = this.gl.ONE; + DST_COLOR = this.gl.ONE; + SRC_ALPHA = this.gl.ZERO; + DST_ALPHA = this.gl.ONE; + break; + + case CubismBlendMode.CubismBlendMode_Multiplicative: + shaderSet = this._shaderSets.at( + ShaderNames.ShaderNames_MultPremultipliedAlpha + offset + ); + SRC_COLOR = this.gl.DST_COLOR; + DST_COLOR = this.gl.ONE_MINUS_SRC_ALPHA; + SRC_ALPHA = this.gl.ZERO; + DST_ALPHA = this.gl.ONE; + break; + } + + this.gl.useProgram(shaderSet.shaderProgram); + + // 頂点配列の設定 + if (bufferData.vertex == null) { + bufferData.vertex = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, bufferData.vertex); + this.gl.bufferData( + this.gl.ARRAY_BUFFER, + vertexArray, + this.gl.DYNAMIC_DRAW + ); + this.gl.enableVertexAttribArray(shaderSet.attributePositionLocation); + this.gl.vertexAttribPointer( + shaderSet.attributePositionLocation, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + + // テクスチャ頂点の設定 + if (bufferData.uv == null) { + bufferData.uv = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, bufferData.uv); + this.gl.bufferData(this.gl.ARRAY_BUFFER, uvArray, this.gl.DYNAMIC_DRAW); + this.gl.enableVertexAttribArray(shaderSet.attributeTexCoordLocation); + this.gl.vertexAttribPointer( + shaderSet.attributeTexCoordLocation, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + + if (masked) { + this.gl.activeTexture(this.gl.TEXTURE1); + const tex: WebGLTexture = renderer + .getClippingContextBufferForDraw() + .getClippingManager() + .getColorBuffer(); + this.gl.bindTexture(this.gl.TEXTURE_2D, tex); + this.gl.uniform1i(shaderSet.samplerTexture1Location, 1); + + // view座標をClippingContextの座標に変換するための行列を設定 + this.gl.uniformMatrix4fv( + shaderSet.uniformClipMatrixLocation, + false, + renderer.getClippingContextBufferForDraw()._matrixForDraw.getArray() + ); + + // 使用するカラーチャンネルを設定 + const channelNo: number = renderer.getClippingContextBufferForDraw() + ._layoutChannelNo; + const colorChannel: CubismTextureColor = renderer + .getClippingContextBufferForDraw() + .getClippingManager() + .getChannelFlagAsColor(channelNo); + this.gl.uniform4f( + shaderSet.uniformChannelFlagLocation, + colorChannel.R, + colorChannel.G, + colorChannel.B, + colorChannel.A + ); + } + + // テクスチャ設定 + this.gl.activeTexture(this.gl.TEXTURE0); + this.gl.bindTexture(this.gl.TEXTURE_2D, textureId); + this.gl.uniform1i(shaderSet.samplerTexture0Location, 0); + + // 座標変換 + this.gl.uniformMatrix4fv( + shaderSet.uniformMatrixLocation, + false, + matrix4x4.getArray() + ); + + this.gl.uniform4f( + shaderSet.uniformBaseColorLocation, + baseColor.R, + baseColor.G, + baseColor.B, + baseColor.A + ); + } + + // IBOを作成し、データを転送 + if (bufferData.index == null) { + bufferData.index = this.gl.createBuffer(); + } + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, bufferData.index); + this.gl.bufferData( + this.gl.ELEMENT_ARRAY_BUFFER, + indexArray, + this.gl.DYNAMIC_DRAW + ); + this.gl.blendFuncSeparate(SRC_COLOR, DST_COLOR, SRC_ALPHA, DST_ALPHA); + } + + /** + * シェーダープログラムを解放する + */ + public releaseShaderProgram(): void { + for (let i = 0; i < this._shaderSets.getSize(); i++) { + this.gl.deleteProgram(this._shaderSets.at(i).shaderProgram); + this._shaderSets.at(i).shaderProgram = 0; + this._shaderSets.set(i, void 0); + this._shaderSets.set(i, null); + } + } + + /** + * シェーダープログラムを初期化する + * @param vertShaderSrc 頂点シェーダのソース + * @param fragShaderSrc フラグメントシェーダのソース + */ + public generateShaders(): void { + for (let i = 0; i < shaderCount; i++) { + this._shaderSets.pushBack(new CubismShaderSet()); + } + + this._shaderSets.at(0).shaderProgram = this.loadShaderProgram( + vertexShaderSrcSetupMask, + fragmentShaderSrcsetupMask + ); + + this._shaderSets.at(1).shaderProgram = this.loadShaderProgram( + vertexShaderSrc, + fragmentShaderSrcPremultipliedAlpha + ); + this._shaderSets.at(2).shaderProgram = this.loadShaderProgram( + vertexShaderSrcMasked, + fragmentShaderSrcMaskPremultipliedAlpha + ); + this._shaderSets.at(3).shaderProgram = this.loadShaderProgram( + vertexShaderSrcMasked, + fragmentShaderSrcMaskInvertedPremultipliedAlpha + ); + + // 加算も通常と同じシェーダーを利用する + this._shaderSets.at(4).shaderProgram = this._shaderSets.at(1).shaderProgram; + this._shaderSets.at(5).shaderProgram = this._shaderSets.at(2).shaderProgram; + this._shaderSets.at(6).shaderProgram = this._shaderSets.at(3).shaderProgram; + + // 乗算も通常と同じシェーダーを利用する + this._shaderSets.at(7).shaderProgram = this._shaderSets.at(1).shaderProgram; + this._shaderSets.at(8).shaderProgram = this._shaderSets.at(2).shaderProgram; + this._shaderSets.at(9).shaderProgram = this._shaderSets.at(3).shaderProgram; + + // SetupMask + this._shaderSets.at( + 0 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(0).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 0 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(0).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at(0).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(0).shaderProgram, + 's_texture0' + ); + this._shaderSets.at( + 0 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(0).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 0 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(0).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 0 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(0).shaderProgram, + 'u_baseColor' + ); + + // 通常(PremultipliedAlpha) + this._shaderSets.at( + 1 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(1).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 1 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(1).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at(1).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(1).shaderProgram, + 's_texture0' + ); + this._shaderSets.at(1).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(1).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 1 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(1).shaderProgram, + 'u_baseColor' + ); + + // 通常(クリッピング、PremultipliedAlpha) + this._shaderSets.at( + 2 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(2).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 2 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(2).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at(2).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(2).shaderProgram, + 's_texture0' + ); + this._shaderSets.at(2).samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets.at(2).shaderProgram, + 's_texture1' + ); + this._shaderSets.at(2).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(2).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 2 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(2).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 2 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(2).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 2 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(2).shaderProgram, + 'u_baseColor' + ); + + // 通常(クリッピング・反転, PremultipliedAlpha) + this._shaderSets.at( + 3 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(3).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 3 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(3).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at(3).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(3).shaderProgram, + 's_texture0' + ); + this._shaderSets.at(3).samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets.at(3).shaderProgram, + 's_texture1' + ); + this._shaderSets.at(3).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(3).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 3 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(3).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 3 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(3).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 3 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(3).shaderProgram, + 'u_baseColor' + ); + + // 加算(PremultipliedAlpha) + this._shaderSets.at( + 4 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(4).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 4 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(4).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at(4).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(4).shaderProgram, + 's_texture0' + ); + this._shaderSets.at(4).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(4).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 4 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(4).shaderProgram, + 'u_baseColor' + ); + + // 加算(クリッピング、PremultipliedAlpha) + this._shaderSets.at( + 5 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(5).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 5 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(5).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at(5).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(5).shaderProgram, + 's_texture0' + ); + this._shaderSets.at(5).samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets.at(5).shaderProgram, + 's_texture1' + ); + this._shaderSets.at(5).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(5).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 5 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(5).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 5 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(5).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 5 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(5).shaderProgram, + 'u_baseColor' + ); + + // 加算(クリッピング・反転、PremultipliedAlpha) + this._shaderSets.at( + 6 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(6).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 6 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(6).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at(6).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(6).shaderProgram, + 's_texture0' + ); + this._shaderSets.at(6).samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets.at(6).shaderProgram, + 's_texture1' + ); + this._shaderSets.at(6).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(6).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 6 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(6).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 6 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(6).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 6 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(6).shaderProgram, + 'u_baseColor' + ); + + // 乗算(PremultipliedAlpha) + this._shaderSets.at( + 7 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(7).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 7 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(7).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at(7).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(7).shaderProgram, + 's_texture0' + ); + this._shaderSets.at(7).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(7).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 7 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(7).shaderProgram, + 'u_baseColor' + ); + + // 乗算(クリッピング、PremultipliedAlpha) + this._shaderSets.at( + 8 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(8).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 8 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(8).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at(8).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(8).shaderProgram, + 's_texture0' + ); + this._shaderSets.at(8).samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets.at(8).shaderProgram, + 's_texture1' + ); + this._shaderSets.at(8).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(8).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 8 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(8).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 8 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(8).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 8 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(8).shaderProgram, + 'u_baseColor' + ); + + // 乗算(クリッピング・反転、PremultipliedAlpha) + this._shaderSets.at( + 9 + ).attributePositionLocation = this.gl.getAttribLocation( + this._shaderSets.at(9).shaderProgram, + 'a_position' + ); + this._shaderSets.at( + 9 + ).attributeTexCoordLocation = this.gl.getAttribLocation( + this._shaderSets.at(9).shaderProgram, + 'a_texCoord' + ); + this._shaderSets.at(9).samplerTexture0Location = this.gl.getUniformLocation( + this._shaderSets.at(9).shaderProgram, + 's_texture0' + ); + this._shaderSets.at(9).samplerTexture1Location = this.gl.getUniformLocation( + this._shaderSets.at(9).shaderProgram, + 's_texture1' + ); + this._shaderSets.at(9).uniformMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(9).shaderProgram, + 'u_matrix' + ); + this._shaderSets.at( + 9 + ).uniformClipMatrixLocation = this.gl.getUniformLocation( + this._shaderSets.at(9).shaderProgram, + 'u_clipMatrix' + ); + this._shaderSets.at( + 9 + ).uniformChannelFlagLocation = this.gl.getUniformLocation( + this._shaderSets.at(9).shaderProgram, + 'u_channelFlag' + ); + this._shaderSets.at( + 9 + ).uniformBaseColorLocation = this.gl.getUniformLocation( + this._shaderSets.at(9).shaderProgram, + 'u_baseColor' + ); + } + + /** + * シェーダプログラムをロードしてアドレスを返す + * @param vertexShaderSource 頂点シェーダのソース + * @param fragmentShaderSource フラグメントシェーダのソース + * @return シェーダプログラムのアドレス + */ + public loadShaderProgram( + vertexShaderSource: string, + fragmentShaderSource: string + ): WebGLProgram { + // Create Shader Program + let shaderProgram: WebGLProgram = this.gl.createProgram(); + + let vertShader = this.compileShaderSource( + this.gl.VERTEX_SHADER, + vertexShaderSource + ); + + if (!vertShader) { + CubismLogError('Vertex shader compile error!'); + return 0; + } + + let fragShader = this.compileShaderSource( + this.gl.FRAGMENT_SHADER, + fragmentShaderSource + ); + if (!fragShader) { + CubismLogError('Vertex shader compile error!'); + return 0; + } + + // Attach vertex shader to program + this.gl.attachShader(shaderProgram, vertShader); + + // Attach fragment shader to program + this.gl.attachShader(shaderProgram, fragShader); + + // link program + this.gl.linkProgram(shaderProgram); + const linkStatus = this.gl.getProgramParameter( + shaderProgram, + this.gl.LINK_STATUS + ); + + // リンクに失敗したらシェーダーを削除 + if (!linkStatus) { + CubismLogError('Failed to link program: {0}', shaderProgram); + + this.gl.deleteShader(vertShader); + vertShader = 0; + + this.gl.deleteShader(fragShader); + fragShader = 0; + + if (shaderProgram) { + this.gl.deleteProgram(shaderProgram); + shaderProgram = 0; + } + + return 0; + } + + // Release vertex and fragment shaders. + this.gl.deleteShader(vertShader); + this.gl.deleteShader(fragShader); + + return shaderProgram; + } + + /** + * シェーダープログラムをコンパイルする + * @param shaderType シェーダタイプ(Vertex/Fragment) + * @param shaderSource シェーダソースコード + * + * @return コンパイルされたシェーダープログラム + */ + public compileShaderSource( + shaderType: GLenum, + shaderSource: string + ): WebGLProgram { + const source: string = shaderSource; + + const shader: WebGLProgram = this.gl.createShader(shaderType); + this.gl.shaderSource(shader, source); + this.gl.compileShader(shader); + + if (!shader) { + const log: string = this.gl.getShaderInfoLog(shader); + CubismLogError('Shader compile log: {0} ', log); + } + + const status: any = this.gl.getShaderParameter( + shader, + this.gl.COMPILE_STATUS + ); + if (!status) { + this.gl.deleteShader(shader); + return null; + } + + return shader; + } + + public setGl(gl: WebGLRenderingContext): void { + this.gl = gl; + } + + _shaderSets: csmVector; // ロードしたシェーダープログラムを保持する変数 + gl: WebGLRenderingContext; // webglコンテキスト +} + +/** + * CubismShader_WebGLのインナークラス + */ +export class CubismShaderSet { + shaderProgram: WebGLProgram; // シェーダープログラムのアドレス + attributePositionLocation: GLuint; // シェーダープログラムに渡す変数のアドレス(Position) + attributeTexCoordLocation: GLuint; // シェーダープログラムに渡す変数のアドレス(TexCoord) + uniformMatrixLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(Matrix) + uniformClipMatrixLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(ClipMatrix) + samplerTexture0Location: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(Texture0) + samplerTexture1Location: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(Texture1) + uniformBaseColorLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(BaseColor) + uniformChannelFlagLocation: WebGLUniformLocation; // シェーダープログラムに渡す変数のアドレス(ChannelFlag) +} + +export enum ShaderNames { + // SetupMask + ShaderNames_SetupMask, + + // Normal + ShaderNames_NormalPremultipliedAlpha, + ShaderNames_NormalMaskedPremultipliedAlpha, + ShaderNames_NomralMaskedInvertedPremultipliedAlpha, + + // Add + ShaderNames_AddPremultipliedAlpha, + ShaderNames_AddMaskedPremultipliedAlpha, + ShaderNames_AddMaskedPremultipliedAlphaInverted, + + // Mult + ShaderNames_MultPremultipliedAlpha, + ShaderNames_MultMaskedPremultipliedAlpha, + ShaderNames_MultMaskedPremultipliedAlphaInverted +} + +export const vertexShaderSrcSetupMask = + 'attribute vec4 a_position;' + + 'attribute vec2 a_texCoord;' + + 'varying vec2 v_texCoord;' + + 'varying vec4 v_myPos;' + + 'uniform mat4 u_clipMatrix;' + + 'void main()' + + '{' + + ' gl_Position = u_clipMatrix * a_position;' + + ' v_myPos = u_clipMatrix * a_position;' + + ' v_texCoord = a_texCoord;' + + ' v_texCoord.y = 1.0 - v_texCoord.y;' + + '}'; +export const fragmentShaderSrcsetupMask = + 'precision mediump float;' + + 'varying vec2 v_texCoord;' + + 'varying vec4 v_myPos;' + + 'uniform vec4 u_baseColor;' + + 'uniform vec4 u_channelFlag;' + + 'uniform sampler2D s_texture0;' + + 'void main()' + + '{' + + ' float isInside = ' + + ' step(u_baseColor.x, v_myPos.x/v_myPos.w)' + + ' * step(u_baseColor.y, v_myPos.y/v_myPos.w)' + + ' * step(v_myPos.x/v_myPos.w, u_baseColor.z)' + + ' * step(v_myPos.y/v_myPos.w, u_baseColor.w);' + + ' gl_FragColor = u_channelFlag * texture2D(s_texture0, v_texCoord).a * isInside;' + + '}'; + +//----- バーテックスシェーダプログラム ----- +// Normal & Add & Mult 共通 +export const vertexShaderSrc = + 'attribute vec4 a_position;' + //v.vertex + 'attribute vec2 a_texCoord;' + //v.texcoord + 'varying vec2 v_texCoord;' + //v2f.texcoord + 'uniform mat4 u_matrix;' + + 'void main()' + + '{' + + ' gl_Position = u_matrix * a_position;' + + ' v_texCoord = a_texCoord;' + + ' v_texCoord.y = 1.0 - v_texCoord.y;' + + '}'; + +// Normal & Add & Mult 共通(クリッピングされたものの描画用) +export const vertexShaderSrcMasked = + 'attribute vec4 a_position;' + + 'attribute vec2 a_texCoord;' + + 'varying vec2 v_texCoord;' + + 'varying vec4 v_clipPos;' + + 'uniform mat4 u_matrix;' + + 'uniform mat4 u_clipMatrix;' + + 'void main()' + + '{' + + ' gl_Position = u_matrix * a_position;' + + ' v_clipPos = u_clipMatrix * a_position;' + + ' v_texCoord = a_texCoord;' + + ' v_texCoord.y = 1.0 - v_texCoord.y;' + + '}'; + +//----- フラグメントシェーダプログラム ----- +// Normal & Add & Mult 共通 (PremultipliedAlpha) +export const fragmentShaderSrcPremultipliedAlpha = + 'precision mediump float;' + + 'varying vec2 v_texCoord;' + //v2f.texcoord + 'uniform vec4 u_baseColor;' + + 'uniform sampler2D s_texture0;' + //_MainTex + 'void main()' + + '{' + + ' gl_FragColor = texture2D(s_texture0 , v_texCoord) * u_baseColor;' + + '}'; + +// Normal (クリッピングされたものの描画用、PremultipliedAlpha兼用) +export const fragmentShaderSrcMaskPremultipliedAlpha = + 'precision mediump float;' + + 'varying vec2 v_texCoord;' + + 'varying vec4 v_clipPos;' + + 'uniform vec4 u_baseColor;' + + 'uniform vec4 u_channelFlag;' + + 'uniform sampler2D s_texture0;' + + 'uniform sampler2D s_texture1;' + + 'void main()' + + '{' + + ' vec4 col_formask = texture2D(s_texture0 , v_texCoord) * u_baseColor;' + + ' vec4 clipMask = (1.0 - texture2D(s_texture1, v_clipPos.xy / v_clipPos.w)) * u_channelFlag;' + + ' float maskVal = clipMask.r + clipMask.g + clipMask.b + clipMask.a;' + + ' col_formask = col_formask * maskVal;' + + ' gl_FragColor = col_formask;' + + '}'; + +// Normal & Add & Mult 共通(クリッピングされて反転使用の描画用、PremultipliedAlphaの場合) +export const fragmentShaderSrcMaskInvertedPremultipliedAlpha = + 'precision mediump float;' + + 'varying vec2 v_texCoord;' + + 'varying vec4 v_clipPos;' + + 'uniform sampler2D s_texture0;' + + 'uniform sampler2D s_texture1;' + + 'uniform vec4 u_channelFlag;' + + 'uniform vec4 u_baseColor;' + + 'void main()' + + '{' + + 'vec4 col_formask = texture2D(s_texture0, v_texCoord) * u_baseColor;' + + 'vec4 clipMask = (1.0 - texture2D(s_texture1, v_clipPos.xy / v_clipPos.w)) * u_channelFlag;' + + 'float maskVal = clipMask.r + clipMask.g + clipMask.b + clipMask.a;' + + 'col_formask = col_formask * (1.0 - maskVal);' + + 'gl_FragColor = col_formask;' + + '}'; + +/** + * WebGL用の描画命令を実装したクラス + */ +export class CubismRenderer_WebGL extends CubismRenderer { + /** + * レンダラの初期化処理を実行する + * 引数に渡したモデルからレンダラの初期化処理に必要な情報を取り出すことができる + * + * @param model モデルのインスタンス + */ + public initialize(model: CubismModel): void { + if (model.isUsingMasking()) { + this._clippingManager = new CubismClippingManager_WebGL(); // クリッピングマスク・バッファ前処理方式を初期化 + this._clippingManager.initialize( + model, + model.getDrawableCount(), + model.getDrawableMasks(), + model.getDrawableMaskCounts() + ); + } + + this._sortedDrawableIndexList.resize(model.getDrawableCount(), 0); + + super.initialize(model); // 親クラスの処理を呼ぶ + } + + /** + * WebGLテクスチャのバインド処理 + * CubismRendererにテクスチャを設定し、CubismRenderer内でその画像を参照するためのIndex値を戻り値とする + * @param modelTextureNo セットするモデルテクスチャの番号 + * @param glTextureNo WebGLテクスチャの番号 + */ + public bindTexture(modelTextureNo: number, glTexture: WebGLTexture): void { + this._textures.setValue(modelTextureNo, glTexture); + } + + /** + * WebGLにバインドされたテクスチャのリストを取得する + * @return テクスチャのリスト + */ + public getBindedTextures(): csmMap { + return this._textures; + } + + /** + * クリッピングマスクバッファのサイズを設定する + * マスク用のFrameBufferを破棄、再作成する為処理コストは高い + * @param size クリッピングマスクバッファのサイズ + */ + public setClippingMaskBufferSize(size: number) { + // FrameBufferのサイズを変更するためにインスタンスを破棄・再作成する + this._clippingManager.release(); + this._clippingManager = void 0; + this._clippingManager = null; + + this._clippingManager = new CubismClippingManager_WebGL(); + + this._clippingManager.setClippingMaskBufferSize(size); + + this._clippingManager.initialize( + this.getModel(), + this.getModel().getDrawableCount(), + this.getModel().getDrawableMasks(), + this.getModel().getDrawableMaskCounts() + ); + } + + /** + * クリッピングマスクバッファのサイズを取得する + * @return クリッピングマスクバッファのサイズ + */ + public getClippingMaskBufferSize(): number { + return this._clippingManager.getClippingMaskBufferSize(); + } + + /** + * コンストラクタ + */ + public constructor() { + super(); + this._clippingContextBufferForMask = null; + this._clippingContextBufferForDraw = null; + this._clippingManager = new CubismClippingManager_WebGL(); + this.firstDraw = true; + this._textures = new csmMap(); + this._sortedDrawableIndexList = new csmVector(); + this._bufferData = { + vertex: WebGLBuffer = null, + uv: WebGLBuffer = null, + index: WebGLBuffer = null + }; + + // テクスチャ対応マップの容量を確保しておく + this._textures.prepareCapacity(32, true); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + this._clippingManager.release(); + this._clippingManager = void 0; + this._clippingManager = null; + + this.gl.deleteBuffer(this._bufferData.vertex); + this._bufferData.vertex = null; + this.gl.deleteBuffer(this._bufferData.uv); + this._bufferData.uv = null; + this.gl.deleteBuffer(this._bufferData.index); + this._bufferData.index = null; + this._bufferData = null; + + this._textures = null; + } + + /** + * モデルを描画する実際の処理 + */ + public doDrawModel(): void { + //------------ クリッピングマスク・バッファ前処理方式の場合 ------------ + if (this._clippingManager != null) { + this.preDraw(); + this._clippingManager.setupClippingContext(this.getModel(), this); + } + + // 上記クリッピング処理内でも一度PreDrawを呼ぶので注意!! + this.preDraw(); + + const drawableCount: number = this.getModel().getDrawableCount(); + const renderOrder: Int32Array = this.getModel().getDrawableRenderOrders(); + + // インデックスを描画順でソート + for (let i = 0; i < drawableCount; ++i) { + const order: number = renderOrder[i]; + this._sortedDrawableIndexList.set(order, i); + } + + // 描画 + for (let i = 0; i < drawableCount; ++i) { + const drawableIndex: number = this._sortedDrawableIndexList.at(i); + + // Drawableが表示状態でなければ処理をパスする + if (!this.getModel().getDrawableDynamicFlagIsVisible(drawableIndex)) { + continue; + } + + // クリッピングマスクをセットする + this.setClippingContextBufferForDraw( + this._clippingManager != null + ? this._clippingManager + .getClippingContextListForDraw() + .at(drawableIndex) + : null + ); + + this.setIsCulling(this.getModel().getDrawableCulling(drawableIndex)); + + this.drawMesh( + this.getModel().getDrawableTextureIndices(drawableIndex), + this.getModel().getDrawableVertexIndexCount(drawableIndex), + this.getModel().getDrawableVertexCount(drawableIndex), + this.getModel().getDrawableVertexIndices(drawableIndex), + this.getModel().getDrawableVertices(drawableIndex), + this.getModel().getDrawableVertexUvs(drawableIndex), + this.getModel().getDrawableOpacity(drawableIndex), + this.getModel().getDrawableBlendMode(drawableIndex), + this.getModel().getDrawableInvertedMaskBit(drawableIndex) + ); + } + } + + /** + * [オーバーライド] + * 描画オブジェクト(アートメッシュ)を描画する。 + * ポリゴンメッシュとテクスチャ番号をセットで渡す。 + * @param textureNo 描画するテクスチャ番号 + * @param indexCount 描画オブジェクトのインデックス値 + * @param vertexCount ポリゴンメッシュの頂点数 + * @param indexArray ポリゴンメッシュのインデックス配列 + * @param vertexArray ポリゴンメッシュの頂点配列 + * @param uvArray uv配列 + * @param opacity 不透明度 + * @param colorBlendMode カラー合成タイプ + * @param invertedMask マスク使用時のマスクの反転使用 + */ + public drawMesh( + textureNo: number, + indexCount: number, + vertexCount: number, + indexArray: Uint16Array, + vertexArray: Float32Array, + uvArray: Float32Array, + opacity: number, + colorBlendMode: CubismBlendMode, + invertedMask: boolean + ): void { + // 裏面描画の有効・無効 + if (this.isCulling()) { + this.gl.enable(this.gl.CULL_FACE); + } else { + this.gl.disable(this.gl.CULL_FACE); + } + + this.gl.frontFace(this.gl.CCW); // Cubism SDK OpenGLはマスク・アートメッシュ共にCCWが表面 + + const modelColorRGBA: CubismTextureColor = this.getModelColor(); + + if (this.getClippingContextBufferForMask() == null) { + // マスク生成時以外 + modelColorRGBA.A *= opacity; + if (this.isPremultipliedAlpha()) { + modelColorRGBA.R *= modelColorRGBA.A; + modelColorRGBA.G *= modelColorRGBA.A; + modelColorRGBA.B *= modelColorRGBA.A; + } + } + + let drawtexture: WebGLTexture; // シェーダに渡すテクスチャ + + // テクスチャマップからバインド済みテクスチャIDを取得 + // バインドされていなければダミーのテクスチャIDをセットする + if (this._textures.getValue(textureNo) != null) { + drawtexture = this._textures.getValue(textureNo); + } else { + drawtexture = null; + } + + CubismShader_WebGL.getInstance().setupShaderProgram( + this, + drawtexture, + vertexCount, + vertexArray, + indexArray, + uvArray, + this._bufferData, + opacity, + colorBlendMode, + modelColorRGBA, + this.isPremultipliedAlpha(), + this.getMvpMatrix(), + invertedMask + ); + + // ポリゴンメッシュを描画する + this.gl.drawElements( + this.gl.TRIANGLES, + indexCount, + this.gl.UNSIGNED_SHORT, + 0 + ); + + // 後処理 + this.gl.useProgram(null); + this.setClippingContextBufferForDraw(null); + this.setClippingContextBufferForMask(null); + } + + /** + * レンダラが保持する静的なリソースを解放する + * WebGLの静的なシェーダープログラムを解放する + */ + public static doStaticRelease(): void { + CubismShader_WebGL.deleteInstance(); + } + + /** + * レンダーステートを設定する + * @param fbo アプリケーション側で指定しているフレームバッファ + * @param viewport ビューポート + */ + public setRenderState(fbo: WebGLFramebuffer, viewport: number[]): void { + s_fbo = fbo; + s_viewport = viewport; + } + + /** + * 描画開始時の追加処理 + * モデルを描画する前にクリッピングマスクに必要な処理を実装している + */ + public preDraw(): void { + if (this.firstDraw) { + this.firstDraw = false; + + // 拡張機能を有効にする + this._anisortopy = + this.gl.getExtension('EXT_texture_filter_anisotropic') || + this.gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic') || + this.gl.getExtension('MOZ_EXT_texture_filter_anisotropic'); + } + + this.gl.disable(this.gl.SCISSOR_TEST); + this.gl.disable(this.gl.STENCIL_TEST); + this.gl.disable(this.gl.DEPTH_TEST); + + // カリング(1.0beta3) + this.gl.frontFace(this.gl.CW); + + this.gl.enable(this.gl.BLEND); + this.gl.colorMask(true, true, true, true); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null); // 前にバッファがバインドされていたら破棄する必要がある + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null); + } + + /** + * マスクテクスチャに描画するクリッピングコンテキストをセットする + */ + public setClippingContextBufferForMask(clip: CubismClippingContext) { + this._clippingContextBufferForMask = clip; + } + + /** + * マスクテクスチャに描画するクリッピングコンテキストを取得する + * @return マスクテクスチャに描画するクリッピングコンテキスト + */ + public getClippingContextBufferForMask(): CubismClippingContext { + return this._clippingContextBufferForMask; + } + + /** + * 画面上に描画するクリッピングコンテキストをセットする + */ + public setClippingContextBufferForDraw(clip: CubismClippingContext): void { + this._clippingContextBufferForDraw = clip; + } + + /** + * 画面上に描画するクリッピングコンテキストを取得する + * @return 画面上に描画するクリッピングコンテキスト + */ + public getClippingContextBufferForDraw(): CubismClippingContext { + return this._clippingContextBufferForDraw; + } + + /** + * glの設定 + */ + public startUp(gl: WebGLRenderingContext): void { + this.gl = gl; + this._clippingManager.setGL(gl); + CubismShader_WebGL.getInstance().setGl(gl); + } + + _textures: csmMap; // モデルが参照するテクスチャとレンダラでバインドしているテクスチャとのマップ + _sortedDrawableIndexList: csmVector; // 描画オブジェクトのインデックスを描画順に並べたリスト + _clippingManager: CubismClippingManager_WebGL; // クリッピングマスク管理オブジェクト + _clippingContextBufferForMask: CubismClippingContext; // マスクテクスチャに描画するためのクリッピングコンテキスト + _clippingContextBufferForDraw: CubismClippingContext; // 画面上描画するためのクリッピングコンテキスト + firstDraw: boolean; + _bufferData: { + vertex: WebGLBuffer; + uv: WebGLBuffer; + index: WebGLBuffer; + }; // 頂点バッファデータ + gl: WebGLRenderingContext; // webglコンテキスト +} + +/** + * レンダラが保持する静的なリソースを開放する + */ +CubismRenderer.staticRelease = (): void => { + CubismRenderer_WebGL.doStaticRelease(); +}; + +// Namespace definition for compatibility. +import * as $ from './cubismrenderer_webgl'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismClippingContext = $.CubismClippingContext; + export type CubismClippingContext = $.CubismClippingContext; + export const CubismClippingManager_WebGL = $.CubismClippingManager_WebGL; + export type CubismClippingManager_WebGL = $.CubismClippingManager_WebGL; + export const CubismRenderTextureResource = $.CubismRenderTextureResource; + export type CubismRenderTextureResource = $.CubismRenderTextureResource; + export const CubismRenderer_WebGL = $.CubismRenderer_WebGL; + export type CubismRenderer_WebGL = $.CubismRenderer_WebGL; + export const CubismShaderSet = $.CubismShaderSet; + export type CubismShaderSet = $.CubismShaderSet; + export const CubismShader_WebGL = $.CubismShader_WebGL; + export type CubismShader_WebGL = $.CubismShader_WebGL; + export const ShaderNames = $.ShaderNames; + export type ShaderNames = $.ShaderNames; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmmap.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmmap.ts new file mode 100644 index 000000000..ba7d46721 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmmap.ts @@ -0,0 +1,315 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { CubismLogDebug } from '../utils/cubismdebug'; + +/** + * Key-Valueのペアを定義するクラス + * csmMapクラスの内部データで使用する。 + */ +export class csmPair<_KeyT, _ValT> { + /** + * コンストラクタ + * @param key Keyとしてセットする値 + * @param value Valueとしてセットする値 + */ + public constructor(key?: _KeyT, value?: _ValT) { + this.first = key == undefined ? null : key; + + this.second = value == undefined ? null : value; + } + + public first: _KeyT; // keyとして用いる変数 + public second: _ValT; // valueとして用いる変数 +} + +/** + * マップ型 + */ +export class csmMap<_KeyT, _ValT> { + /** + * 引数付きコンストラクタ + * @param size 初期化時点で確保するサイズ + */ + public constructor(size?: number) { + if (size != undefined) { + if (size < 1) { + this._keyValues = []; + this._dummyValue = null; + this._size = 0; + } else { + this._keyValues = new Array(size); + this._size = size; + } + } else { + this._keyValues = []; + this._dummyValue = null; + this._size = 0; + } + } + + /** + * デストラクタ + */ + public release() { + this.clear(); + } + + /** + * キーを追加する + * @param key 新たに追加するキー + */ + public appendKey(key: _KeyT): void { + // 新しくKey/Valueのペアを作る + this.prepareCapacity(this._size + 1, false); // 1つ以上入る隙間を作る + // 新しいkey/valueのインデックスは_size + + this._keyValues[this._size] = new csmPair<_KeyT, _ValT>(key); + this._size += 1; + } + + /** + * 添字演算子[key]のオーバーロード(get) + * @param key 添字から特定されるValue値 + */ + public getValue(key: _KeyT): _ValT { + let found = -1; + + for (let i = 0; i < this._size; i++) { + if (this._keyValues[i].first == key) { + found = i; + break; + } + } + + if (found >= 0) { + return this._keyValues[found].second; + } else { + this.appendKey(key); // 新規キーを追加 + return this._keyValues[this._size - 1].second; + } + } + + /** + * 添字演算子[key]のオーバーロード(set) + * @param key 添字から特定されるValue値 + * @param value 代入するValue値 + */ + public setValue(key: _KeyT, value: _ValT): void { + let found = -1; + + for (let i = 0; i < this._size; i++) { + if (this._keyValues[i].first == key) { + found = i; + break; + } + } + + if (found >= 0) { + this._keyValues[found].second = value; + } else { + this.appendKey(key); // 新規キーを追加 + this._keyValues[this._size - 1].second = value; + } + } + + /** + * 引数で渡したKeyを持つ要素が存在するか + * @param key 存在を確認するkey + * @return true 引数で渡したkeyを持つ要素が存在する + * @return false 引数で渡したkeyを持つ要素が存在しない + */ + public isExist(key: _KeyT): boolean { + for (let i = 0; i < this._size; i++) { + if (this._keyValues[i].first == key) { + return true; + } + } + return false; + } + + /** + * keyValueのポインタを全て解放する + */ + public clear(): void { + this._keyValues = void 0; + this._keyValues = null; + this._keyValues = []; + + this._size = 0; + } + + /** + * コンテナのサイズを取得する + * + * @return コンテナのサイズ + */ + public getSize(): number { + return this._size; + } + + /** + * コンテナのキャパシティを確保する + * @param newSize 新たなキャパシティ。引数の値が現在のサイズ未満の場合は何もしない。 + * @param fitToSize trueなら指定したサイズに合わせる。falseならサイズを2倍確保しておく。 + */ + public prepareCapacity(newSize: number, fitToSize: boolean): void { + if (newSize > this._keyValues.length) { + if (this._keyValues.length == 0) { + if (!fitToSize && newSize < csmMap.DefaultSize) + newSize = csmMap.DefaultSize; + this._keyValues.length = newSize; + } else { + if (!fitToSize && newSize < this._keyValues.length * 2) + newSize = this._keyValues.length * 2; + this._keyValues.length = newSize; + } + } + } + + /** + * コンテナの先頭要素を返す + */ + public begin(): iterator<_KeyT, _ValT> { + const ite: iterator<_KeyT, _ValT> = new iterator<_KeyT, _ValT>(this, 0); + return ite; + } + + /** + * コンテナの終端要素を返す + */ + public end(): iterator<_KeyT, _ValT> { + const ite: iterator<_KeyT, _ValT> = new iterator<_KeyT, _ValT>( + this, + this._size + ); // 終了 + return ite; + } + + /** + * コンテナから要素を削除する + * + * @param ite 削除する要素 + */ + public erase(ite: iterator<_KeyT, _ValT>): iterator<_KeyT, _ValT> { + const index: number = ite._index; + if (index < 0 || this._size <= index) { + return ite; // 削除範囲外 + } + + // 削除 + this._keyValues.splice(index, 1); + --this._size; + + const ite2: iterator<_KeyT, _ValT> = new iterator<_KeyT, _ValT>( + this, + index + ); // 終了 + return ite2; + } + + /** + * コンテナの値を32ビット符号付き整数型でダンプする + */ + public dumpAsInt() { + for (let i = 0; i < this._size; i++) { + CubismLogDebug('{0} ,', this._keyValues[i]); + CubismLogDebug('\n'); + } + } + + public static readonly DefaultSize = 10; // コンテナの初期化のデフォルトサイズ + public _keyValues: csmPair<_KeyT, _ValT>[]; // key-valueペアの配列 + public _dummyValue: _ValT; // 空の値を返す為のダミー + public _size: number; // コンテナの要素数 +} + +/** + * csmMapのイテレータ + */ +export class iterator<_KeyT, _ValT> { + /** + * コンストラクタ + */ + constructor(v?: csmMap<_KeyT, _ValT>, idx?: number) { + this._map = v != undefined ? v : new csmMap<_KeyT, _ValT>(); + + this._index = idx != undefined ? idx : 0; + } + + /** + * =演算子のオーバーロード + */ + public set(ite: iterator<_KeyT, _ValT>): iterator<_KeyT, _ValT> { + this._index = ite._index; + this._map = ite._map; + return this; + } + + /** + * 前置き++演算子のオーバーロード + */ + public preIncrement(): iterator<_KeyT, _ValT> { + ++this._index; + return this; + } + + /** + * 前置き--演算子のオーバーロード + */ + public preDecrement(): iterator<_KeyT, _ValT> { + --this._index; + return this; + } + + /** + * 後置き++演算子のオーバーロード + */ + public increment(): iterator<_KeyT, _ValT> { + const iteold = new iterator<_KeyT, _ValT>(this._map, this._index++); // 古い値を保存 + return iteold; + } + + /** + * 後置き--演算子のオーバーロード + */ + public decrement(): iterator<_KeyT, _ValT> { + const iteold = new iterator<_KeyT, _ValT>(this._map, this._index); // 古い値を保存 + this._map = iteold._map; + this._index = iteold._index; + return this; + } + + /** + * *演算子のオーバーロード + */ + public ptr(): csmPair<_KeyT, _ValT> { + return this._map._keyValues[this._index]; + } + + /** + * !=演算 + */ + public notEqual(ite: iterator<_KeyT, _ValT>): boolean { + return this._index != ite._index || this._map != ite._map; + } + + _index: number; // コンテナのインデックス値 + _map: csmMap<_KeyT, _ValT>; // コンテナ +} + +// Namespace definition for compatibility. +import * as $ from './csmmap'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const csmMap = $.csmMap; + export type csmMap = $.csmMap; + export const csmPair = $.csmPair; + export type csmPair = $.csmPair; + export const iterator = $.iterator; + export type iterator = $.iterator; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmrectf.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmrectf.ts new file mode 100644 index 000000000..074d72787 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmrectf.ts @@ -0,0 +1,89 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * 矩形形状(座標・長さはfloat値)を定義するクラス + */ +export class csmRect { + /** + * コンストラクタ + * @param x 左端X座標 + * @param y 上端Y座標 + * @param w 幅 + * @param h 高さ + */ + public constructor(x?: number, y?: number, w?: number, h?: number) { + this.x = x; + this.y = y; + this.width = w; + this.height = h; + } + + /** + * 矩形中央のX座標を取得する + */ + public getCenterX(): number { + return this.x + 0.5 * this.width; + } + + /** + * 矩形中央のY座標を取得する + */ + public getCenterY(): number { + return this.y + 0.5 * this.height; + } + + /** + * 右側のX座標を取得する + */ + public getRight(): number { + return this.x + this.width; + } + + /** + * 下端のY座標を取得する + */ + public getBottom(): number { + return this.y + this.height; + } + + /** + * 矩形に値をセットする + * @param r 矩形のインスタンス + */ + public setRect(r: csmRect): void { + this.x = r.x; + this.y = r.y; + this.width = r.width; + this.height = r.height; + } + + /** + * 矩形中央を軸にして縦横を拡縮する + * @param w 幅方向に拡縮する量 + * @param h 高さ方向に拡縮する量 + */ + public expand(w: number, h: number) { + this.x -= w; + this.y -= h; + this.width += w * 2.0; + this.height += h * 2.0; + } + + public x: number; // 左端X座標 + public y: number; // 上端Y座標 + public width: number; // 幅 + public height: number; // 高さ +} + +// Namespace definition for compatibility. +import * as $ from './csmrectf'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const csmRect = $.csmRect; + export type csmRect = $.csmRect; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmstring.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmstring.ts new file mode 100644 index 000000000..df735e9f8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmstring.ts @@ -0,0 +1,107 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * 文字列クラス。 + */ +export class csmString { + /** + * 文字列を後方に追加する + * + * @param c 追加する文字列 + * @return 更新された文字列 + */ + public append(c: string, length?: number): csmString { + this.s += length !== undefined ? c.substr(0, length) : c; + + return this; + } + + /** + * 文字サイズを拡張して文字を埋める + * @param length 拡張する文字数 + * @param v 埋める文字 + * @return 更新された文字列 + */ + public expansion(length: number, v: string): csmString { + for (let i = 0; i < length; i++) { + this.append(v); + } + + return this; + } + + /** + * 文字列の長さをバイト数で取得する + */ + public getBytes(): number { + return encodeURIComponent(this.s).replace(/%../g, 'x').length; + } + + /** + * 文字列の長さを返す + */ + public getLength(): number { + return this.s.length; + } + + /** + * 文字列比較 < + * @param s 比較する文字列 + * @return true: 比較する文字列より小さい + * @return false: 比較する文字列より大きい + */ + public isLess(s: csmString): boolean { + return this.s < s.s; + } + + /** + * 文字列比較 > + * @param s 比較する文字列 + * @return true: 比較する文字列より大きい + * @return false: 比較する文字列より小さい + */ + public isGreat(s: csmString): boolean { + return this.s > s.s; + } + + /** + * 文字列比較 == + * @param s 比較する文字列 + * @return true: 比較する文字列と等しい + * @return false: 比較する文字列と異なる + */ + public isEqual(s: string): boolean { + return this.s == s; + } + + /** + * 文字列が空かどうか + * @return true: 空の文字列 + * @return false: 値が設定されている + */ + public isEmpty(): boolean { + return this.s.length == 0; + } + + /** + * 引数付きコンストラクタ + */ + public constructor(s: string) { + this.s = s; + } + + s: string; +} + +// Namespace definition for compatibility. +import * as $ from './csmstring'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const csmString = $.csmString; + export type csmString = $.csmString; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmvector.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmvector.ts new file mode 100644 index 000000000..a054f87ee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/type/csmvector.ts @@ -0,0 +1,352 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +/** + * ベクター型(可変配列型) + */ +export class csmVector { + /** + * 引数付きコンストラクタ + * @param iniitalCapacity 初期化後のキャパシティ。データサイズは_capacity * sizeof(T) + * @param zeroClear trueなら初期化時に確保した領域を0で埋める + */ + constructor(initialCapacity = 0) { + if (initialCapacity < 1) { + this._ptr = []; + this._capacity = 0; + this._size = 0; + } else { + this._ptr = new Array(initialCapacity); + this._capacity = initialCapacity; + this._size = 0; + } + } + + /** + * インデックスで指定した要素を返す + */ + public at(index: number): T { + return this._ptr[index]; + } + + /** + * 要素をセット + * @param index 要素をセットするインデックス + * @param value セットする要素 + */ + public set(index: number, value: T): void { + this._ptr[index] = value; + } + + /** + * コンテナを取得する + */ + public get(offset = 0): T[] { + const ret: T[] = new Array(); + for (let i = offset; i < this._size; i++) { + ret.push(this._ptr[i]); + } + return ret; + } + + /** + * pushBack処理、コンテナに新たな要素を追加する + * @param value PushBack処理で追加する値 + */ + public pushBack(value: T): void { + if (this._size >= this._capacity) { + this.prepareCapacity( + this._capacity == 0 ? csmVector.s_defaultSize : this._capacity * 2 + ); + } + + this._ptr[this._size++] = value; + } + + /** + * コンテナの全要素を解放する + */ + public clear(): void { + this._ptr.length = 0; + this._size = 0; + } + + /** + * コンテナの要素数を返す + * @return コンテナの要素数 + */ + public getSize(): number { + return this._size; + } + + /** + * コンテナの全要素に対して代入処理を行う + * @param newSize 代入処理後のサイズ + * @param value 要素に代入する値 + */ + public assign(newSize: number, value: T): void { + const curSize = this._size; + + if (curSize < newSize) { + this.prepareCapacity(newSize); // capacity更新 + } + + for (let i = 0; i < newSize; i++) { + this._ptr[i] = value; + } + + this._size = newSize; + } + + /** + * サイズ変更 + */ + public resize(newSize: number, value: T = null): void { + this.updateSize(newSize, value, true); + } + + /** + * サイズ変更 + */ + public updateSize( + newSize: number, + value: any = null, + callPlacementNew = true + ): void { + const curSize: number = this._size; + + if (curSize < newSize) { + this.prepareCapacity(newSize); // capacity更新 + + if (callPlacementNew) { + for (let i: number = this._size; i < newSize; i++) { + if (typeof value == 'function') { + // new + this._ptr[i] = JSON.parse(JSON.stringify(new value())); + } // プリミティブ型なので値渡し + else { + this._ptr[i] = value; + } + } + } else { + for (let i: number = this._size; i < newSize; i++) { + this._ptr[i] = value; + } + } + } else { + // newSize <= this._size + //--- + const sub = this._size - newSize; + this._ptr.splice(this._size - sub, sub); // 不要なので破棄する + } + this._size = newSize; + } + + /** + * コンテナにコンテナ要素を挿入する + * @param position 挿入する位置 + * @param begin 挿入するコンテナの開始位置 + * @param end 挿入するコンテナの終端位置 + */ + public insert( + position: iterator, + begin: iterator, + end: iterator + ): void { + let dstSi: number = position._index; + const srcSi: number = begin._index; + const srcEi: number = end._index; + + const addCount: number = srcEi - srcSi; + + this.prepareCapacity(this._size + addCount); + + // 挿入用の既存データをシフトして隙間を作る + const addSize = this._size - dstSi; + if (addSize > 0) { + for (let i = 0; i < addSize; i++) { + this._ptr.splice(dstSi + i, 0, null); + } + } + + for (let i: number = srcSi; i < srcEi; i++, dstSi++) { + this._ptr[dstSi] = begin._vector._ptr[i]; + } + + this._size = this._size + addCount; + } + + /** + * コンテナからインデックスで指定した要素を削除する + * @param index インデックス値 + * @return true 削除実行 + * @return false 削除範囲外 + */ + public remove(index: number): boolean { + if (index < 0 || this._size <= index) { + return false; // 削除範囲外 + } + + this._ptr.splice(index, 1); + --this._size; + + return true; + } + + /** + * コンテナから要素を削除して他の要素をシフトする + * @param ite 削除する要素 + */ + public erase(ite: iterator): iterator { + const index: number = ite._index; + if (index < 0 || this._size <= index) { + return ite; // 削除範囲外 + } + + // 削除 + this._ptr.splice(index, 1); + --this._size; + + const ite2: iterator = new iterator(this, index); // 終了 + return ite2; + } + + /** + * コンテナのキャパシティを確保する + * @param newSize 新たなキャパシティ。引数の値が現在のサイズ未満の場合は何もしない. + */ + public prepareCapacity(newSize: number): void { + if (newSize > this._capacity) { + if (this._capacity == 0) { + this._ptr = new Array(newSize); + this._capacity = newSize; + } else { + this._ptr.length = newSize; + this._capacity = newSize; + } + } + } + + /** + * コンテナの先頭要素を返す + */ + public begin(): iterator { + const ite: iterator = + this._size == 0 ? this.end() : new iterator(this, 0); + return ite; + } + + /** + * コンテナの終端要素を返す + */ + public end(): iterator { + const ite: iterator = new iterator(this, this._size); + return ite; + } + + public getOffset(offset: number): csmVector { + const newVector = new csmVector(); + newVector._ptr = this.get(offset); + newVector._size = this.get(offset).length; + newVector._capacity = this.get(offset).length; + + return newVector; + } + + _ptr: T[]; // コンテナの先頭アドレス + _size: number; // コンテナの要素数 + _capacity: number; // コンテナのキャパシティ + + static readonly s_defaultSize = 10; // コンテナ初期化のデフォルトサイズ +} + +export class iterator { + /** + * コンストラクタ + */ + public constructor(v?: csmVector, index?: number) { + this._vector = v != undefined ? v : null; + this._index = index != undefined ? index : 0; + } + + /** + * 代入 + */ + public set(ite: iterator): iterator { + this._index = ite._index; + this._vector = ite._vector; + return this; + } + + /** + * 前置き++演算 + */ + public preIncrement(): iterator { + ++this._index; + return this; + } + + /** + * 前置き--演算 + */ + public preDecrement(): iterator { + --this._index; + return this; + } + + /** + * 後置き++演算子 + */ + public increment(): iterator { + const iteold = new iterator(this._vector, this._index++); // 古い値を保存 + return iteold; + } + + /** + * 後置き--演算子 + */ + public decrement(): iterator { + const iteold = new iterator(this._vector, this._index--); // 古い値を保存 + return iteold; + } + + /** + * ptr + */ + public ptr(): T { + return this._vector._ptr[this._index]; + } + + /** + * =演算子のオーバーロード + */ + public substitution(ite: iterator): iterator { + this._index = ite._index; + this._vector = ite._vector; + return this; + } + + /** + * !=演算子のオーバーロード + */ + public notEqual(ite: iterator): boolean { + return this._index != ite._index || this._vector != ite._vector; + } + + _index: number; // コンテナのインデックス値 + _vector: csmVector; // コンテナ +} + +// Namespace definition for compatibility. +import * as $ from './csmvector'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const csmVector = $.csmVector; + export type csmVector = $.csmVector; + export const iterator = $.iterator; + export type iterator = $.iterator; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/utils/cubismdebug.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/utils/cubismdebug.ts new file mode 100644 index 000000000..4d6f1433a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/utils/cubismdebug.ts @@ -0,0 +1,162 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { + CSM_LOG_LEVEL, + CSM_LOG_LEVEL_DEBUG, + CSM_LOG_LEVEL_ERROR, + CSM_LOG_LEVEL_INFO, + CSM_LOG_LEVEL_VERBOSE, + CSM_LOG_LEVEL_WARNING +} from '../cubismframeworkconfig'; +import { CubismFramework, LogLevel } from '../live2dcubismframework'; + +export const CubismLogPrint = (level: LogLevel, fmt: string, args: any[]) => { + CubismDebug.print(level, '[CSM]' + fmt, args); +}; + +export const CubismLogPrintIn = (level: LogLevel, fmt: string, args: any[]) => { + CubismLogPrint(level, fmt + '\n', args); +}; + +export const CSM_ASSERT = (expr: any) => { + console.assert(expr); +}; + +export let CubismLogVerbose: (fmt: string, ...args: any[]) => void; +export let CubismLogDebug: (fmt: string, ...args: any[]) => void; +export let CubismLogInfo: (fmt: string, ...args: any[]) => void; +export let CubismLogWarning: (fmt: string, ...args: any[]) => void; +export let CubismLogError: (fmt: string, ...args: any[]) => void; + +if (CSM_LOG_LEVEL <= CSM_LOG_LEVEL_VERBOSE) { + CubismLogVerbose = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Verbose, '[V]' + fmt, args); + }; + + CubismLogDebug = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Debug, '[D]' + fmt, args); + }; + + CubismLogInfo = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Info, '[I]' + fmt, args); + }; + + CubismLogWarning = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Warning, '[W]' + fmt, args); + }; + + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} else if (CSM_LOG_LEVEL == CSM_LOG_LEVEL_DEBUG) { + CubismLogDebug = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Debug, '[D]' + fmt, args); + }; + + CubismLogInfo = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Info, '[I]' + fmt, args); + }; + + CubismLogWarning = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Warning, '[W]' + fmt, args); + }; + + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} else if (CSM_LOG_LEVEL == CSM_LOG_LEVEL_INFO) { + CubismLogInfo = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Info, '[I]' + fmt, args); + }; + + CubismLogWarning = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Warning, '[W]' + fmt, args); + }; + + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} else if (CSM_LOG_LEVEL == CSM_LOG_LEVEL_WARNING) { + CubismLogWarning = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Warning, '[W]' + fmt, args); + }; + + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} else if (CSM_LOG_LEVEL == CSM_LOG_LEVEL_ERROR) { + CubismLogError = (fmt: string, ...args: any[]) => { + CubismLogPrintIn(LogLevel.LogLevel_Error, '[E]' + fmt, args); + }; +} + +/** + * デバッグ用のユーティリティクラス。 + * ログの出力、バイトのダンプなど + */ +export class CubismDebug { + /** + * ログを出力する。第一引数にログレベルを設定する。 + * CubismFramework.initialize()時にオプションで設定されたログ出力レベルを下回る場合はログに出さない。 + * + * @param logLevel ログレベルの設定 + * @param format 書式付き文字列 + * @param args 可変長引数 + */ + public static print(logLevel: LogLevel, format: string, args?: any[]): void { + // オプションで設定されたログ出力レベルを下回る場合はログに出さない + if (logLevel < CubismFramework.getLoggingLevel()) { + return; + } + + const logPrint: Live2DCubismCore.csmLogFunction = + CubismFramework.coreLogFunction; + + if (!logPrint) return; + + const buffer: string = format.replace(/\{(\d+)\}/g, (m, k) => { + return args[k]; + }); + logPrint(buffer); + } + + /** + * データから指定した長さだけダンプ出力する。 + * CubismFramework.initialize()時にオプションで設定されたログ出力レベルを下回る場合はログに出さない。 + * + * @param logLevel ログレベルの設定 + * @param data ダンプするデータ + * @param length ダンプする長さ + */ + public static dumpBytes( + logLevel: LogLevel, + data: Uint8Array, + length: number + ): void { + for (let i = 0; i < length; i++) { + if (i % 16 == 0 && i > 0) this.print(logLevel, '\n'); + else if (i % 8 == 0 && i > 0) this.print(logLevel, ' '); + this.print(logLevel, '{0} ', [data[i] & 0xff]); + } + + this.print(logLevel, '\n'); + } + + /** + * private コンストラクタ + */ + private constructor() {} +} + +// Namespace definition for compatibility. +import * as $ from './cubismdebug'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismDebug = $.CubismDebug; + export type CubismDebug = $.CubismDebug; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/utils/cubismjson.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/utils/cubismjson.ts new file mode 100644 index 000000000..d8097a0cb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/utils/cubismjson.ts @@ -0,0 +1,1253 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +import { strtod } from '../live2dcubismframework'; +import { csmMap, iterator as csmMap_iterator } from '../type/csmmap'; +import { csmString } from '../type/csmstring'; +import { csmVector, iterator as csmVector_iterator } from '../type/csmvector'; +import { CubismLogInfo } from './cubismdebug'; + +// StaticInitializeNotForClientCall()で初期化する +const CSM_JSON_ERROR_TYPE_MISMATCH = 'Error: type mismatch'; +const CSM_JSON_ERROR_INDEX_OF_BOUNDS = 'Error: index out of bounds'; + +/** + * パースしたJSONエレメントの要素の基底クラス。 + */ +export abstract class Value { + /** + * コンストラクタ + */ + public constructor() {} + + /** + * 要素を文字列型で返す(csmString型) + */ + public abstract getString(defaultValue?: string, indent?: string): string; + + /** + * 要素を文字列型で返す(string) + */ + public getRawString(defaultValue?: string, indent?: string): string { + return this.getString(defaultValue, indent); + } + + /** + * 要素を数値型で返す(number) + */ + public toInt(defaultValue = 0): number { + return defaultValue; + } + + /** + * 要素を数値型で返す(number) + */ + public toFloat(defaultValue = 0): number { + return defaultValue; + } + + /** + * 要素を真偽値で返す(boolean) + */ + public toBoolean(defaultValue = false): boolean { + return defaultValue; + } + + /** + * サイズを返す + */ + public getSize(): number { + return 0; + } + + /** + * 要素を配列で返す(Value[]) + */ + public getArray(defaultValue: Value[] = null): Value[] { + return defaultValue; + } + + /** + * 要素をコンテナで返す(array) + */ + public getVector(defaultValue = new csmVector()): csmVector { + return defaultValue; + } + + /** + * 要素をマップで返す(csmMap) + */ + public getMap(defaultValue?: csmMap): csmMap { + return defaultValue; + } + + /** + * 添字演算子[index] + */ + public getValueByIndex(index: number): Value { + return Value.errorValue.setErrorNotForClientCall( + CSM_JSON_ERROR_TYPE_MISMATCH + ); + } + + /** + * 添字演算子[string | csmString] + */ + public getValueByString(s: string | csmString): Value { + return Value.nullValue.setErrorNotForClientCall( + CSM_JSON_ERROR_TYPE_MISMATCH + ); + } + + /** + * マップのキー一覧をコンテナで返す + * + * @return マップのキーの一覧 + */ + public getKeys(): csmVector { + return Value.s_dummyKeys; + } + + /** + * Valueの種類がエラー値ならtrue + */ + public isError(): boolean { + return false; + } + + /** + * Valueの種類がnullならtrue + */ + public isNull(): boolean { + return false; + } + + /** + * Valueの種類が真偽値ならtrue + */ + public isBool(): boolean { + return false; + } + + /** + * Valueの種類が数値型ならtrue + */ + public isFloat(): boolean { + return false; + } + + /** + * Valueの種類が文字列ならtrue + */ + public isString(): boolean { + return false; + } + + /** + * Valueの種類が配列ならtrue + */ + public isArray(): boolean { + return false; + } + + /** + * Valueの種類がマップ型ならtrue + */ + public isMap(): boolean { + return false; + } + + /** + * 引数の値と等しければtrue + */ + public equals(value: csmString): boolean; + public equals(value: string): boolean; + public equals(value: number): boolean; + public equals(value: boolean): boolean; + public equals(value: any): boolean { + return false; + } + + /** + * Valueの値が静的ならtrue、静的なら解放しない + */ + public isStatic(): boolean { + return false; + } + + /** + * Valueにエラー値をセットする + */ + public setErrorNotForClientCall(errorStr: string): Value { + return JsonError.errorValue; + } + + /** + * 初期化用メソッド + */ + public static staticInitializeNotForClientCall(): void { + JsonBoolean.trueValue = new JsonBoolean(true); + JsonBoolean.falseValue = new JsonBoolean(false); + Value.errorValue = new JsonError('ERROR', true); + Value.nullValue = new JsonNullvalue(); + Value.s_dummyKeys = new csmVector(); + } + + /** + * リリース用メソッド + */ + public static staticReleaseNotForClientCall(): void { + JsonBoolean.trueValue = null; + JsonBoolean.falseValue = null; + Value.errorValue = null; + Value.nullValue = null; + Value.s_dummyKeys = null; + } + + protected _stringBuffer: string; // 文字列バッファ + + private static s_dummyKeys: csmVector; // ダミーキー + + public static errorValue: Value; // 一時的な返り値として返すエラー。 CubismFramework::Disposeするまではdeleteしない + public static nullValue: Value; // 一時的な返り値として返すNULL。 CubismFramework::Disposeするまではdeleteしない +} + +/** + * Ascii文字のみ対応した最小限の軽量JSONパーサ。 + * 仕様はJSONのサブセットとなる。 + * 設定ファイル(model3.json)などのロード用 + * + * [未対応項目] + * ・日本語などの非ASCII文字 + * ・eによる指数表現 + */ +export class CubismJson { + /** + * コンストラクタ + */ + public constructor(buffer?: ArrayBuffer, length?: number) { + this._error = null; + this._lineCount = 0; + this._root = null; + + if (buffer != undefined) { + this.parseBytes(buffer, length); + } + } + + /** + * バイトデータから直接ロードしてパースする + * + * @param buffer バッファ + * @param size バッファサイズ + * @return CubismJsonクラスのインスタンス。失敗したらNULL + */ + public static create(buffer: ArrayBuffer, size: number) { + const json = new CubismJson(); + const succeeded: boolean = json.parseBytes(buffer, size); + + if (!succeeded) { + CubismJson.delete(json); + return null; + } else { + return json; + } + } + + /** + * パースしたJSONオブジェクトの解放処理 + * + * @param instance CubismJsonクラスのインスタンス + */ + public static delete(instance: CubismJson) { + instance = null; + } + + /** + * パースしたJSONのルート要素を返す + */ + public getRoot(): Value { + return this._root; + } + + /** + * UnicodeのバイナリをStringに変換 + * + * @param buffer 変換するバイナリデータ + * @return 変換後の文字列 + */ + public arrayBufferToString(buffer: ArrayBuffer): string { + const uint8Array: Uint8Array = new Uint8Array(buffer); + let str = ''; + + for (let i = 0, len: number = uint8Array.length; i < len; ++i) { + str += '%' + this.pad(uint8Array[i].toString(16)); + } + + str = decodeURIComponent(str); + return str; + } + + /** + * エンコード、パディング + */ + private pad(n: string): string { + return n.length < 2 ? '0' + n : n; + } + + /** + * JSONのパースを実行する + * @param buffer パース対象のデータバイト + * @param size データバイトのサイズ + * return true : 成功 + * return false: 失敗 + */ + public parseBytes(buffer: ArrayBuffer, size: number): boolean { + const endPos: number[] = new Array(1); // 参照渡しにするため配列 + const decodeBuffer: string = this.arrayBufferToString(buffer); + this._root = this.parseValue(decodeBuffer, size, 0, endPos); + + if (this._error) { + let strbuf = '\0'; + strbuf = 'Json parse error : @line ' + (this._lineCount + 1) + '\n'; + this._root = new JsonString(strbuf); + + CubismLogInfo('{0}', this._root.getRawString()); + return false; + } else if (this._root == null) { + this._root = new JsonError(new csmString(this._error), false); // rootは解放されるのでエラーオブジェクトを別途作成する + return false; + } + return true; + } + + /** + * パース時のエラー値を返す + */ + public getParseError(): string { + return this._error; + } + + /** + * ルート要素の次の要素がファイルの終端だったらtrueを返す + */ + public checkEndOfFile(): boolean { + return this._root.getArray()[1].equals('EOF'); + } + + /** + * JSONエレメントからValue(float,String,Value*,Array,null,true,false)をパースする + * エレメントの書式に応じて内部でParseString(), ParseObject(), ParseArray()を呼ぶ + * + * @param buffer JSONエレメントのバッファ + * @param length パースする長さ + * @param begin パースを開始する位置 + * @param outEndPos パース終了時の位置 + * @return パースから取得したValueオブジェクト + */ + protected parseValue( + buffer: string, + length: number, + begin: number, + outEndPos: number[] + ) { + if (this._error) return null; + + let o: Value = null; + let i: number = begin; + let f: number; + + for (; i < length; i++) { + const c: string = buffer[i]; + switch (c) { + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + const afterString: string[] = new Array(1); // 参照渡しにするため + f = strtod(buffer.slice(i), afterString); + outEndPos[0] = buffer.indexOf(afterString[0]); + return new JsonFloat(f); + } + case '"': + return new JsonString( + this.parseString(buffer, length, i + 1, outEndPos) + ); // \"の次の文字から + case '[': + o = this.parseArray(buffer, length, i + 1, outEndPos); + return o; + case '{': + o = this.parseObject(buffer, length, i + 1, outEndPos); + return o; + case 'n': // null以外にない + if (i + 3 < length) { + o = new JsonNullvalue(); // 解放できるようにする + outEndPos[0] = i + 4; + } else { + this._error = 'parse null'; + } + return o; + case 't': // true以外にない + if (i + 3 < length) { + o = JsonBoolean.trueValue; + outEndPos[0] = i + 4; + } else { + this._error = 'parse true'; + } + return o; + case 'f': // false以外にない + if (i + 4 < length) { + o = JsonBoolean.falseValue; + outEndPos[0] = i + 5; + } else { + this._error = "illegal ',' position"; + } + return o; + case ',': // Array separator + this._error = "illegal ',' position"; + return null; + case ']': // 不正な}だがスキップする。配列の最後に不要な , があると思われる + outEndPos[0] = i; // 同じ文字を再処理 + return null; + case '\n': + this._lineCount++; + case ' ': + case '\t': + case '\r': + default: + // スキップ + break; + } + } + + this._error = 'illegal end of value'; + return null; + } + + /** + * 次の「"」までの文字列をパースする。 + * + * @param string -> パース対象の文字列 + * @param length -> パースする長さ + * @param begin -> パースを開始する位置 + * @param outEndPos -> パース終了時の位置 + * @return パースした文F字列要素 + */ + protected parseString( + string: string, + length: number, + begin: number, + outEndPos: number[] + ): string { + if (this._error) return null; + + let i = begin; + let c: string, c2: string; + const ret: csmString = new csmString(''); + let bufStart: number = begin; // sbufに登録されていない文字の開始位置 + + for (; i < length; i++) { + c = string[i]; + + switch (c) { + case '"': { + // 終端の”、エスケープ文字は別に処理されるのでここに来ない + outEndPos[0] = i + 1; // ”の次の文字 + ret.append(string.slice(bufStart), i - bufStart); // 前の文字までを登録する + return ret.s; + } + case '//': { + // エスケープの場合 + i++; // 2文字をセットで扱う + + if (i - 1 > bufStart) { + ret.append(string.slice(bufStart), i - bufStart); // 前の文字までを登録する + } + bufStart = i + 1; // エスケープ(2文字)の次の文字から + + if (i < length) { + c2 = string[i]; + + switch (c2) { + case '\\': + ret.expansion(1, '\\'); + break; + case '"': + ret.expansion(1, '"'); + break; + case '/': + ret.expansion(1, '/'); + break; + case 'b': + ret.expansion(1, '\b'); + break; + case 'f': + ret.expansion(1, '\f'); + break; + case 'n': + ret.expansion(1, '\n'); + break; + case 'r': + ret.expansion(1, '\r'); + break; + case 't': + ret.expansion(1, '\t'); + break; + case 'u': + this._error = 'parse string/unicord escape not supported'; + break; + default: + break; + } + } else { + this._error = 'parse string/escape error'; + } + } + default: { + break; + } + } + } + + this._error = 'parse string/illegal end'; + return null; + } + + /** + * JSONのオブジェクトエレメントをパースしてValueオブジェクトを返す + * + * @param buffer JSONエレメントのバッファ + * @param length パースする長さ + * @param begin パースを開始する位置 + * @param outEndPos パース終了時の位置 + * @return パースから取得したValueオブジェクト + */ + protected parseObject( + buffer: string, + length: number, + begin: number, + outEndPos: number[] + ): Value { + if (this._error) return null; + const ret: JsonMap = new JsonMap(); + + // Key: Value + let key = ''; + let i: number = begin; + let c = ''; + const localRetEndPos2: number[] = Array(1); + let ok = false; + + // , が続く限りループ + for (; i < length; i++) { + FOR_LOOP: for (; i < length; i++) { + c = buffer[i]; + + switch (c) { + case '"': + key = this.parseString(buffer, length, i + 1, localRetEndPos2); + if (this._error) { + return null; + } + + i = localRetEndPos2[0]; + ok = true; + break FOR_LOOP; //-- loopから出る + case '}': // 閉じカッコ + outEndPos[0] = i + 1; + return ret; // 空 + case ':': + this._error = "illegal ':' position"; + break; + case '\n': + this._lineCount++; + default: + break; // スキップする文字 + } + } + if (!ok) { + this._error = 'key not found'; + return null; + } + + ok = false; + + // : をチェック + FOR_LOOP2: for (; i < length; i++) { + c = buffer[i]; + + switch (c) { + case ':': + ok = true; + i++; + break FOR_LOOP2; + case '}': + this._error = "illegal '}' position"; + break; + case '\n': + this._lineCount++; + // case ' ': case '\t' : case '\r': + default: + break; // スキップする文字 + } + } + + if (!ok) { + this._error = "':' not found"; + return null; + } + + // 値をチェック + const value: Value = this.parseValue(buffer, length, i, localRetEndPos2); + if (this._error) { + return null; + } + + i = localRetEndPos2[0]; + + // ret.put(key, value); + ret.put(key, value); + + FOR_LOOP3: for (; i < length; i++) { + c = buffer[i]; + + switch (c) { + case ',': + break FOR_LOOP3; + case '}': + outEndPos[0] = i + 1; + return ret; // 正常終了 + case '\n': + this._lineCount++; + default: + break; // スキップ + } + } + } + + this._error = 'illegal end of perseObject'; + return null; + } + + /** + * 次の「"」までの文字列をパースする。 + * @param buffer JSONエレメントのバッファ + * @param length パースする長さ + * @param begin パースを開始する位置 + * @param outEndPos パース終了時の位置 + * @return パースから取得したValueオブジェクト + */ + protected parseArray( + buffer: string, + length: number, + begin: number, + outEndPos: number[] + ): Value { + if (this._error) return null; + let ret: JsonArray = new JsonArray(); + + // key : value + let i: number = begin; + let c: string; + const localRetEndpos2: number[] = new Array(1); + + // , が続く限りループ + for (; i < length; i++) { + // : をチェック + const value: Value = this.parseValue(buffer, length, i, localRetEndpos2); + + if (this._error) { + return null; + } + i = localRetEndpos2[0]; + + if (value) { + ret.add(value); + } + + // FOR_LOOP3: + // boolean breakflag = false; + FOR_LOOP: for (; i < length; i++) { + c = buffer[i]; + + switch (c) { + case ',': + // breakflag = true; + // break; // 次のKEY, VAlUEへ + break FOR_LOOP; + case ']': + outEndPos[0] = i + 1; + return ret; // 終了 + case '\n': + ++this._lineCount; + //case ' ': case '\t': case '\r': + default: + break; // スキップ + } + } + } + + ret = void 0; + this._error = 'illegal end of parseObject'; + return null; + } + + _error: string; // パース時のエラー + _lineCount: number; // エラー報告に用いる行数カウント + _root: Value; // パースされたルート要素 +} + +/** + * パースしたJSONの要素をfloat値として扱う + */ +export class JsonFloat extends Value { + /** + * コンストラクタ + */ + constructor(v: number) { + super(); + + this._value = v; + } + + /** + * Valueの種類が数値型ならtrue + */ + public isFloat(): boolean { + return true; + } + + /** + * 要素を文字列で返す(csmString型) + */ + public getString(defaultValue: string, indent: string): string { + const strbuf = '\0'; + this._value = parseFloat(strbuf); + this._stringBuffer = strbuf; + + return this._stringBuffer; + } + + /** + * 要素を数値型で返す(number) + */ + public toInt(defaultValue = 0): number { + return parseInt(this._value.toString()); + } + + /** + * 要素を数値型で返す(number) + */ + public toFloat(defaultValue = 0.0): number { + return this._value; + } + + /** + * 引数の値と等しければtrue + */ + public equals(value: csmString): boolean; + public equals(value: string): boolean; + public equals(value: number): boolean; + public equals(value: boolean): boolean; + public equals(value: any): boolean { + if ('number' === typeof value) { + // int + if (Math.round(value)) { + return false; + } + // float + else { + return value == this._value; + } + } + return false; + } + + private _value: number; // JSON要素の値 +} + +/** + * パースしたJSONの要素を真偽値として扱う + */ +export class JsonBoolean extends Value { + /** + * Valueの種類が真偽値ならtrue + */ + public isBool(): boolean { + return true; + } + + /** + * 要素を真偽値で返す(boolean) + */ + public toBoolean(defaultValue = false): boolean { + return this._boolValue; + } + + /** + * 要素を文字列で返す(csmString型) + */ + public getString(defaultValue: string, indent: string): string { + this._stringBuffer = this._boolValue ? 'true' : 'false'; + + return this._stringBuffer; + } + + /** + * 引数の値と等しければtrue + */ + public equals(value: csmString): boolean; + public equals(value: string): boolean; + public equals(value: number): boolean; + public equals(value: boolean): boolean; + public equals(value: any): boolean { + if ('boolean' === typeof value) { + return value == this._boolValue; + } + return false; + } + + /** + * Valueの値が静的ならtrue, 静的なら解放しない + */ + public isStatic(): boolean { + return true; + } + + /** + * 引数付きコンストラクタ + */ + public constructor(v: boolean) { + super(); + + this._boolValue = v; + } + + static trueValue: JsonBoolean; // true + static falseValue: JsonBoolean; // false + + private _boolValue: boolean; // JSON要素の値 +} + +/** + * パースしたJSONの要素を文字列として扱う + */ +export class JsonString extends Value { + /** + * 引数付きコンストラクタ + */ + public constructor(s: string); + public constructor(s: csmString); + public constructor(s: any) { + super(); + + if ('string' === typeof s) { + this._stringBuffer = s; + } + + if (s instanceof csmString) { + this._stringBuffer = s.s; + } + } + + /** + * Valueの種類が文字列ならtrue + */ + public isString(): boolean { + return true; + } + + /** + * 要素を文字列で返す(csmString型) + */ + public getString(defaultValue: string, indent: string): string { + return this._stringBuffer; + } + + /** + * 引数の値と等しければtrue + */ + public equals(value: csmString): boolean; + public equals(value: string): boolean; + public equals(value: number): boolean; + public equals(value: boolean): boolean; + public equals(value: any): boolean { + if ('string' === typeof value) { + return this._stringBuffer == value; + } + + if (value instanceof csmString) { + return this._stringBuffer == value.s; + } + + return false; + } +} + +/** + * JSONパース時のエラー結果。文字列型のようにふるまう + */ +export class JsonError extends JsonString { + /** + * Valueの値が静的ならtrue、静的なら解放しない + */ + public isStatic(): boolean { + return this._isStatic; + } + + /** + * エラー情報をセットする + */ + public setErrorNotForClientCall(s: string): Value { + this._stringBuffer = s; + return this; + } + + /** + * 引数付きコンストラクタ + */ + public constructor(s: csmString | string, isStatic: boolean) { + if ('string' === typeof s) { + super(s); + } else { + super(s); + } + this._isStatic = isStatic; + } + + /** + * Valueの種類がエラー値ならtrue + */ + public isError(): boolean { + return true; + } + + protected _isStatic: boolean; // 静的なValueかどうか +} + +/** + * パースしたJSONの要素をNULL値として持つ + */ +export class JsonNullvalue extends Value { + /** + * Valueの種類がNULL値ならtrue + */ + public isNull(): boolean { + return true; + } + + /** + * 要素を文字列で返す(csmString型) + */ + public getString(defaultValue: string, indent: string): string { + return this._stringBuffer; + } + + /** + * Valueの値が静的ならtrue, 静的なら解放しない + */ + public isStatic(): boolean { + return true; + } + + /** + * Valueにエラー値をセットする + */ + public setErrorNotForClientCall(s: string): Value { + this._stringBuffer = s; + return JsonError.nullValue; + } + + /** + * コンストラクタ + */ + public constructor() { + super(); + + this._stringBuffer = 'NullValue'; + } +} + +/** + * パースしたJSONの要素を配列として持つ + */ +export class JsonArray extends Value { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._array = new csmVector(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + for ( + let ite: csmVector_iterator = this._array.begin(); + ite.notEqual(this._array.end()); + ite.preIncrement() + ) { + let v: Value = ite.ptr(); + + if (v && !v.isStatic()) { + v = void 0; + v = null; + } + } + } + + /** + * Valueの種類が配列ならtrue + */ + public isArray(): boolean { + return true; + } + + /** + * 添字演算子[index] + */ + public getValueByIndex(index: number): Value { + if (index < 0 || this._array.getSize() <= index) { + return Value.errorValue.setErrorNotForClientCall( + CSM_JSON_ERROR_INDEX_OF_BOUNDS + ); + } + + const v: Value = this._array.at(index); + + if (v == null) { + return Value.nullValue; + } + + return v; + } + + /** + * 添字演算子[string | csmString] + */ + public getValueByString(s: string | csmString): Value { + return Value.errorValue.setErrorNotForClientCall( + CSM_JSON_ERROR_TYPE_MISMATCH + ); + } + + /** + * 要素を文字列で返す(csmString型) + */ + public getString(defaultValue: string, indent: string): string { + const stringBuffer: string = indent + '[\n'; + + for ( + let ite: csmVector_iterator = this._array.begin(); + ite.notEqual(this._array.end()); + ite.increment() + ) { + const v: Value = ite.ptr(); + this._stringBuffer += indent + '' + v.getString(indent + ' ') + '\n'; + } + + this._stringBuffer = stringBuffer + indent + ']\n'; + + return this._stringBuffer; + } + + /** + * 配列要素を追加する + * @param v 追加する要素 + */ + public add(v: Value): void { + this._array.pushBack(v); + } + + /** + * 要素をコンテナで返す(csmVector) + */ + public getVector(defaultValue: csmVector = null): csmVector { + return this._array; + } + + /** + * 要素の数を返す + */ + public getSize(): number { + return this._array.getSize(); + } + + private _array: csmVector; // JSON要素の値 +} + +/** + * パースしたJSONの要素をマップとして持つ + */ +export class JsonMap extends Value { + /** + * コンストラクタ + */ + public constructor() { + super(); + this._map = new csmMap(); + } + + /** + * デストラクタ相当の処理 + */ + public release(): void { + const ite: csmMap_iterator = this._map.begin(); + + while (ite.notEqual(this._map.end())) { + let v: Value = ite.ptr().second; + + if (v && !v.isStatic()) { + v = void 0; + v = null; + } + + ite.preIncrement(); + } + } + + /** + * Valueの値がMap型ならtrue + */ + public isMap(): boolean { + return true; + } + + /** + * 添字演算子[string | csmString] + */ + public getValueByString(s: string | csmString): Value { + if (s instanceof csmString) { + const ret: Value = this._map.getValue(s.s); + if (ret == null) { + return Value.nullValue; + } + return ret; + } + + for ( + let iter: csmMap_iterator = this._map.begin(); + iter.notEqual(this._map.end()); + iter.preIncrement() + ) { + if (iter.ptr().first == s) { + if (iter.ptr().second == null) { + return Value.nullValue; + } + return iter.ptr().second; + } + } + + return Value.nullValue; + } + + /** + * 添字演算子[index] + */ + public getValueByIndex(index: number): Value { + return Value.errorValue.setErrorNotForClientCall( + CSM_JSON_ERROR_TYPE_MISMATCH + ); + } + + /** + * 要素を文字列で返す(csmString型) + */ + public getString(defaultValue: string, indent: string) { + this._stringBuffer = indent + '{\n'; + + const ite: csmMap_iterator = this._map.begin(); + while (ite.notEqual(this._map.end())) { + const key = ite.ptr().first; + const v: Value = ite.ptr().second; + + this._stringBuffer += + indent + ' ' + key + ' : ' + v.getString(indent + ' ') + ' \n'; + ite.preIncrement(); + } + + this._stringBuffer += indent + '}\n'; + + return this._stringBuffer; + } + + /** + * 要素をMap型で返す + */ + public getMap(defaultValue?: csmMap): csmMap { + return this._map; + } + + /** + * Mapに要素を追加する + */ + public put(key: string, v: Value): void { + this._map.setValue(key, v); + } + + /** + * Mapからキーのリストを取得する + */ + public getKeys(): csmVector { + if (!this._keys) { + this._keys = new csmVector(); + + const ite: csmMap_iterator = this._map.begin(); + + while (ite.notEqual(this._map.end())) { + const key: string = ite.ptr().first; + this._keys.pushBack(key); + ite.preIncrement(); + } + } + return this._keys; + } + + /** + * Mapの要素数を取得する + */ + public getSize(): number { + return this._keys.getSize(); + } + + private _map: csmMap; // JSON要素の値 + private _keys: csmVector; // JSON要素の値 +} + +// Namespace definition for compatibility. +import * as $ from './cubismjson'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismJson = $.CubismJson; + export type CubismJson = $.CubismJson; + export const JsonArray = $.JsonArray; + export type JsonArray = $.JsonArray; + export const JsonBoolean = $.JsonBoolean; + export type JsonBoolean = $.JsonBoolean; + export const JsonError = $.JsonError; + export type JsonError = $.JsonError; + export const JsonFloat = $.JsonFloat; + export type JsonFloat = $.JsonFloat; + export const JsonMap = $.JsonMap; + export type JsonMap = $.JsonMap; + export const JsonNullvalue = $.JsonNullvalue; + export type JsonNullvalue = $.JsonNullvalue; + export const JsonString = $.JsonString; + export type JsonString = $.JsonString; + export const Value = $.Value; + export type Value = $.Value; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/utils/cubismstring.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/utils/cubismstring.ts new file mode 100644 index 000000000..eaeccd48c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/src/utils/cubismstring.ts @@ -0,0 +1,129 @@ +/** + * Copyright(c) Live2D Inc. All rights reserved. + * + * Use of this source code is governed by the Live2D Open Software license + * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. + */ + +export class CubismString { + /** + * 標準出力の書式を適用した文字列を取得する。 + * @param format 標準出力の書式指定文字列 + * @param ...args 書式指定文字列に渡す文字列 + * @return 書式を適用した文字列 + */ + public static getFormatedString(format: string, ...args: any[]): string { + const ret: string = format; + return ret.replace( + /\{(\d+)\}/g, + ( + m, + k // m="{0}", k="0" + ) => { + return args[k]; + } + ); + } + + /** + * textがstartWordで始まっているかどうかを返す + * @param test 検査対象の文字列 + * @param startWord 比較対象の文字列 + * @return true textがstartWordで始まっている + * @return false textがstartWordで始まっていない + */ + public static isStartWith(text: string, startWord: string): boolean { + let textIndex = 0; + let startWordIndex = 0; + while (startWord[startWordIndex] != '\0') { + if ( + text[textIndex] == '\0' || + text[textIndex++] != startWord[startWordIndex++] + ) { + return false; + } + } + return false; + } + + /** + * position位置の文字から数字を解析する。 + * + * @param string 文字列 + * @param length 文字列の長さ + * @param position 解析したい文字の位置 + * @param outEndPos 一文字も読み込まなかった場合はエラー値(-1)が入る + * @return 解析結果の数値 + */ + public static stringToFloat( + string: string, + length: number, + position: number, + outEndPos: number[] + ): number { + let i: number = position; + let minus = false; // マイナスフラグ + let period = false; + let v1 = 0; + + //負号の確認 + let c: number = parseInt(string[i]); + if (c < 0) { + minus = true; + i++; + } + + //整数部の確認 + for (; i < length; i++) { + const c = string[i]; + if (0 <= parseInt(c) && parseInt(c) <= 9) { + v1 = v1 * 10 + (parseInt(c) - 0); + } else if (c == '.') { + period = true; + i++; + break; + } else { + break; + } + } + + //小数部の確認 + if (period) { + let mul = 0.1; + for (; i < length; i++) { + c = parseFloat(string[i]) & 0xff; + if (0 <= c && c <= 9) { + v1 += mul * (c - 0); + } else { + break; + } + mul *= 0.1; //一桁下げる + if (!c) break; + } + } + + if (i == position) { + //一文字も読み込まなかった場合 + outEndPos[0] = -1; //エラー値が入るので呼び出し元で適切な処理を行う + return 0; + } + + if (minus) v1 = -v1; + + outEndPos[0] = i; + return v1; + } + + /** + * コンストラクタ呼び出し不可な静的クラスにする。 + */ + private constructor() {} +} + +// Namespace definition for compatibility. +import * as $ from './cubismstring'; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Live2DCubismFramework { + export const CubismString = $.CubismString; + export type CubismString = $.CubismString; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/tsconfig.json b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/tsconfig.json new file mode 100644 index 000000000..f9f9789b8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/framework/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es5", + "moduleResolution": "node", + "esModuleInterop": true, + "experimentalDecorators": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "emitDecoratorMetadata": true + }, + "include": [ + "src/**/*.ts", + "../Core/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Creator.js new file mode 100644 index 000000000..a515b49b8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Creator.js @@ -0,0 +1,15 @@ +import Live2dGameObject from './Live2dGameObject.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var key = GetAdvancedValue(config, 'key'); + var gameObject = new Live2dGameObject(this.scene, 0, 0, key); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Factory.js new file mode 100644 index 000000000..cf01bf5fd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Factory.js @@ -0,0 +1,7 @@ +import Live2dGameObject from './Live2dGameObject.js'; + +export default function (x, y, key, config) { + var gameObject = new Live2dGameObject(this.scene, x, y, key, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObject.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObject.d.ts new file mode 100644 index 000000000..753f68bbf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObject.d.ts @@ -0,0 +1,113 @@ +import Live2dGameObjectBase from './Live2dGameObjectBase'; + +export default Live2dGameObject; + +declare namespace Live2dGameObject { + interface IConfig { + autoPlayIdleMotion?: string + } + + type ParametersType = { [name: string]: number }; + + interface ILookAtConfig { + camera?: Phaser.Cameras.Scene2D.Camera; + + eyeBallX?: number, eyeBallY?: number, + angleX?: number, angleY?: number, angleZ?: number, + bodyAngleX?: number, + } + + type HitTestResultType = { [name: string]: boolean }; +} + +declare class Live2dGameObject extends Live2dGameObjectBase { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + key?: string, + config?: Live2dGameObject.IConfig + ); + + setModel( + key: string, + config?: Live2dGameObject.IConfig + ): this; + + // Expression + getExpressionNames(): string[]; + + setExpression(expressionName: string): this; + + expressionName: string; + + // Motion + getMotionNames(groupName?: string): string[]; + + getMotionGroupNames(): string[]; + + startMotion( + group: string, + no?: number | undefined, + priority?: 'none' | 'idle' | 'normal' | 'force' | 0 | 1 | 2 | 3 + ): this; + + stopAllMotions(): this; + + getPlayinigMotionNames(): string[]; + + isAnyMotionPlaying(): boolean; + + autoPlayIdleMotion(motionName: string): this; + + // Time-scale + setTimeScale(timeScale: number): this; + timeScale: number; + + // Parameter + registerParameter(name: string): this; + + addParameterValue( + name: string, + value: number + ): this; + + resetParameterValue(name: string): this + + getParameters(): Live2dGameObject.ParametersType; + + get params(): Live2dGameObject.ParametersType; + + lookAt( + x: number, + y: number, + config?: Live2dGameObject.ILookAtConfig + ): this; + + lookForward(config?: Live2dGameObject.ILookAtConfig): this; + + // LipSync + setLipSyncValue(value: number): this; + + lipSyncValue: number; + + // Hit test + getHitTestResult(): Live2dGameObject.HitTestResultType; + + get hitTestResult(): Live2dGameObject.HitTestResultType; + + hitTest( + hitAreaName: string, + worldX: number, + worldY: number, + camera?: Phaser.Cameras.Scene2D.Camera + ): boolean; + + // Position + getModelXY( + worldX: number, + worldY: number, + camera?: Phaser.Cameras.Scene2D.Camera, + out?: { x: number, y: number } + ): { x: number, y: number }; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObject.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObject.js new file mode 100644 index 000000000..3299f5b88 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObject.js @@ -0,0 +1,75 @@ +import Live2dGameObjectBase from './Live2dGameObjectBase.js'; +import Render from './render/Render.js'; +import Methods from './methods/Methods.js'; +import Model from './model/Model.js'; + +class Live2dGameObject extends Live2dGameObjectBase { + constructor(scene, x, y, key, config) { + super(scene, 'rexLive2d'); + + this.model = new Model(this); + + this.setModel(key, config); + this.setOrigin(0.5); + this.setPosition(x, y); + this.setTimeScale(1); + } + + preUpdate(time, delta) { + delta *= this.timeScale; + this.model.update(time, delta); + } + + preDestroy() { + this.model.release(); + this.model = undefined; + } + + get alpha() { + return super.alpha; + } + + set alpha(value) { + if (super.alpha === value) { + return; + } + super.alpha = value; + + this.model.setOpacity(value); + // But it won't change render result + // Only work for hitTest + } + + get expressionName() { + return this.model._currentExpressionName; + } + + set expressionName(expressionName) { + this.setExpression(expressionName); + } + + get params() { + return this.getParameters(); + } + + get lipSyncValue() { + return this.model._lipSyncValue; + } + + set lipSyncValue(value) { + this.setLipSyncValue(value); + } + + get hitTestResult() { + return this.getHitTestResult(); + } + +} + +Object.assign( + Live2dGameObject.prototype, + Render, + Methods, +) + +export default Live2dGameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObjectBase.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObjectBase.d.ts new file mode 100644 index 000000000..b3584b35a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObjectBase.d.ts @@ -0,0 +1,76 @@ +export default Live2dGameObjectBase; + +declare namespace Live2dGameObjectBase { + +} + +declare class Live2dGameObjectBase extends Phaser.GameObjects.GameObject { + + clearAlpha(): this; + setAlpha(value?: number): this; + alpha: number; + + blendMode: Phaser.BlendModes | string; + setBlendMode(value: string | Phaser.BlendModes): this; + + width: number; + height: number; + displayWidth: number; + displayHeight: number; + setSize(width: number, height: number): this; + setDisplaySize(width: number, height: number): this; + + depth: number; + setDepth(value: number): this; + + getCenter(output?: O): O; + getTopLeft(output?: O, includeParent?: boolean): O; + getTopCenter(output?: O, includeParent?: boolean): O; + getTopRight(output?: O, includeParent?: boolean): O; + getLeftCenter(output?: O, includeParent?: boolean): O; + getRightCenter(output?: O, includeParent?: boolean): O; + getBottomLeft(output?: O, includeParent?: boolean): O; + getBottomCenter(output?: O, includeParent?: boolean): O; + getBottomRight(output?: O, includeParent?: boolean): O; + getBounds(output?: O): O; + + originX: number; + originY: number; + displayOriginX: number; + displayOriginY: number; + setOrigin(x?: number, y?: number): this; + setOriginFromFrame(): this; + setDisplayOrigin(x?: number, y?: number): this; + updateDisplayOrigin(): this; + + scrollFactorX: number; + scrollFactorY: number; + setScrollFactor(x: number, y?: number): this; + + x: number; + y: number; + z: number; + w: number; + scale: number; + scaleX: number; + scaleY: number; + angle: number; + rotation: number; + setPosition(x?: number, y?: number, z?: number, w?: number): this; + copyPosition(source: Phaser.Types.Math.Vector2Like | Phaser.Types.Math.Vector3Like | Phaser.Types.Math.Vector4Like): this; + setRandomPosition(x?: number, y?: number, width?: number, height?: number): this; + setRotation(radians?: number): this; + setAngle(degrees?: number): this; + setScale(x: number, y?: number): this; + setX(value?: number): this; + setY(value?: number): this; + setZ(value?: number): this; + setW(value?: number): this; + getLocalTransformMatrix(tempMatrix?: Phaser.GameObjects.Components.TransformMatrix): Phaser.GameObjects.Components.TransformMatrix; + getWorldTransformMatrix(tempMatrix?: Phaser.GameObjects.Components.TransformMatrix, parentMatrix?: Phaser.GameObjects.Components.TransformMatrix): Phaser.GameObjects.Components.TransformMatrix; + getLocalPoint(x: number, y: number, point?: Phaser.Math.Vector2, camera?: Phaser.Cameras.Scene2D.Camera): Phaser.Math.Vector2; + getParentRotation(): number; + + visible: boolean; + setVisible(value: boolean): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObjectBase.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObjectBase.js new file mode 100644 index 000000000..860eee52c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/Live2dGameObjectBase.js @@ -0,0 +1,20 @@ +class Live2dGameObjectBase extends Phaser.GameObjects.GameObject { + +} + +const Components = Phaser.GameObjects.Components; +Phaser.Class.mixin(Live2dGameObjectBase, + [ + Components.AlphaSingle, + Components.BlendMode, + Components.ComputedSize, + Components.Depth, + Components.GetBounds, + Components.Origin, + Components.ScrollFactor, + Components.Transform, + Components.Visible, + ] +); + +export default Live2dGameObjectBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnExpressionStart.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnExpressionStart.js new file mode 100644 index 000000000..8e64e7194 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnExpressionStart.js @@ -0,0 +1,6 @@ +var OnExpressionStart = function (gameObject, name) { + gameObject.emit(`expression.start-${name}`); + gameObject.emit('expression.start', name); +} + +export default OnExpressionStart; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnIdle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnIdle.js new file mode 100644 index 000000000..28ac4b93f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnIdle.js @@ -0,0 +1,5 @@ +var OnIdle = function (gameObject) { + gameObject.emit('idle'); +} + +export default OnIdle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnMotionComplete.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnMotionComplete.js new file mode 100644 index 000000000..ea8112311 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnMotionComplete.js @@ -0,0 +1,6 @@ +var OnMotionComplete = function (gameObject, group, no) { + gameObject.emit(`motion.complete-${group}`, no); + gameObject.emit('motion.complete', group, no); +} + +export default OnMotionComplete; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnMotionStart.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnMotionStart.js new file mode 100644 index 000000000..0a487ee2e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/events/OnMotionStart.js @@ -0,0 +1,6 @@ +var OnMotionStart = function (gameObject, group, no) { + gameObject.emit(`motion.start-${group}`, no); + gameObject.emit('motion.start', group, no); +} + +export default OnMotionStart; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/globaldata/CanvasMatrix.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/globaldata/CanvasMatrix.js new file mode 100644 index 000000000..679a91725 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/globaldata/CanvasMatrix.js @@ -0,0 +1,34 @@ +import { CubismMatrix44 } from '../../framework/src/math/cubismmatrix44'; + +class CanvasMatrix extends CubismMatrix44 { + constructor() { + super(); + + this.setSize(0, 0); + } + + setSize(width, height) { + this.width = width; + this.height = height; + + if (width > height) { + this.scale(1.0, width / height); + } else { + this.scale(height / width, 1.0); + } + + return this; + } + + toLocalX(x) { + var t = (this.width === 0) ? 0 : (x / this.width); + return (2 * t) - 1; + } + + toLocalY(y) { + var t = (this.height === 0) ? 0 : (y / this.height); + return 1 - (2 * t); + } +} + +export default CanvasMatrix; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/globaldata/GlobalData.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/globaldata/GlobalData.js new file mode 100644 index 000000000..0de8e5e55 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/globaldata/GlobalData.js @@ -0,0 +1,70 @@ +import GetGame from '../../../../utils/system/GetGame.js'; +import CanvasMatrix from './CanvasMatrix.js'; + +var GlobalDataInstance = undefined; + +// Global data shared for all Live2dGameObjects +class GlobalData { + static getInstance(gameObject) { + if (!GlobalDataInstance) { + GlobalDataInstance = new GlobalData(gameObject); + } + return GlobalDataInstance; + } + + constructor(gameObject) { + var game = GetGame(gameObject); + var gl = game.renderer.gl; + var scale = game.scale; + + this.game = game; + this.gl = gl; + this.scale = scale; + + // A frame buffer for all live2d game object + this.frameBuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); + + this.viewportRect = [0, 0, 0, 0]; + this.projectionMatrix = new CanvasMatrix(); + this.onResize(); + + scale.on('resize', this.onResize, this); + game.events.once('destroy', this.destroy, this); + } + + destroy() { + this.scale.off('resize', this.onResize, this); + + this.game = undefined; + this.gl = undefined; + this.scale = undefined; + + this.frameBuffer = undefined; + this.viewportRect = undefined; + this.projectionMatrix = undefined; + + GlobalDataInstance = undefined; + } + + get canvasWidth() { + return this.scale.width; + } + + get canvasHeight() { + return this.scale.height; + } + + onResize() { + var width = this.canvasWidth; + var height = this.canvasHeight; + + // Set view port + this.viewportRect[2] = width; + this.viewportRect[3] = height; + + // Set projectionMatrix + this.projectionMatrix.setSize(width, height); + } +} + +export default GlobalData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/Methods.js new file mode 100644 index 000000000..743276762 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/Methods.js @@ -0,0 +1,68 @@ +import SetModel from './SetModel.js'; + +import GetExpressionNames from './expression/GetExpressionNames.js'; +import SetExpression from './expression/SetExpression.js'; +import SetRandomExpression from './expression/SetRandomExpression.js'; + +import GetMotionNames from './motion/GetMotionNames.js'; +import GetMotionGroupNames from './motion/GetMotionGroupNames.js'; +import StartMotion from './motion/StartMotion.js'; +import StopAllMotions from './motion/StopAllMotions.js'; +import GetPlayinigMotionNames from './motion/GetPlayinigMotionNames.js'; +import IsAnyMotionPlaying from './motion/IsAnyMotionPlaying.js'; +import AutoPlayIdleMotion from './motion/AutoPlayIdleMotion.js'; + +import RegisterParameter from './parameter/RegisterParameter.js'; +import AddParameterValue from './parameter/AddParameterValue.js'; +import ResetParameterValue from './parameter/ResetParameterValue.js'; +import GetParameters from './parameter/GetParameters.js'; +import LookAt from './parameter/LookAt.js'; +import LookForward from './parameter/LookForward.js'; + +import SetLipSyncValue from './lipsync/SetLipSyncValue.js'; + +import SetInteractive from './interactive/SetInteractive.js'; +import GetHitTestResult from './interactive/GetHitTestResult.js'; +import HitTest from './interactive/HitTest.js'; + +import GetModelXY from './position/WorldXYToModelXY.js'; + +import SetTimeScale from './SetTimeScale.js'; + + +var Methods = { + setModel: SetModel, + + getExpressionNames: GetExpressionNames, + setExpression: SetExpression, + setRandomExpression: SetRandomExpression, + + getMotionNames: GetMotionNames, + getMotionGroupNames: GetMotionGroupNames, + startMotion: StartMotion, + stopAllMotions: StopAllMotions, + getPlayinigMotionNames: GetPlayinigMotionNames, + isAnyMotionPlaying: IsAnyMotionPlaying, + autoPlayIdleMotion: AutoPlayIdleMotion, + + registerParameter: RegisterParameter, + addParameterValue: AddParameterValue, + resetParameterValue: ResetParameterValue, + getParameters: GetParameters, + lookAt: LookAt, + lookForward: LookForward, + + setLipSyncValue: SetLipSyncValue, + + setInteractive: SetInteractive, + getHitTestResult: GetHitTestResult, + hitTest: HitTest, + + getModelXY: GetModelXY, + + setTimeScale: SetTimeScale, +} + + + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/SetModel.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/SetModel.js new file mode 100644 index 000000000..50eb5ec80 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/SetModel.js @@ -0,0 +1,33 @@ +import Model from '../model/Model.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var SetModel = function (key, config) { + if (this.key === key) { + return this; + } + + var data = this.scene.cache.custom.live2d.get(key); + if (!data || !data.model) { + console.error(`Live2d: can't load ${key}'s assets`); + return; + } + + if (this.key !== undefined) { // Change model + this.model.release(); // Release old model + this.model = new Model(this); // Create new model + } + + this.key = key; + this.model.setup(data); + this.setSize(this.model._pixelWidth, this.model._pixelHeight); + + var autoPlayIdleMotion = GetValue(config, 'autoPlayIdleMotion', undefined); + if (autoPlayIdleMotion !== undefined) { + this.autoPlayIdleMotion(autoPlayIdleMotion); + } + + return this; +} + +export default SetModel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/SetTimeScale.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/SetTimeScale.js new file mode 100644 index 000000000..088479fe7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/SetTimeScale.js @@ -0,0 +1,5 @@ +var SetTimeScale = function (timeScale) { + this.timeScale = timeScale; + return this; +} +export default SetTimeScale; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/expression/GetExpressionNames.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/expression/GetExpressionNames.js new file mode 100644 index 000000000..7b00d55de --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/expression/GetExpressionNames.js @@ -0,0 +1,5 @@ +var GetExpressionNames = function () { + return this.model.getExpressionNames(); +} + +export default GetExpressionNames; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/expression/SetExpression.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/expression/SetExpression.js new file mode 100644 index 000000000..ba0de4800 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/expression/SetExpression.js @@ -0,0 +1,6 @@ +var SetExpression = function (expressionName) { + this.model.setExpression(expressionName); + return this; +} + +export default SetExpression; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/expression/SetRandomExpression.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/expression/SetRandomExpression.js new file mode 100644 index 000000000..6ec0d3edc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/expression/SetRandomExpression.js @@ -0,0 +1,6 @@ +var SetRandomExpression = function () { + this.model.setRandomExpression(); + return this; +} + +export default SetRandomExpression; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/GetHitTestResult.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/GetHitTestResult.js new file mode 100644 index 000000000..1b79031e7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/GetHitTestResult.js @@ -0,0 +1,5 @@ +var GetHitTestResult = function () { + return this.model._hitTestResult; +} + +export default GetHitTestResult; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/HitAreaCallback.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/HitAreaCallback.js new file mode 100644 index 000000000..1eb973b2c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/HitAreaCallback.js @@ -0,0 +1,24 @@ +var HitAreaCallback = function (shape, localX, localY, gameObject) { + var model = gameObject.model; + if (!model) { + return false; + } + + var matrixXY = model.localXYToModelMatrixXY(localX, localY, true); + var x = matrixXY.x + var y = matrixXY.y; + var modelSetting = model._modelSetting; + var count = modelSetting.getHitAreasCount(); + var anyHit = false; + var hitTestResult = model._hitTestResult; + for (var i = 0; i < count; i++) { + var hitAreaName = modelSetting.getHitAreaName(i); + var isHit = model.hitTest(hitAreaName, x, y); + hitTestResult[hitAreaName] = isHit; + anyHit = anyHit || isHit; + } + + return anyHit; +} + +export default HitAreaCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/HitTest.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/HitTest.js new file mode 100644 index 000000000..de3faf8f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/HitTest.js @@ -0,0 +1,6 @@ +var HitTest = function (hitAreaName, worldX, worldY, camera) { + var modelXY = this.getModelXY(worldX, worldY, camera, true); + return this.model.hitTest(hitAreaName, modelXY); +} + +export default HitTest; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/SetInteractive.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/SetInteractive.js new file mode 100644 index 000000000..280e6fda9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/interactive/SetInteractive.js @@ -0,0 +1,44 @@ +import HitAreaCallback from './HitAreaCallback.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GameObject = Phaser.GameObjects.GameObject; + +var SetInteractive = function (hitArea, hitAreaCallback, dropZone) { + var isInit = !this.input; + + if (IsPlainObject(hitArea)) { + hitArea.hitArea = HitAreaCallback; + hitArea.hitAreaCallback = HitAreaCallback; + } else { + hitArea = HitAreaCallback; + hitAreaCallback = HitAreaCallback; + } + + GameObject.prototype.setInteractive.call(this, hitArea, hitAreaCallback, dropZone); + + if (isInit) { + this + .on('pointerdown', function (pointer, localX, localY, event) { + FireEvent(this, 'pointerdown', pointer, localX, localY, event); + }) + .on('pointerup', function (pointer, localX, localY, event) { + FireEvent(this, 'pointerup', pointer, localX, localY, event); + }) + .on('pointermove', function (pointer, localX, localY, event) { + FireEvent(this, 'pointermove', pointer, localX, localY, event); + }) + } + + return this; +} + +var FireEvent = function (gameObject, eventPrefix, pointer, localX, localY, event) { + var hitTestResult = gameObject.hitTestResult; + for (var name in hitTestResult) { + if (hitTestResult[name]) { + gameObject.emit(`${eventPrefix}-${name}`, pointer, localX, localY, event); + } + } +} + +export default SetInteractive; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/lipsync/SetLipSyncValue.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/lipsync/SetLipSyncValue.js new file mode 100644 index 000000000..d60449133 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/lipsync/SetLipSyncValue.js @@ -0,0 +1,6 @@ +var SetLipSyncValue = function (value) { + this.model._lipSyncValue = value; + return this; +} + +export default SetLipSyncValue; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/AutoPlayIdleMotion.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/AutoPlayIdleMotion.js new file mode 100644 index 000000000..e9e3500f1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/AutoPlayIdleMotion.js @@ -0,0 +1,24 @@ +import * as Const from '../../model/Const.js'; + +var AutoPlayIdleMotion = function (motionName) { + // Not regiester 'idle' event, but also disable auto-play-idle-motion + if (!this.autoPlayIdleMotionCallback && !motionName) { + return this; + } + + // Register 'idle' event one time + if (!this.autoPlayIdleMotionCallback) { + this.autoPlayIdleMotionCallback = function () { + if (!this.idleMotionName) { + return; + } + this.startMotion(this.idleMotionName, undefined, Const.PriorityIdle); + } + this.on('idle', this.autoPlayIdleMotionCallback, this); + } + this.idleMotionName = motionName; + + return this; +} + +export default AutoPlayIdleMotion; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/GetMotionGroupNames.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/GetMotionGroupNames.js new file mode 100644 index 000000000..9a6eade66 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/GetMotionGroupNames.js @@ -0,0 +1,4 @@ +var GetMotionGroupNames = function () { + return this.model.getMotionGroupNames(); +} +export default GetMotionGroupNames; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/GetMotionNames.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/GetMotionNames.js new file mode 100644 index 000000000..ea265a3be --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/GetMotionNames.js @@ -0,0 +1,5 @@ +var GetMotionNames = function (groupName) { + return this.model.getMotionNames(groupName); +} + +export default GetMotionNames; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/GetPlayinigMotionNames.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/GetPlayinigMotionNames.js new file mode 100644 index 000000000..d193687e1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/GetPlayinigMotionNames.js @@ -0,0 +1,5 @@ +var GetPlayinigMotionNames = function () { + return this.model.getPlayinigMotionNames(); +} + +export default GetPlayinigMotionNames; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/IsAnyMotionPlaying.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/IsAnyMotionPlaying.js new file mode 100644 index 000000000..ebf9f0604 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/IsAnyMotionPlaying.js @@ -0,0 +1,5 @@ +var IsAnyMotionPlaying = function () { + return this.model.isAnyMotionPlaying(); +} + +export default IsAnyMotionPlaying; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/StartMotion.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/StartMotion.js new file mode 100644 index 000000000..d62ebf651 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/StartMotion.js @@ -0,0 +1,18 @@ +import * as Const from '../../model/Const.js'; + +var StartMotion = function (group, no, priority) { + if (typeof (priority) === 'string') { + priority = PriorityModes[priority]; + } + this.model.startMotion(group, no, priority); + return this; +} + +const PriorityModes = { + none: Const.PriorityNone, + idle: Const.PriorityIdle, + normal: Const.PriorityNormal, + force: Const.PriorityForce +} + +export default StartMotion; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/StopAllMotions.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/StopAllMotions.js new file mode 100644 index 000000000..7d092bc2f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/motion/StopAllMotions.js @@ -0,0 +1,6 @@ +var StopAllMotions = function () { + this.model.stopAllMotions(); + return this; +} + +export default StopAllMotions; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/AddParameterValue.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/AddParameterValue.js new file mode 100644 index 000000000..fc930cadd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/AddParameterValue.js @@ -0,0 +1,6 @@ +var AddParameterValue = function (name, value) { + this.model.addParameterValue(name, value); + return this; +} + +export default AddParameterValue; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/GetParameters.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/GetParameters.js new file mode 100644 index 000000000..674db9fbc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/GetParameters.js @@ -0,0 +1,5 @@ +var GetParameters = function () { + return this.model._addParamValues; +} + +export default GetParameters; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/LookAt.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/LookAt.js new file mode 100644 index 000000000..a0dce0ecd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/LookAt.js @@ -0,0 +1,46 @@ + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +var LookAt = function (x, y, config) { + if (IsPlainObject(x)) { + config = x; + x = undefined; + y = undefined; + } + + var modelX, modelY; + if (x === undefined) { + modelX = 0; + modelY = 0; + } else { + var camera = GetValue(config, 'camera', undefined); + var modelXY = this.getModelXY(x, y, camera, true); + modelX = modelXY.x; + modelY = modelXY.y; + } + + var params = this.getParameters(); + + // Eyes + var eyeBallXWeight = GetValue(config, 'eyeBallX', 1); + var eyeBallYWeight = GetValue(config, 'eyeBallY', 1); + params.EyeBallX = modelX * eyeBallXWeight; + params.EyeBallY = modelY * eyeBallYWeight; + + // Head + var angleXWeight = GetValue(config, 'angleX', 30); + var angleYWeight = GetValue(config, 'angleY', 30); + var angleZWeight = GetValue(config, 'angleZ', 30); + params.AngleX = modelX * angleXWeight; + params.AngleY = modelY * angleYWeight; + params.AngleZ = (-1) * modelX * modelY * angleZWeight; + + // Body + var bodyAngleXWeight = GetValue(config, 'bodyAngleX', 10); + params.BodyAngleX = modelX * bodyAngleXWeight; + + return this; +} + +export default LookAt; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/LookForward.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/LookForward.js new file mode 100644 index 000000000..7170e7832 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/LookForward.js @@ -0,0 +1,6 @@ +var LookForward = function(config) { + this.lookAt(config); + return this; +} + +export default LookForward; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/RegisterParameter.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/RegisterParameter.js new file mode 100644 index 000000000..13420a7a4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/RegisterParameter.js @@ -0,0 +1,6 @@ +var RegisterParameter = function (name) { + this.model.registerParameter(name); + return this; +} + +export default RegisterParameter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/ResetParameterValue.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/ResetParameterValue.js new file mode 100644 index 000000000..e5dc477cf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/parameter/ResetParameterValue.js @@ -0,0 +1,6 @@ +var ResetParameterValue = function (name) { + this.model.resetParameterValue(name); + return this; +} + +export default ResetParameterValue; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/position/WorldXYToModelXY.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/position/WorldXYToModelXY.js new file mode 100644 index 000000000..e7d9ea28b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/methods/position/WorldXYToModelXY.js @@ -0,0 +1,23 @@ +import WorldXYToGameObjectLocalXY from '../../../../../utils/position/WorldXYToGameObjectLocalXY.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +var WorldXYToModelXY = function (worldX, worldY, camera, out) { + if ((camera === undefined) || (camera === true) || IsPlainObject(camera)) { + out = camera; + camera = this.scene.cameras.main; + } + if (out === undefined) { + out = {} + } else if (out === true) { + out = globOut; + } + + out = WorldXYToGameObjectLocalXY(this, worldX, worldY, camera, out); + + return this.model.localXYToModelMatrixXY(out.x, out.y, out); +} + +var globOut = {}; + +export default WorldXYToModelXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/Const.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/Const.js new file mode 100644 index 000000000..0d5c3296e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/Const.js @@ -0,0 +1,4 @@ +export const PriorityNone = 0; +export const PriorityIdle = 1; +export const PriorityNormal = 2; +export const PriorityForce = 3; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/Methods.js new file mode 100644 index 000000000..2ee11453b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/Methods.js @@ -0,0 +1,49 @@ +import Setup from './setup/Setup.js'; +import Update from './update/Update.js'; +import Draw from './draw/Draw.js'; + +import GetExpressionNames from './expression/GetExpressionNames.js'; +import SetExpression from './expression/SetExpression.js'; +import SetRandomExpression from './expression/SetRandomExpression.js'; + +import GetMotionNames from './motion/GetMotionNames.js'; +import GetMotionGroupNames from './motion/GetMotionGroupNames.js'; +import StartMotion from './motion/StartMotion.js'; +import StopAllMotions from './motion/StopAllMotions.js'; +import IsAnyMotionPlaying from './motion/IsAnyMotionPlaying.js'; +import GetPlayinigMotionNames from './motion/GetPlayinigMotionNames.js'; + +import RegisterParameter from './parameter/RegisterParameter.js'; +import AddParameterValue from './parameter/AddParameterValue.js'; +import ResetParameterValue from './parameter/ResetParameterValue.js'; + +import LocalXYToModelMatrixXY from './position/LocalXToModelMatrixX.js'; +import GetDrawableBounds from './hitarea/GetDrawableBounds.js'; +import HitTest from './hitarea/HitTest.js'; + +var Methods = { + setup: Setup, + update: Update, + draw: Draw, + + getExpressionNames: GetExpressionNames, + setExpression: SetExpression, + setRandomExpression: SetRandomExpression, + + getMotionNames: GetMotionNames, + getMotionGroupNames: GetMotionGroupNames, + startMotion: StartMotion, + stopAllMotions: StopAllMotions, + isAnyMotionPlaying: IsAnyMotionPlaying, + getPlayinigMotionNames: GetPlayinigMotionNames, + + registerParameter: RegisterParameter, + addParameterValue: AddParameterValue, + resetParameterValue: ResetParameterValue, + + localXYToModelMatrixXY: LocalXYToModelMatrixXY, + getDrawableBounds: GetDrawableBounds, + hitTest: HitTest, +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/Model.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/Model.js new file mode 100644 index 000000000..eb1568adb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/Model.js @@ -0,0 +1,51 @@ +import { csmMap } from '../../framework/src/type/csmmap'; +import { csmVector } from '../../framework/src/type/csmvector'; +import { CubismUserModel } from '../../framework/src/model/cubismusermodel'; +import ViewMatrix from './ViewMatrix.js'; +import GlobalData from '../globaldata/GlobalData.js'; +import Methods from './Methods.js'; + +class Model extends CubismUserModel { + constructor(parent) { + super(); + + // Initialize Live2d framework, and get shared resources + this._globalData = GlobalData.getInstance(parent); + + this.parent = parent; // Live2dGameObject + this.viewMatrix = new ViewMatrix(); + + this._eyeBlinkIds = new csmVector(); + + this._lipSyncIds = new csmVector(); + this._lipSyncValue = 0; + + this._motions = new csmMap(); + + this._expressions = new csmMap(); + this._currentExpressionName = undefined; + + this._addParamValues = {}; + + this._pixelWidth = 0; + this._pixelHeight = 0; + + this._hitTestResult = {}; + + // this._wavFileHandler = new LAppWavFileHandler(); + } + + release() { + super.release(); + + this.parent = undefined; + this._globalData = undefined; + } +} + +Object.assign( + Model.prototype, + Methods +) + +export default Model; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/ViewMatrix.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/ViewMatrix.js new file mode 100644 index 000000000..fdad10de2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/ViewMatrix.js @@ -0,0 +1,32 @@ +import { CubismMatrix44 } from '../../framework/src/math/cubismmatrix44'; + +class ViewMatrix extends CubismMatrix44 { + copyFrom(matrix) { + this.setMatrix(matrix.getArray()); + return this; + } + + rotate(angle) { + // Do nothing if angle = 0 + if (angle === 0) { + return; + } + + var sin = Math.sin(angle); + var cos = Math.cos(angle); + + var matrix = this._tr; + + var a = matrix[0]; + var b = matrix[1]; + var c = matrix[4]; + var d = matrix[5]; + + matrix[0] = a * cos + c * sin; + matrix[1] = b * cos + d * sin; + matrix[4] = a * -sin + c * cos; + matrix[5] = b * -sin + d * cos; + } +} + +export default ViewMatrix; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/draw/Draw.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/draw/Draw.js new file mode 100644 index 000000000..823f3b230 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/draw/Draw.js @@ -0,0 +1,20 @@ +import UpdateViewMatrix from './UpdateViewMatrix.js'; + +var Draw = function (calcMatrix) { + if (!this._model) { + return; + } + + var globalData = this._globalData; + + var matrix = UpdateViewMatrix(this, calcMatrix); + + var renderer = this.getRenderer(); + renderer.setMvpMatrix(matrix); + renderer.setRenderState(globalData.frameBuffer, globalData.viewportRect); + renderer.drawModel(); + + return this; +} + +export default Draw; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/draw/UpdateViewMatrix.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/draw/UpdateViewMatrix.js new file mode 100644 index 000000000..d2a492264 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/draw/UpdateViewMatrix.js @@ -0,0 +1,47 @@ +var UpdateViewMatrix = function (model, calcMatrix) { + var gameObject = model.parent; + var projectionMatrix = model._globalData.projectionMatrix; + + var matrix = model.viewMatrix; + // Reset to identity matrix + matrix.loadIdentity(); + + // Apply scale + var modelWidth = gameObject.width; + var modelHeight = gameObject.height; + var canvasWidth = projectionMatrix.width; + var canvasHeight = projectionMatrix.height + + var scaleX = (calcMatrix.scaleX * modelWidth) / canvasWidth; + var scaleY = (calcMatrix.scaleY * modelHeight) / canvasHeight; + + if (modelWidth > modelHeight) { + scaleY *= modelWidth / modelHeight; + } else { + scaleX *= modelHeight / modelWidth; + } + + matrix.scale(scaleX, scaleY); + + // Apply rotate + matrix.rotate(-calcMatrix.rotationNormalized); + + // Apply translate + matrix.translate( + projectionMatrix.toLocalX(calcMatrix.getX(0, 0)), + projectionMatrix.toLocalY(calcMatrix.getY(0, 0)) + ); + + var modelMatrix = model._modelMatrix; + // Offset for origin + // modelMatrix.translate( + // modelMatrix._width * (0.5 - gameObject.originX), + // modelMatrix._height * (gameObject.originY - 0.5) + // ); + // Apply model matrix + matrix.multiplyByMatrix(modelMatrix); + + return matrix; +} + +export default UpdateViewMatrix; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/expression/GetExpressionNames.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/expression/GetExpressionNames.js new file mode 100644 index 000000000..7dde9f33c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/expression/GetExpressionNames.js @@ -0,0 +1,11 @@ +var GetExpressionNames = function () { + var names = []; + var count = this._expressions.getSize(); + var keyValuse = this._expressions._keyValues; + for (var i = 0; i < count; i++) { + names.push(keyValuse[i].first); + } + return names; +} + +export default GetExpressionNames; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/expression/SetExpression.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/expression/SetExpression.js new file mode 100644 index 000000000..1aa44167c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/expression/SetExpression.js @@ -0,0 +1,38 @@ +import * as Const from '../Const.js'; +import OnExpressionStart from '../../events/OnExpressionStart.js'; + +var SetExpression = function (name) { + if (name === undefined) { + name = 0; + } + + var motion; + var nameType = typeof (name); + if (nameType === 'string') { + motion = this._expressions.getValue(name); + } else if (nameType === 'number') { + var keyValue = this._expressions._keyValues[name]; + motion = (keyValue) ? keyValue.second : null; + name = (keyValue) ? keyValue.first : undefined; + } + + if (!motion) { + // Error + return this; + } + + motion._name = name; + + this._expressionManager.startMotionPriority( + motion, + false, + Const.PriorityForce + ); + this._currentExpressionName = name; + + OnExpressionStart(this.parent, name); + + return this; +} + +export default SetExpression; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/expression/SetRandomExpression.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/expression/SetRandomExpression.js new file mode 100644 index 000000000..659312689 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/expression/SetRandomExpression.js @@ -0,0 +1,12 @@ +var SetRandomExpression = function () { + var count = this._expressions.getSize(); + if (count === 0) { + return this; + } + + var index = Math.floor(Math.random() * count); + this.setExpression(index); + return this; +} + +export default SetRandomExpression; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/hitarea/GetDrawableBounds.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/hitarea/GetDrawableBounds.js new file mode 100644 index 000000000..517a2c955 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/hitarea/GetDrawableBounds.js @@ -0,0 +1,47 @@ +import HitAreaNameToDrawIndex from './HitAreaNameToDrawIndex.js'; + +const Rectangle = Phaser.Geom.Rectangle; + +var GetDrawableBounds = function (index, bounds) { + if (bounds === undefined) { + bounds = new Rectangle(); + } else if (bounds === true) { + if (GlobRect === undefined) { + GlobRect = new Rectangle; + } + bounds = GlobRect; + } + + if (typeof (index) === 'string') { + index = HitAreaNameToDrawIndex.call(this, index); + if (index === undefined) { + return null; + } + } + + var count = this._model.getDrawableVertexCount(index) * 2; + var vertices = this._model.getDrawableVertices(index); + + var left = Infinity, + right = -Infinity, + top = Infinity, + bottom = -Infinity; + + for (var i = 0; i < count; i += 2) { + var x = vertices[i]; + var y = vertices[i + 1]; + + left = Math.min(left, x); + right = Math.max(right, x); + top = Math.min(top, y); + bottom = Math.max(bottom, y); + } + + bounds.setTo(left, top, (right - left), (bottom - top)); + + return bounds; +} + +var GlobRect; + +export default GetDrawableBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/hitarea/HitAreaNameToDrawIndex.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/hitarea/HitAreaNameToDrawIndex.js new file mode 100644 index 000000000..63925e708 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/hitarea/HitAreaNameToDrawIndex.js @@ -0,0 +1,13 @@ +var HitAreaNameToDrawIndex = function (hitAreaName) { + var count = this._modelSetting.getHitAreasCount(); + for (var i = 0; i < count; i++) { + if (this._modelSetting.getHitAreaName(i) === hitAreaName) { + var drawId = this._modelSetting.getHitAreaId(i) + var drawIndex = this._model.getDrawableIndex(drawId); + return drawIndex; + } + } + return undefined; +} + +export default HitAreaNameToDrawIndex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/hitarea/HitTest.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/hitarea/HitTest.js new file mode 100644 index 000000000..7b295cd34 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/hitarea/HitTest.js @@ -0,0 +1,15 @@ +var HitTest = function (hitAreaName, x, y) { + var bounds = this.getDrawableBounds(hitAreaName, true); + if (!bounds) { + return false; + } + if (typeof (x) === 'object') { + var xy = x; + x = xy.x; + y = xy.y; + } + + return bounds.contains(x, y); +} + +export default HitTest; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/GetMotionGroupNames.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/GetMotionGroupNames.js new file mode 100644 index 000000000..bb95c978b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/GetMotionGroupNames.js @@ -0,0 +1,17 @@ +var GetMotionGroupNames = function () { + var names = []; + var count = this._motions.getSize(); + var keyValuse = this._motions._keyValues; + for (var i = 0; i < count; i++) { + var name = keyValuse[i].first; + var groupName = name.split('_')[0]; + if (names.indexOf(groupName) !== -1) { + continue; + } + + names.push(groupName); + } + return names; +} + +export default GetMotionGroupNames; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/GetMotionNames.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/GetMotionNames.js new file mode 100644 index 000000000..f6a17cb21 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/GetMotionNames.js @@ -0,0 +1,15 @@ +var GetMotionNames = function (groupName) { + var names = []; + var count = this._motions.getSize(); + var keyValuse = this._motions._keyValues; + for (var i = 0; i < count; i++) { + var name = keyValuse[i].first; + if (groupName & !name.startsWith(groupName)) { + continue; + } + names.push(name); + } + return names; +} + +export default GetMotionNames; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/GetPlayinigMotionNames.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/GetPlayinigMotionNames.js new file mode 100644 index 000000000..3d06174d8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/GetPlayinigMotionNames.js @@ -0,0 +1,16 @@ +var GetPlayinigMotionNames = function () { + var names = []; + var motionManager = this._motionManager; + var motions = motionManager._motions; + for (var i = 0, cnt = motions.getSize(); i < cnt; i++) { + var motionQueueEntry = motions.at(i); + if (motionQueueEntry._finished) { + continue; + } + names.push(motionQueueEntry._motion._name); + } + + return names; +} + +export default GetPlayinigMotionNames; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/IsAnyMotionPlaying.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/IsAnyMotionPlaying.js new file mode 100644 index 000000000..56b18b399 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/IsAnyMotionPlaying.js @@ -0,0 +1,5 @@ +var IsAnyMotionPlaying = function() { + return !this._motionManager.isFinished(); +} + +export default IsAnyMotionPlaying; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/StartMotion.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/StartMotion.js new file mode 100644 index 000000000..755269410 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/StartMotion.js @@ -0,0 +1,46 @@ +import * as Const from '../Const.js'; +import OnMotionStart from '../../events/OnMotionStart.js'; +import OnMotionComplete from '../../events/OnMotionComplete.js'; + +var StartMotion = function (group, no, priority) { + if (priority === undefined) { + priority = Const.PriorityNormal; + } + + if (priority === Const.PriorityForce) { + this._motionManager.setReservePriority(priority); + } else if (!this._motionManager.reserveMotion(priority)) { + // Error + return this; + } + + if (no === undefined) { + no = Math.floor(Math.random() * this._modelSetting.getMotionCount(group)); + } + + var name = `${group}_${no}`; + var motion = this._motions.getValue(name); + if (!motion) { + // Error + return this; + } + + motion._name = name; + + var gameObject = this.parent; + motion.setFinishedMotionHandler(function () { + OnMotionComplete(gameObject, group, no) + }); + + this._motionManager.startMotionPriority( + motion, + false, + priority + ); + + OnMotionStart(gameObject, group, no); + + return this; +} + +export default StartMotion; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/StopAllMotions.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/StopAllMotions.js new file mode 100644 index 000000000..d62697135 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/motion/StopAllMotions.js @@ -0,0 +1,6 @@ +var StopAllMotions = function () { + this._motionManager.stopAllMotions(); + return this; +} + +export default StopAllMotions; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/parameter/AddParameterValue.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/parameter/AddParameterValue.js new file mode 100644 index 000000000..79febd176 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/parameter/AddParameterValue.js @@ -0,0 +1,20 @@ +const Capitalize = Phaser.Utils.String.UppercaseFirst; + +var AddParameterValue = function (name, value) { + var propertyName = `_idParam${Capitalize(name)}`; + if (!this.hasOwnProperty(propertyName)) { + this.registerParameter(name); + + // Can't register this parameter + if (!this.hasOwnProperty(propertyName)) { + // Error + return this; + } + } + + this._addParamValues[name] += value; + + return this; +} + +export default AddParameterValue; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/parameter/RegisterParameter.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/parameter/RegisterParameter.js new file mode 100644 index 000000000..6c8129bc1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/parameter/RegisterParameter.js @@ -0,0 +1,25 @@ +import { CubismFramework } from '../../../framework/src/live2dcubismframework'; +import { CubismDefaultParameterId } from '../../../framework/src/cubismdefaultparameterid'; + +const Capitalize = Phaser.Utils.String.UppercaseFirst; + +var RegisterParameter = function (name) { + var capName = `Param${Capitalize(name)}`; + var propertyName = `_id${capName}`; + if (this.hasOwnProperty(propertyName)) { + return this; + } + if (!CubismDefaultParameterId.hasOwnProperty(capName)) { + // Error; + return this; + } + + var parameterId = CubismDefaultParameterId[capName]; + this[propertyName] = CubismFramework.getIdManager().getId(parameterId); + + this._addParamValues[name] = 0; + + return this; +} + +export default RegisterParameter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/parameter/ResetParameterValue.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/parameter/ResetParameterValue.js new file mode 100644 index 000000000..439cb14c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/parameter/ResetParameterValue.js @@ -0,0 +1,19 @@ +const Capitalize = Phaser.Utils.String.UppercaseFirst; + +var ResetParameterValue = function (name) { + var propertyName = `_idParam${Capitalize(name)}`; + if (!this.hasOwnProperty(propertyName)) { + this.registerParameter(name); + + // Can't register this parameter + if (!this.hasOwnProperty(propertyName)) { + return this; + } + } + + this._addParamValues[name] = 0; + + return this; +} + +export default ResetParameterValue; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/position/LocalXToModelMatrixX.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/position/LocalXToModelMatrixX.js new file mode 100644 index 000000000..c1dcfed1d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/position/LocalXToModelMatrixX.js @@ -0,0 +1,16 @@ +var LocalXYToModelMatrixXY = function (localX, localY, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = GlobMatrixXY; + } + + out.x = (localX - (this._pixelWidth / 2)) / this._pixelsPerUnit; + out.y = ((this._pixelHeight / 2) - localY) / this._pixelsPerUnit; + + return out; +} + +var GlobMatrixXY = {}; + +export default LocalXYToModelMatrixXY; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/setup/Setup.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/setup/Setup.js new file mode 100644 index 000000000..1637ff0f0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/setup/Setup.js @@ -0,0 +1,161 @@ +import { ACubismMotion } from '../../../framework/src/motion/acubismmotion'; +import { CubismEyeBlink } from '../../../framework/src/effect/cubismeyeblink'; +import { BreathParameterData, CubismBreath } from '../../../framework/src/effect/cubismbreath'; +import { csmVector } from '../../../framework/src/type/csmvector'; +import { csmMap } from '../../../framework/src/type/csmmap'; + +var Setup = function (data) { + // Load setting + var setting = data.setting; + this._modelSetting = setting; + + // Load CubismModel + var arrayBuffer = data.model; + // - Create this._model + this.loadModel(arrayBuffer, arrayBuffer.byteLength); + // - Re-create render for current this._model + this.createRenderer(); + // - Set gl to current renderer + this.getRenderer().startUp(this._globalData.gl); + + // Load CubismExpression + var expressions = data.expressions; + for (var expressionName in expressions) { + var arrayBuffer = expressions[expressionName]; + var motion = this.loadExpression(arrayBuffer, arrayBuffer.byteLength, expressionName); + + if (this._expressions.getValue(expressionName) != null) { + ACubismMotion.delete(this._expressions.getValue(expressionName)); + this._expressions.setValue(expressionName, null); + } + + this._expressions.setValue(expressionName, motion); + } + + // Load CubismPhysics + var arrayBuffer = data.physics; + if (arrayBuffer) { + this.loadPhysics(arrayBuffer, arrayBuffer.byteLength); + } + + // Load CubismPose + var arrayBuffer = data.pose; + if (arrayBuffer) { + this.loadPose(arrayBuffer, arrayBuffer.byteLength); + } + + // Setup EyeBlink + if (setting.getEyeBlinkParameterCount() > 0) { + this._eyeBlink = CubismEyeBlink.create(setting); + } + + // Setup Breath + this._breath = CubismBreath.create(); + + this.registerParameter('angleX'); + this.registerParameter('angleY'); + this.registerParameter('angleZ'); + this.registerParameter('bodyAngleX'); + this.registerParameter('breath'); + + var breathParameters = new csmVector(); + breathParameters.pushBack( + new BreathParameterData(this._idParamAngleX, 0.0, 15.0, 6.5345, 0.5) + ); + breathParameters.pushBack( + new BreathParameterData(this._idParamAngleY, 0.0, 8.0, 3.5345, 0.5) + ); + breathParameters.pushBack( + new BreathParameterData(this._idParamAngleZ, 0.0, 10.0, 5.5345, 0.5) + ); + breathParameters.pushBack( + new BreathParameterData(this._idParamBodyAngleX, 0.0, 4.0, 15.5345, 0.5) + ); + breathParameters.pushBack( + new BreathParameterData(this._idParamBreath, 0.5, 0.5, 3.2345, 1) + ); + + this._breath.setParameters(breathParameters); + + // Load UserData + var arrayBuffer = data.userData; + if (arrayBuffer) { + this.loadUserData(arrayBuffer, arrayBuffer.byteLength); + } + + // Setup EyeBlinkIds + var eyeBlinkIdCount = setting.getEyeBlinkParameterCount(); + for (var i = 0; i < eyeBlinkIdCount; i++) { + this._eyeBlinkIds.pushBack(setting.getEyeBlinkParameterId(i)); + } + + // Setup LipSyncIds + var lipSyncIdCount = setting.getLipSyncParameterCount(); + for (let i = 0; i < lipSyncIdCount; i++) { + this._lipSyncIds.pushBack(setting.getLipSyncParameterId(i)); + } + + // Load CubismMotion + this._model.saveParameters(); + var motionGroups = data.motions; + for (var groupName in motionGroups) { + var motionGroup = motionGroups[groupName]; + for (var i in motionGroup) { + var arrayBuffer = motionGroup[i]; + var motionName = `${groupName}_${i}`; + var motion = this.loadMotion(arrayBuffer, arrayBuffer.byteLength, motionName); + + i = parseInt(i); + var fadeTime = setting.getMotionFadeInTimeValue(groupName, i); + if (fadeTime >= 0.0) { + motion.setFadeInTime(fadeTime); + } + + var fadeTime = setting.getMotionFadeOutTimeValue(groupName, i); + if (fadeTime >= 0.0) { + motion.setFadeOutTime(fadeTime); + } + + motion.setEffectIds(this._eyeBlinkIds, this._lipSyncIds); + + if (this._motions.getValue(motionName) != null) { + ACubismMotion.delete(this._motions.getValue(motionName)); + } + + this._motions.setValue(motionName, motion); + } + + } + + // Load texture + var textures = data.textures; + for (var i in textures) { + this.getRenderer().bindTexture(parseInt(i), textures[i]); + } + + // Stop all motions + this._motionManager.stopAllMotions(); + + // Setup canvas size + var canvasinfo = this._model._model.canvasinfo; + this._pixelWidth = canvasinfo.CanvasWidth; + this._pixelHeight = canvasinfo.CanvasHeight; + this._pixelsPerUnit = canvasinfo.PixelsPerUnit; + + // Setup ModelMatrix + var layout = new csmMap(); + setting.getLayoutMap(layout); + this._modelMatrix.setupFromLayout(layout); + + + // Hit test result + var count = this._modelSetting.getHitAreasCount(); + for (var i = 0; i < count; i++) { + var hitAreaName = this._modelSetting.getHitAreaName(i); + this._hitTestResult[hitAreaName] = false; + } + + return this; +} + +export default Setup; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/update/Update.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/update/Update.js new file mode 100644 index 000000000..e2aef05ea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/model/update/Update.js @@ -0,0 +1,70 @@ +import OnIdle from '../../events/OnIdle.js'; + +const Capitalize = Phaser.Utils.String.UppercaseFirst; + +var Update = function (time, delta) { + var deltaTimeSeconds = delta / 1000; + + var motionUpdated = false; + this._model.loadParameters(); + if (!this._motionManager.isFinished()) { + motionUpdated = this._motionManager.updateMotion(this._model, deltaTimeSeconds); + } else { + OnIdle(this.parent); + } + this._model.saveParameters(); + + // Add parameter values + for (var name in this._addParamValues) { + var addValue = this._addParamValues[name]; + if (addValue === 0) { + continue; + } + + var propertyName = `_idParam${Capitalize(name)}`; + if (!this.hasOwnProperty(propertyName)) { + this.registerParameter(name); + + // Can't register this parameter + if (!this.hasOwnProperty(propertyName)) { + // Error + return this; + } + } + + this._model.addParameterValueById(this[propertyName], addValue); + } + + if (!motionUpdated && this._eyeBlink) { + this._eyeBlink.updateParameters(this._model, deltaTimeSeconds); + } + + if (this._expressionManager) { + this._expressionManager.updateMotion(this._model, deltaTimeSeconds); + } + + if (this._breath != null) { + this._breath.updateParameters(this._model, deltaTimeSeconds); + } + + if (this._physics != null) { + this._physics.evaluate(this._model, deltaTimeSeconds); + } + + if (this._lipsync && (this._lipSyncValue !== 0)) { + var count = this._lipSyncIds.getSize(); + for (var i = 0; i < count; ++i) { + this._model.addParameterValueById(this._lipSyncIds.at(i), this._lipSyncValue); + } + } + + if (this._pose != null) { + this._pose.updateParameters(this._model, deltaTimeSeconds); + } + + this._model.update(); + + return this; +} + +export default Update; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/render/CanvasRenderer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/render/CanvasRenderer.js new file mode 100644 index 000000000..274af3abf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/render/CanvasRenderer.js @@ -0,0 +1,4 @@ +var CanvasRenderer = function (renderer, src, camera, parentMatrix) { +}; + +export default CanvasRenderer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/render/Render.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/render/Render.js new file mode 100644 index 000000000..404fc7750 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/render/Render.js @@ -0,0 +1,8 @@ +import WebGLRenderer from './WebGLRenderer.js'; +import CanvasRenderer from './CanvasRenderer.js'; + +export default { + renderWebGL: WebGLRenderer, + renderCanvas: CanvasRenderer + +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/render/WebGLRenderer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/render/WebGLRenderer.js new file mode 100644 index 000000000..2be65d562 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/gameobject/render/WebGLRenderer.js @@ -0,0 +1,20 @@ +// const Utils = Phaser.Renderer.WebGL.Utils; +const GetCalcMatrix = Phaser.GameObjects.GetCalcMatrix; + +var WebGLRenderer = function (renderer, src, camera, parentMatrix) { + if (renderer.newType) { + renderer.pipelines.clear(); + } + + camera.addToRenderList(src); + + var calcMatrix = GetCalcMatrix(src, camera, parentMatrix).calc; + + src.model.draw(calcMatrix); + + if (!renderer.nextTypeMatch) { + renderer.pipelines.rebind(); + } +}; + +export default WebGLRenderer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/index.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/index.js new file mode 100644 index 000000000..3bfa19bdd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/index.js @@ -0,0 +1,9 @@ +import Live2dCoreScriptFileCallback from './loader/core/CoreScriptFileCallback.js'; +import Live2dFileCallback from './loader/model/Live2dFileCallback.js'; +import Live2dGameObject from './gameobject/Live2dGameObject.js'; + +export { + Live2dCoreScriptFileCallback, + Live2dFileCallback, + Live2dGameObject +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/index.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/index.ts new file mode 100644 index 000000000..58d9f29e6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/index.ts @@ -0,0 +1,9 @@ +import Live2dCoreScriptFileCallback from './loader/core/CoreScriptFileCallback'; +import Live2dFileCallback from './loader/model/Live2dFileCallback'; +import Live2dGameObject from './gameobject/Live2dGameObject'; + +export { + Live2dCoreScriptFileCallback, + Live2dFileCallback, + Live2dGameObject +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/CoreScriptFileCallback.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/CoreScriptFileCallback.d.ts new file mode 100644 index 000000000..8f2ab2825 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/CoreScriptFileCallback.d.ts @@ -0,0 +1,6 @@ +export default CoreScriptFileCallback; + +declare function CoreScriptFileCallback( + this: Phaser.Loader.LoaderPlugin, + url: string +): Phaser.Loader.LoaderPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/CoreScriptFileCallback.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/CoreScriptFileCallback.js new file mode 100644 index 000000000..d56ca231b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/CoreScriptFileCallback.js @@ -0,0 +1,8 @@ +import Live2dCoreScriptFile from './Live2dCoreScriptFile.js'; + +var CoreScriptFileCallback = function (url) { + this.addFile(new Live2dCoreScriptFile(this, url)); + return this; +} + +export default CoreScriptFileCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/Live2dCoreScriptFile.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/Live2dCoreScriptFile.js new file mode 100644 index 000000000..4a1a042c9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/Live2dCoreScriptFile.js @@ -0,0 +1,39 @@ +import AwaitFile from '../../../../loader/awaitloader/AwaitFile.js'; +import LoadScriptPromise from '../../../../utils/loader/LoadScriptPromise.js'; +import InitializeCubism from '../../utils/Initialize.js'; +import { + IsIdle as IsCoreNotLoad, + SetState as SetCoreScriptState, + LOADING, LOADED +} from './Live2dCoreScriptState.js'; + +class Live2dCoreScriptFile extends AwaitFile { + constructor(loader, url) { + if (url === undefined) { + url = 'https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js'; + } + + var callback = function (successCallback, failureCallback) { + LoadScriptPromise(url) + .then(function () { + InitializeCubism(); + + SetCoreScriptState(LOADED); + + successCallback(); + }) + } + + if (IsCoreNotLoad) { + SetCoreScriptState(LOADING); + } + + super(loader, { + type: 'live2dcore', + key: 'live2dcore', + config: { callback: callback } + }) + } +} + +export default Live2dCoreScriptFile; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/Live2dCoreScriptState.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/Live2dCoreScriptState.js new file mode 100644 index 000000000..087e1057a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/core/Live2dCoreScriptState.js @@ -0,0 +1,26 @@ +const IDLE = 0; +const LOADING = 1; +const LOADED = 2; + +var Live2dCoreScriptState = IDLE; + +var SetState = function (state) { + Live2dCoreScriptState = state; +} + +var IsIdle = function () { + return (Live2dCoreScriptState === IDLE); +} + +var IsLoading = function () { + return (Live2dCoreScriptState === LOADING); +} + +var IsLoaded = function () { + return (Live2dCoreScriptState === LOADED); +} + +export { + IDLE, LOADING, LOADED, + SetState, IsIdle, IsLoading, IsLoaded +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/CreateBinaryFile.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/CreateBinaryFile.js new file mode 100644 index 000000000..145c6c34c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/CreateBinaryFile.js @@ -0,0 +1,10 @@ +const BinaryFile = Phaser.Loader.FileTypes.BinaryFile; + +var CreateBinaryFile = function (loader, key, url, xhrSettings, dataKey) { + var file = new BinaryFile(loader, key, url, xhrSettings); + file.dataKey = dataKey; // Store data by dataKey into live2d cache later + file.cache = false; // Don't store data into binary cache + return file; +} + +export default CreateBinaryFile; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/Live2dFile.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/Live2dFile.js new file mode 100644 index 000000000..e9d1a13c7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/Live2dFile.js @@ -0,0 +1,84 @@ +import CreateBinaryFile from './CreateBinaryFile.js'; +import { CubismModelSettingJson } from '../../framework/src/cubismmodelsettingjson'; +import LoadChildrenFiles from './LoadChildrenFiles.js'; +import SetValue from '../../../../utils/object/SetValue.js'; + +const GetFastValue = Phaser.Utils.Objects.GetFastValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +class Live2dFile extends Phaser.Loader.MultiFile { + constructor(loader, key, url, xhrSettings) { + if (IsPlainObject(key)) { + var config = key; + + key = GetFastValue(config, 'key'); + url = GetFastValue(config, 'url'); + xhrSettings = GetFastValue(config, 'xhrSettings'); + } + + var cache = loader.cacheManager.custom.live2d; + + // Load setting + var settingFile = CreateBinaryFile(loader, key, url, xhrSettings, 'setting'); + super(loader, 'live2d', key, [settingFile]); + + this.cache = cache; + this.homeDir = url.substring(0, url.lastIndexOf('/') + 1); + } + + onFileComplete(file) { + var index = this.files.indexOf(file); + if (index === -1) { + return; + } + + // console.log(`Load file '${file.key}' at '${file.url}'`) + + this.pending--; + + if (index === 0) { + var arrayBuffer = file.data; + var setting = new CubismModelSettingJson(arrayBuffer, arrayBuffer.byteLength); + file.data = setting; + + // Load remainder files by setting + LoadChildrenFiles(this, setting); + } + } + + addToCache() { + if (this.isReadyToProcess()) { + var textureManager = this.loader.textureManager; + var data = { key: this.key }; + for (var i = 0, cnt = this.files.length; i < cnt; i++) { + var file = this.files[i]; + + var fileData = file.data; + // Process textures + if (file.dataKey.startsWith('textures')) { + var key = file.key.replace(`${this.key}!`, ''); + var texture; + // Add image to textureManager manually + if (!textureManager.exists(key)) { + texture = textureManager.addImage(key, file.data); + } else { + texture = textureManager.get(key); + } + + // Store glTexture to live2d data cache + fileData = texture.source[0].glTexture; + } + + SetValue(data, file.dataKey, fileData, '!!!'); + + file.pendingDestroy(); + } + + this.cache.add(this.key, data); + + this.complete = true; + } + } +} + +export default Live2dFile; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/Live2dFileCallback.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/Live2dFileCallback.d.ts new file mode 100644 index 000000000..2a5223bfa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/Live2dFileCallback.d.ts @@ -0,0 +1,7 @@ +export default Live2dFileCallback; + +declare function Live2dFileCallback( + this: Phaser.Loader.LoaderPlugin, + key: string, + url: string +): Phaser.Loader.LoaderPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/Live2dFileCallback.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/Live2dFileCallback.js new file mode 100644 index 000000000..f21c1c0eb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/Live2dFileCallback.js @@ -0,0 +1,57 @@ +import Live2dCoreScriptFile from '../core/Live2dCoreScriptFile.js'; +import Live2dFile from './Live2dFile.js'; +import { + IsIdle as IsCoreNotLoad, + IsLoaded as IsCoreLoaded, + SetState as SetCoreScriptState, + LOADED as CoreScriptLoaded +} from '../core/Live2dCoreScriptState.js' +import Initialize from '../../utils/Initialize.js'; + +var Live2dFileCallback = function (key, url) { + var loader = this; + + loader.cacheManager.addCustom('live2d'); + + if (IsCoreNotLoad()) { + if (window.Live2DCubismCore) { + // Core script is loaded before + Initialize(); + SetCoreScriptState(CoreScriptLoaded); + + } else { + // Core script is not loaded + // Load core script from default path + loader.addFile(new Live2dCoreScriptFile(loader)); + + } + } + + if (IsCoreLoaded()) { + // Core script is loaded + // Can load model assets directly + LoadFiles(loader, key, url); + } else { + // Core script is loading + loader.once('filecomplete-live2dcore-live2dcore', function () { + // Load model assets + LoadFiles(loader, key, url); + }) + } + + return this; +} + +var LoadFiles = function (loader, key, url) { + if (Array.isArray(key)) { + for (var i = 0; i < key.length; i++) { + var multifile = new Live2dFile(loader, key[i]); + loader.addFile(multifile.files); + } + } else { + var multifile = new Live2dFile(loader, key, url); + loader.addFile(multifile.files); + } +} + +export default Live2dFileCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/LoadChildrenFiles.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/LoadChildrenFiles.js new file mode 100644 index 000000000..994cd8e6a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/loader/model/LoadChildrenFiles.js @@ -0,0 +1,182 @@ +import CreateBinaryFile from './CreateBinaryFile.js'; + +const GetFastValue = Phaser.Utils.Objects.GetFastValue; +const ImageFile = Phaser.Loader.FileTypes.ImageFile; + +var LoadChildrenFiles = function (parent, setting) { + var loader = parent.loader; + var xhrSettings = GetFastValue(parent.config, 'xhrSettings'); + var key = parent.key; + var homeDir = parent.homeDir; + var requestUrls = []; // Load a file one time + + // Load CubismModel + var modelFileName = setting.getModelFileName(); + if (modelFileName !== '') { + var requestUrl = `${homeDir}${modelFileName}`; + if (requestUrls.indexOf(requestUrl) === -1) { + var modelFile = CreateBinaryFile( + loader, + `${key}!${modelFileName}`, + requestUrl, + xhrSettings, + 'model' + ); + + parent.addToMultiFile(modelFile); + loader.addFile(modelFile); + + requestUrls.push(requestUrl); + } + + } else { + // Error + console.error(`Live2d: can't load model ${key}`); + return; + } + + // Load CubismExpression + var cnt = setting.getExpressionCount(); + for (var i = 0; i < cnt; i++) { + var expressionFileName = setting.getExpressionFileName(i); + var expressionName = setting.getExpressionName(i); + + var requestUrl = `${homeDir}${expressionFileName}`; + if (requestUrls.indexOf(requestUrl) === -1) { + var expressionFile = CreateBinaryFile( + loader, + `${key}!${expressionFileName}`, + requestUrl, + xhrSettings, + `expressions!!!${expressionName}` + ); + + parent.addToMultiFile(expressionFile); + loader.addFile(expressionFile); + + requestUrls.push(requestUrl); + } + + } + + // Load CubismPhysics + var physicsFileName = setting.getPhysicsFileName(); + if (physicsFileName !== '') { + var requestUrl = `${homeDir}${physicsFileName}`; + if (requestUrls.indexOf(requestUrl) === -1) { + var physicsFile = CreateBinaryFile( + loader, + `${key}!${physicsFileName}`, + requestUrl, + xhrSettings, + 'physics' + ); + + parent.addToMultiFile(physicsFile); + loader.addFile(physicsFile); + + requestUrls.push(requestUrl); + } + + } + + // Load CubismPose + var poseFileName = setting.getPoseFileName(); + if (poseFileName !== '') { + var requestUrl = `${homeDir}${poseFileName}`; + if (requestUrls.indexOf(requestUrl) === -1) { + var poseFile = CreateBinaryFile( + loader, + `${key}!${poseFileName}`, + requestUrl, + xhrSettings, + 'pose' + ); + + parent.addToMultiFile(poseFile); + loader.addFile(poseFile); + + requestUrls.push(requestUrl); + } + + } + + // Load UserData + var userDataFileName = setting.getUserDataFile(); + if (userDataFileName !== '') { + var requestUrl = `${homeDir}${userDataFileName}`; + if (requestUrls.indexOf(requestUrl) === -1) { + var userDataFile = CreateBinaryFile( + loader, + `${key}!${userDataFileName}`, + requestUrl, + xhrSettings, + 'userData' + ); + + parent.addToMultiFile(userDataFile); + loader.addFile(userDataFile); + + requestUrls.push(requestUrl); + } + + } + + // Load CubismMotion + var groupCnt = setting.getMotionGroupCount(); + for (var gi = 0; gi < groupCnt; gi++) { + var groupName = setting.getMotionGroupName(gi); + var cnt = setting.getMotionCount(groupName); + for (var i = 0; i < cnt; i++) { + var motionFileName = setting.getMotionFileName(groupName, i); + var requestUrl = `${homeDir}${motionFileName}`; + if (requestUrls.indexOf(requestUrl) === -1) { + var motionFile = CreateBinaryFile( + loader, + `${key}!${motionFileName}`, + requestUrl, + xhrSettings, + `motions!!!${groupName}!!!${i}` + ); + + parent.addToMultiFile(motionFile); + loader.addFile(motionFile); + + requestUrls.push(requestUrl); + } + + } + + } + + // Load texture + var textureCnt = setting.getTextureCount(); + for (var i = 0; i < textureCnt; i++) { + var textureFileName = setting.getTextureFileName(i); + if (textureFileName === '') { + // Error + continue; + } + + // TODO: store texture into live2d cache? + var requestUrl = `${homeDir}${textureFileName}`; + if (requestUrls.indexOf(requestUrl) === -1) { + var imageFile = new ImageFile( + loader, + `${key}!${textureFileName}`, + requestUrl, + xhrSettings + ); + imageFile.dataKey = `textures!!!${i}`; + + parent.addToMultiFile(imageFile); + loader.addFile(imageFile); + + requestUrls.push(requestUrl); + } + + } + +} + +export default LoadChildrenFiles; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/note.md b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/note.md new file mode 100644 index 000000000..f08255cca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/note.md @@ -0,0 +1,386 @@ +# Live2d + +## Load assets + +1. Load setting (index) file. In `LAppModel.loadAssets` + ```ts + fetch(`${modelHomePath}${jsonFileName}`) + ``` + - `modelHomePath` : `\Samples\Resources\Haru\` + - `jsonFileName` : `Haru.model3.json` +1. Load CubismModel + ```ts + fetch(`${modelHomePath}${modelFileName}`); + ``` + - `modelFileName` : `modelSetting.getModelFileName()` + - Result : + ```ts + CubismUserModel.loadModel(arrayBuffer) + ``` +1. Load CubismExpression + ```ts + const count: number = modelSetting.getExpressionCount(); + for (let i = 0; i < count; i++) { + const expressionFileName = modelSetting.getExpressionFileName(i); + fetch(`${modelHomePath}${expressionFileName}`); + } + ``` + - Result : + ```ts + const motion: ACubismMotion = this.loadExpression( + arrayBuffer, + arrayBuffer.byteLength, + expressionName + ); + // More : update expressions + ``` +1. Load CubismPhysics + ```ts + fetch(`${modelHomePath}${physicsFileName}`); + ``` + - `physicsFileName` : `modelSetting.getPhysicsFileName()` + - Result : + ```ts + CubismUserModel.loadPhysics(arrayBuffer) + ``` +1. Load CubismPose + ```ts + fetch(`${modelHomePath}${poseFileName}`); + ``` + - `poseFileName` : `modelSetting.getPoseFileName()` + - Result : + ```ts + CubismUserModel.loadPose(arrayBuffer) + ``` +1. Setup EyeBlink +1. Setup Breath +1. Load UserData + ```ts + fetch(`${modelHomePath}${userDataFileName}`); + ``` + - `userDataFileName` : `modelSetting.getUserDataFile()` + - Result : + ```ts + CubismUserModel.loadUserData(arrayBuffer) + ``` +1. Setup EyeBlinkIds +1. Setup LipSyncIds +1. Setup Layout +1. Load CubismMotion + ```ts + const motionGroupCount: number = this._modelSetting.getMotionGroupCount(); + const group: string[] = []; + for (let i = 0; i < motionGroupCount; i++) { + group[i] = modelSetting.getMotionGroupName(i); + fetch(`${modelHomePath}${expressionFileName}`); + } + ``` + - Result : + ```ts + const motion: ACubismMotion = this.loadExpression( + arrayBuffer, + arrayBuffer.byteLength, + expressionName + ); + // More : update expressions + ``` +1. Load texture + ```ts + const textureCount: number = this._modelSetting.getTextureCount(); + for ( + let modelTextureNumber = 0; + modelTextureNumber < textureCount; + modelTextureNumber++ + ) { + if (this._modelSetting.getTextureFileName(modelTextureNumber) == '') { + console.log('getTextureFileName null'); + continue; + } + + let texturePath = + this._modelSetting.getTextureFileName(modelTextureNumber); + texturePath = this._modelHomeDir + texturePath; + + } + ``` + +## Update + +1. `LAppDelegate.run()` +1. `LAppPal.updateTime()` +1. `LAppView.render()` +1. `LAppLive2DManager.onUpdate()` +1. `LAppModel.update()` + ```ts + this._model.loadParameters(); // 前回セーブされた状態をロード + if (this._motionManager.isFinished()) { + // モーションの再生がない場合、待機モーションの中からランダムで再生する + this.startRandomMotion( + LAppDefine.MotionGroupIdle, + LAppDefine.PriorityIdle + ); + } else { + motionUpdated = this._motionManager.updateMotion( + this._model, + deltaTimeSeconds + ); // モーションを更新 + } + this._model.saveParameters(); // 状態を保存 + ``` + +### Dragging interactive + +```ts + // ドラッグによる変化 + // ドラッグによる顔の向きの調整 + this._model.addParameterValueById(this._idParamAngleX, this._dragX * 30); // -30から30の値を加える + this._model.addParameterValueById(this._idParamAngleY, this._dragY * 30); + this._model.addParameterValueById( + this._idParamAngleZ, + this._dragX * this._dragY * -30 + ); + + // ドラッグによる体の向きの調整 + this._model.addParameterValueById( + this._idParamBodyAngleX, + this._dragX * 10 + ); // -10から10の値を加える + + // ドラッグによる目の向きの調整 + this._model.addParameterValueById(this._idParamEyeBallX, this._dragX); // -1から1の値を加える + this._model.addParameterValueById(this._idParamEyeBallY, this._dragY); + +``` + +### Lip sync + +## Render + +1. `LAppLive2DManager.onUpdate()` + - `projection` matrix : + ```ts + const projection: CubismMatrix44 = new CubismMatrix44(); + if(width < height) { + projection.scale(1.0, width / height); + } else { + projection.scale(height / width, 1.0); + } + + if (this._viewMatrix != null) { + projection.multiplyByMatrix(this._viewMatrix); + } + ``` + - `_viewMatrix` matrix : + - In LAppView + - Copy to LAppLive2DManager + ```ts + live2DManager.setViewMatrix(this._viewMatrix) + ``` + ```ts + this._viewMatrix = new CubismMatrix44(); + + public setViewMatrix(m: CubismMatrix44) { + for (let i = 0; i < 16; i++) { + this._viewMatrix.getArray()[i] = m.getArray()[i]; + } + } + ``` +1. `LAppModel.draw(projection)` + ```ts + projection.multiplyByMatrix(this._modelMatrix); + this.getRenderer().setMvpMatrix(projection); + this.doDraw(); + ``` + - `_modelMatrix` : Member of LAppModel/CubismUserModel + +### CubismMatrix44 + +- `matrix.loadIdentity()` : Reset +- `matrix.setMatrix(tr)` : Copy from +- `var arr = matrix.getArray()` : 1d array of 16 units +- Translate + - `var translateX = matrix.getTranslateX()` : TranslateX, `tr[12]` + - `var translateY = matrix.getTranslateY()` : TranslateX, `tr[13]` + - `matrix.translate(x, y)` + - `matrix.translateX(x)` + - `matrix.translateY(y)` + - `matrix.translateRelative(x, y)` +- Scale + - `var scaleX = matrix.getScaleX()` : ScaleX, `tr[0]` + - `var scaleY = matrix.getScaleY()` : ScaleY, `tr[5]` + - `matrix.scale(scaleX, scaleY)` + - `matrix.scaleRelative(scaleX, scaleY)` +- Transform + - `var positionX = matrix.transformX(localX)` + - `var positionY = matrix.transformY(localY)` + - `var localX = matrix.invertTransformX(positionX)` + - `var localY = matrix.invertTransformY(positionY)` +- Multiply + - `matrix.multiply(a, b, dst)` + - `matrix.multiplyByMatrix(m)` +- Rotate + - See [this article](https://learnopengl.com/Getting-started/Transformations), **Rotation** section. +- `matrix.clone()` + +### Model display matrix + +- Get model display matrix + ```ts + var modelMatrix = model.getModelMatrix() + ``` +- Change size = set scale + ```ts + modelMatrix.setWidth(width); + modelMatrix.setHeight(width); + ``` +- Set position + ```ts + modelMatrix.setPosition(x, y); + modelMatrix.setX(x); + modelMatrix.setY(y); + modelMatrix.setCenterPosition(x, y); + modelMatrix.top(y); // = setY + modelMatrix.bottom(y); + modelMatrix.left(y); // = setX + modelMatrix.right(y); + modelMatrix.centerX(y); + modelMatrix.centerY(y); + ``` + +#### Compare + +##### Init + +- SDK + ```javascript + this._modelMatrix = new CubismModelMatrix( + this._model.getCanvasWidth(), + this._model.getCanvasHeight() + ); + ``` + ```javascript + this._modelSetting.getLayoutMap(layout); + this._modelMatrix.setupFromLayout(layout); + ``` +- Pixi-live2d-display + ```javascript + protected getLayout(): CommonLayout { + // un-capitalize each key to satisfy the common layout format + // e.g. CenterX -> centerX + return mapKeys({ ...this.settings.layout }, (_, key) => key.charAt(0).toLowerCase() + key.slice(1)); + } + ``` + ```javascript + const layout = Object.assign( + { + width: 2, + height: 2, + }, + this.getLayout(), + ); + + this.localTransform.scale(layout.width / 2, layout.height / 2); + // this calculation differs from Live2D SDK... + const offsetX = (layout.x !== undefined && layout.x - layout.width / 2) + || (layout.centerX !== undefined && layout.centerX) + || (layout.left !== undefined && layout.left - layout.width / 2) + || (layout.right !== undefined && layout.right + layout.width / 2) + || 0; + const offsetY = (layout.y !== undefined && layout.y - layout.height / 2) + || (layout.centerY !== undefined && layout.centerY) + || (layout.top !== undefined && layout.top - layout.height / 2) + || (layout.bottom !== undefined && layout.bottom + layout.height / 2) + || 0; + this.localTransform.translate(this.width * offsetX, -this.height * offsetY); + ``` + ```javascript + (this as Mutable).pixelsPerUnit = this.coreModel.getModel().canvasinfo.PixelsPerUnit; + // move the origin from top left to center + this.centeringTransform + .scale(this.pixelsPerUnit, this.pixelsPerUnit) + .translate(this.originalWidth / 2, this.originalHeight / 2); + ``` + + + +## Hit test + +```ts +const x: number = this._deviceToScreen.transformX( + this._touchManager.getX() +); // 論理座標変換した座標を取得。 +const y: number = this._deviceToScreen.transformY( + this._touchManager.getY() +); // 論理座標変化した座標を取得。 +``` + +```ts +const { width, height } = canvas; +const ratio: number = width / height; +const left: number = -ratio; +const right: number = ratio; +const bottom: number = LAppDefine.ViewLogicalLeft; +const top: number = LAppDefine.ViewLogicalRight; +``` + +```ts +this._deviceToScreen.loadIdentity(); +if (width > height) { + const screenW: number = Math.abs(right - left); + this._deviceToScreen.scaleRelative(screenW / width, -screenW / width); +} else { + const screenH: number = Math.abs(top - bottom); + this._deviceToScreen.scaleRelative(screenH / height, -screenH / height); +} +this._deviceToScreen.translateRelative(-width * 0.5, -height * 0.5); +``` + +```ts +hitTest(hitAreaName, x, y) +``` + +- `hitAreaName` : `'Head'`, `'Body'` + +```ts +for (let i = 0; i < count; i++) { + if (this._modelSetting.getHitAreaName(i) == hitArenaName) { + const drawId: CubismIdHandle = this._modelSetting.getHitAreaId(i); + return this.isHit(drawId, x, y); + } +} +``` + +```ts +CubismUserModel.isHit(drawableId,pointX,pointY); +``` + +```ts +this._modelMatrix = new CubismModelMatrix( + this._model.getCanvasWidth(), + this._model.getCanvasHeight() +); +``` + +```ts +public getCanvasWidth(): number { + if (this._model == null) { + return 0.0; + } + return ( + this._model.canvasinfo.CanvasWidth / this._model.canvasinfo.PixelsPerUnit + ); +} +``` + +```ts +public getCanvasHeight(): number { + if (this._model == null) { + return 0.0; + } + return ( + this._model.canvasinfo.CanvasHeight / this._model.canvasinfo.PixelsPerUnit + ); +} +``` + + diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/utils/Initialize.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/utils/Initialize.js new file mode 100644 index 000000000..b307f367f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/live2d/utils/Initialize.js @@ -0,0 +1,20 @@ +import { CubismFramework, Option } from '../framework/src/live2dcubismframework'; + +// Invoke this method after loading live2dcubismcore.js, and before loading any model asset. +var Initialize = function (config) { + if (!window.Live2DCubismCore) { + console.error('live2dcubismcore.js does not load') + } + + // Setup cubism + var option = new Option(); + // TODO: option.logFunction, option.loggingLevel + CubismFramework.startUp(option); + + // Initialize cubism + CubismFramework.initialize(); + + // TODO: More... +} + +export default Initialize; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Card.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Card.d.ts new file mode 100644 index 000000000..354f555cd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Card.d.ts @@ -0,0 +1,94 @@ +// import * as Phaser from 'phaser'; +import FaceContainer from '../utils/FaceContainer'; +import Image from '../image/Image'; +import RenderTexture from '../rendertexture/RenderTexture'; + +export default Card; + +declare namespace Card { + + type FaceTypes = 0 | 1 | 'front' | 'back'; + + type FaceDefType = { key: string, frame?: string } | + { width: number, height: number } | + Image | + RenderTexture; + + type OrientationTypes = 0 | 1 | 'x' | 'y' | 'horizontal' | 'vertical'; + + type FlipDirTypes = 0 | 1 | 'right' | 'left' | 'left-to-right' | 'right-to-left'; + interface IConfigFlip { + frontToBack?: FlipDirTypes, + backToFront?: FlipDirTypes, + duration?: number, + ease?: string, + delay?: number, + } + + interface IConfig { + x?: number, + y?: number, + width?: number, + height?: number, + + face?: FaceTypes, + back?: FaceDefType, + front?: FaceDefType, + + orientation?: OrientationTypes, + + flip?: IConfigFlip | false, + } + + namespace Events { + type FlipCompleteCallbackType = () => void; + } + + class Flip extends Phaser.Events.EventEmitter { + flip( + duration?: number, + repeat?: number + ): this; + + flipRight( + duration?: number, + repeat?: number + ): this; + + flipLeft( + duration?: number, + repeat?: number + ): this; + + stop(): this; + + setDuration(duration: number): this; + duration: number; + + setEase(ease: string): this; + ease: string; + + readonly isRunning: boolean; + } + +} + +declare class Card extends FaceContainer { + constructor( + scene: Phaser.Scene, + config?: Card.IConfig + ); + + setFace(face: Card.FaceTypes): this; + toggleFace(): this; + face: number; + + frontFace: Image | RenderTexture; + backFace: Image | RenderTexture; + faces: { + front: Image | RenderTexture, + back: Image | RenderTexture, + }; + + flip: Card.Flip | undefined; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Card.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Card.js new file mode 100644 index 000000000..d6c2af815 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Card.js @@ -0,0 +1,161 @@ +import FaceContainer from '../utils/FaceContainer.js'; +import CreateFaces from '../utils/CreateFaces.js'; +import ForEachFace from '../utils/ForEachFace.js'; +import LayoutFaces from './LayoutFaces.js'; +import Flip from './Flip.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +const FaceNames = ['back', 'front']; + +class Card extends FaceContainer { + constructor(scene, x, y, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + } + + var faces = CreateFaces(scene, config, FaceNames); + var backFace = faces.back; + var frontFace = faces.front; + + var width = GetValue(config, 'width'); + var height = GetValue(config, 'height'); + if ((width === undefined) || (height === undefined)) { + if (width === undefined) { + var frontFaceWidth = (frontFace) ? frontFace.width : 0; + var backFaceWidth = (backFace) ? backFace.width : 0; + width = Math.max(frontFaceWidth, backFaceWidth); + } + + if (height === undefined) { + var frontFaceHeight = (frontFace) ? frontFace.height : 0; + var backFaceHeight = (backFace) ? backFace.height : 0; + height = Math.max(frontFaceHeight, backFaceHeight); + } + } + + super(scene, x, y, width, height, faces); + this.type = 'rexPerspectiveCard'; + + this.frontFaceRotationX = 0; + this.frontFaceRotationY = 0; + this.frontFaceRotationZ = 0; + + ForEachFace(faces, function (face, name) { + this[`${name}Face`] = face; + }, this); + + var flipConfig = GetValue(config, 'flip', undefined); + if (flipConfig !== false) { + this.flip = new Flip(this, flipConfig); + } + + this.setOrientation(GetValue(config, 'orientation', 0)); + LayoutFaces(this, faces); + + this.setFace(GetValue(config, 'face', 0)); + } + + get rotationX() { + return this.frontFaceRotationX; + } + + set rotationX(value) { + if (this.frontFaceRotationX === value) { + return; + } + + this.frontFaceRotationX = value; + ForEachFace(this.faces, function (face) { + face.rotationX = value; + }, null, true); + } + + get rotationY() { + return this.frontFaceRotationY; + } + + set rotationY(value) { + if (this.frontFaceRotationY === value) { + return; + } + + this.frontFaceRotationY = value; + ForEachFace(this.faces, function (face) { + face.rotationY = value; + }, null, true); + } + + get rotationZ() { + return this.frontFaceRotationZ; + } + + set rotationZ(value) { + if (this.frontFaceRotationZ === value) { + return; + } + + this.frontFaceRotationZ = value; + ForEachFace(this.faces, function (face) { + face.rotationZ = value; + }, null, true); + } + + setOrientation(orientation) { + if (typeof (orientation) === 'string') { + orientation = ORIENTATIONMODE[orientation]; + } + this.orientation = orientation; + return this; + } + + get face() { + return this.currentFaceIndex; + } + + set face(index) { + if (typeof (index) === 'string') { + index = FACEMODE[index]; + } + this.currentFaceIndex = index; + + var isBackFace = (index === 1); + var angle = (isBackFace) ? 180 : 0; + if (this.orientation === 0) { // Flip around Y + this.angleY = angle; + } else { // Flip around X + this.angleX = angle; + } + } + + setFace(face) { + this.face = face; + return this; + } + + toggleFace() { + var newFace = (this.face === 0) ? 1 : 0; + this.setFace(newFace); + return this; + } +} + +const ORIENTATIONMODE = { + x: 0, + horizontal: 0, + h: 0, + + y: 1, + vertical: 1, + v: 1 +} + +const FACEMODE = { + front: 0, + back: 1, +} + +export default Card; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Creator.js new file mode 100644 index 000000000..8dc3f36a3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Creator.js @@ -0,0 +1,14 @@ +import Card from './Card.js'; + +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var gameObject = new Card(this.scene, 0, 0, config); + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Factory.js new file mode 100644 index 000000000..55b26d405 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Factory.js @@ -0,0 +1,7 @@ +import Card from './Card.js'; + +export default function (x, y, config) { + var gameObject = new Card(this.scene, x, y, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Flip.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Flip.js new file mode 100644 index 000000000..d6ac9e891 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/Flip.js @@ -0,0 +1,122 @@ +import EaseValueTaskBase from '../../../../utils/componentbase/tweentask/EaseValueTaskBase.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Linear = Phaser.Math.Linear; + +class Flip extends EaseValueTaskBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + // this.timer + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + this.setEase(GetValue(o, 'ease', 'Cubic')); + + this.setFrontToBackDirection(GetValue(o, 'frontToBack', 0)); + this.setBackToFrontDirection(GetValue(o, 'backToFront', 1)); + return this; + } + + setFrontToBackDirection(direction) { + if (typeof (direction) === 'string') { + direction = DIRMODE[direction]; + } + this.endAngleFB = (direction === 0) ? 180 : -180; + return this; + } + + setBackToFrontDirection(direction) { + if (typeof (direction) === 'string') { + direction = DIRMODE[direction]; + } + this.endAngleBF = (direction === 0) ? -180 : 180; + return this; + } + + start(duration, repeat) { + if (this.timer.isRunning) { + return this; + } + + this.timer + .setDelay(this.delay) + .setDuration(duration); + + var loop = repeat + 1; + var gameObject = this.parent; + if (gameObject.face === 0) { // isFrontToBack + this.startAngle = 0; + this.endAngle = this.endAngleFB * loop; + } else { + this.startAngle = this.endAngleBF; + this.endAngle = this.startAngle - (this.endAngleBF * loop); + } + + super.start(); + return this; + } + + flip(duration, repeat) { + if (this.isRunning) { + return this; + } + if (duration === undefined) { + duration = this.duration; + } + if (repeat === undefined) { + repeat = 0; + } + + this.start(duration, repeat); + this.emit('start', this.parent, this); + + // Set face index + this.parent.currentFaceIndex = (this.parent.currentFaceIndex + repeat + 1) % 2; + return this; + } + + flipRight(duration, repeat) { + if (this.parent.currentFaceIndex === 0) { // Front to back + this.setFrontToBackDirection(0); + } else { // Back to front + this.setBackToFrontDirection(0); + } + this.flip(duration, repeat); + return this; + } + + flipLeft(duration, repeat) { + if (this.parent.currentFaceIndex === 0) { // Front to back + this.setFrontToBackDirection(1); + } else { // Back to front + this.setBackToFrontDirection(1); + } + this.flip(duration, repeat); + return this; + } + + updateGameObject(gameObject, timer) { + var t = this.easeFn(timer.t); + + var value = Linear(this.startAngle, this.endAngle, t); + if (gameObject.orientation === 0) { + gameObject.angleY = value; + } else { + gameObject.angleX = value; + } + } +} + +const DIRMODE = { + 'right': 0, + 'left-to-right': 0, + 'left': 1, + 'right-to-left': 1 +} + +export default Flip; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/LayoutFaces.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/LayoutFaces.js new file mode 100644 index 000000000..bec3ff15a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/card/LayoutFaces.js @@ -0,0 +1,16 @@ +const DegToRad = Phaser.Math.DegToRad; + +const RAD180 = DegToRad(180); + +var LayoutFaces = function (parent, faces) { + var backFace = faces.back; + if (backFace) { + if (parent.orientation === 0) { // Flip around Y + backFace.transformVerts(0, 0, 0, 0, RAD180, 0); + } else { // Flip around X + backFace.transformVerts(0, 0, 0, RAD180, 0, 0); + } + } +} + +export default LayoutFaces; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Carousel.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Carousel.d.ts new file mode 100644 index 000000000..d260e2107 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Carousel.d.ts @@ -0,0 +1,72 @@ +// import * as Phaser from 'phaser'; +import FaceContainer from '../utils/FaceContainer'; +import Card from '../card/Card'; +import Image from '../image/Image'; +import RenderTexture from '../rendertexture/RenderTexture'; + +export default Carousel; + +declare namespace Carousel { + + interface IConfigRoll { + duration?: number, + ease?: string, + delay?: number, + } + + interface IConfig { + x?: number, + y?: number, + + faces?: (Card | Image | RenderTexture)[], + face?: number, + rtl?: boolean, + + width?: number, + height?: number, + + faceWidth?: number, + faceSpace?: number, + + z?: number, + zEnd?: number, + + roll?: IConfigRoll | false, + } + + class Roll extends Phaser.Events.EventEmitter { + toNext(duration?: number): this; + toPrevious(duration?: number): this; + toRight(duration?: number): this; + toLeft(duration?: number): this; + to(faceIndex: number, duration?: number): this; + stop(): this; + + setDuration(duration: number): this; + duration: this; + + setEase(ease: string): this + ease: string; + + readonly isRunning: boolean; + } + + namespace Events { + type RollCompleteCallbackType = () => void; + } +} + +declare class Carousel extends FaceContainer { + constructor( + scene: Phaser.Scene, + config?: Carousel.IConfig + ); + + faces: (Card | Image | RenderTexture)[]; + + setFace(face: number): this; + face: number; + + roll: Carousel.Roll | undefined; +} + diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Carousel.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Carousel.js new file mode 100644 index 000000000..dc618663b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Carousel.js @@ -0,0 +1,130 @@ +import FaceContainer from '../utils/FaceContainer.js'; +import Roll from './Roll.js'; +import CreateFaces from '../utils/CreateFaces.js'; +import ForEachFace from '../utils/ForEachFace.js'; +import GetFirstFace from './GetFirstFace.js'; +import LayoutFaces from './LayoutFaces.js'; +import FaceNameToIndex from './FaceNameToIndex.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const DegToRad = Phaser.Math.DegToRad; +const RadToDeg = Phaser.Math.RadToDeg; +const WrapDegrees = Phaser.Math.Angle.WrapDegrees; +const Linear = Phaser.Math.Linear; +const Wrap = Phaser.Math.Wrap; + +class Carousel extends FaceContainer { + constructor(scene, x, y, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + } + + var faceConfig = GetValue(config, 'faces', undefined); + if (!faceConfig) { + faceConfig = []; + } + var faces = CreateFaces(scene, faceConfig); + var firstFace = GetFirstFace(faces); + + var width = GetValue(config, 'width'); + var height = GetValue(config, 'height'); + if (width === undefined) { + width = (firstFace) ? firstFace.width : 0; + } + if (height === undefined) { + height = (firstFace) ? firstFace.height : 0; + } + + super(scene, x, y, width, height, faces); + this.type = 'rexPerspectiveCarousel'; + + this.face0RotationY = undefined; + + var faceCount = faces.length; + // Face angle + this.faceAngle = (faceCount > 0) ? DegToRad(360 / faces.length) : 0; + + // Face width, face radius + var faceWidth = GetValue(config, 'faceWidth', undefined); + if (faceWidth === undefined) { + var faceSpace = GetValue(config, 'faceSpace', 0); + faceWidth = (firstFace) ? (firstFace.width + faceSpace) : 0; + } + this.faceWidth = faceWidth; + if (faceCount > 2) { + this.faceRadius = (faceWidth / 2) / Math.tan(this.faceAngle / 2); + } else { + this.faceRadius = faceWidth / 2; + } + + LayoutFaces(this, faces); + + var rollConfig = GetValue(config, 'roll', undefined); + if (rollConfig !== false) { + var RollClass = GetValue(config, 'rollClass', Roll); + this.roll = new RollClass(this, rollConfig); + } + + // Left-To-Right, or Right-To-Left + this.rtl = GetValue(config, 'rtl', false); + + // z-index + this.zStart = GetValue(config, 'z', 1); + this.zEnd = GetValue(config, 'zEnd', this.zStart - 1); + + this.setFace(GetValue(config, 'face', 0)); + } + + get rotationY() { + return this.face0RotationY; + } + + set rotationY(value) { + if (this.face0RotationY === value) { + return; + } + + this.face0RotationY = value; + var deltaAngle = this.faceAngle; + var zStart = this.zStart; + var zEnd = this.zEnd; + var sign = (this.rtl) ? -1 : 1; + ForEachFace(this.faces, function (face, i) { + // Set rotationY + var rotationY = value + (sign * deltaAngle * i); + face.rotationY = rotationY; + + // Set depth + var angle = Math.abs(WrapDegrees(RadToDeg(rotationY))); // 0~180 + var z = Linear(zStart, zEnd, angle / 180); + face.setDepth(z); + }, null, true); + } + + get face() { + return this.currentFaceIndex; + } + + set face(index) { + if (typeof (index) === 'string') { + index = FaceNameToIndex(this.faces, index); + if (index === -1) { + index = 0; + } + } + + index = Wrap(index, 0, this.faces.length); + this.currentFaceIndex = index; + this.rotationY = ((this.rtl) ? 1 : -1) * this.faceAngle * index; + } + + setFace(index) { + this.face = index; + return this; + } +} + +export default Carousel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Creator.js new file mode 100644 index 000000000..0ed01426c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Creator.js @@ -0,0 +1,14 @@ +import Carousel from './Carousel.js'; + +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var gameObject = new Carousel(this.scene, 0, 0, config); + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/FaceNameToIndex.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/FaceNameToIndex.js new file mode 100644 index 000000000..b6fb6514a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/FaceNameToIndex.js @@ -0,0 +1,10 @@ +var FaceNameToIndex = function (faces, name) { + for (var i = 0, cnt = faces.length; i < cnt; i++) { + if (face && (face.name === name)) { + return i; + } + } + return -1; +} + +export default FaceNameToIndex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Factory.js new file mode 100644 index 000000000..ed9d5da69 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Factory.js @@ -0,0 +1,7 @@ +import Carousel from './Carousel.js'; + +export default function (x, y, config) { + var gameObject = new Carousel(this.scene, x, y, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/GetFirstFace.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/GetFirstFace.js new file mode 100644 index 000000000..024f5ca1e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/GetFirstFace.js @@ -0,0 +1,12 @@ +var GetFirstFace = function (faces) { + var face; + for (var i = 0, cnt = faces.length; i < cnt; i++) { + face = faces[i]; + if (face) { + break; + } + } + return face; +} + +export default GetFirstFace \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/LayoutFaces.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/LayoutFaces.js new file mode 100644 index 000000000..e2ae43452 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/LayoutFaces.js @@ -0,0 +1,18 @@ +import ForEachFace from '../utils/ForEachFace.js'; + +var LayoutFaces = function (parent, faces) { + if (parent.faceWidth === 0) { + return; + } + + var radius = parent.faceRadius; + ForEachFace(faces, function (face) { + var transferZ = radius / face.height; + face + .transformVerts(0, 0, transferZ) + .panZ(transferZ); + + }, null, true); +} + +export default LayoutFaces; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Roll.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Roll.js new file mode 100644 index 000000000..ea91c8c5b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/carousel/Roll.js @@ -0,0 +1,109 @@ +import EaseValueTaskBase from '../../../../utils/componentbase/tweentask/EaseValueTaskBase.js'; +import FaceNameToIndex from './FaceNameToIndex.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const RadToDeg = Phaser.Math.RadToDeg; +const DegToRad = Phaser.Math.DegToRad; +const WrapDegrees = Phaser.Math.Angle.WrapDegrees; +const ShortestBetween = Phaser.Math.Angle.ShortestBetween; +const Wrap = Phaser.Math.Wrap; +const Linear = Phaser.Math.Linear; + +class Roll extends EaseValueTaskBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + // this.timer + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + this.setEase(GetValue(o, 'ease', 'Cubic')); + return this; + } + + start(deltaRotation) { + if (this.timer.isRunning) { + return this; + } + + this.timer + .setDelay(this.delay) + .setDuration(this.duration); + + var gameObject = this.parent; + this.startRotationY = gameObject.rotationY; + this.endRotationY = this.startRotationY + deltaRotation; + + super.start(); + return this; + } + + to(index, duration) { + if (this.isRunning) { + return this; + } + + var carousel = this.parent; + + if (typeof (index) === 'string') { + index = FaceNameToIndex(carousel.faces, index); + if (index === -1) { + index = 0; + } + } + index = Wrap(index, 0, carousel.faces.length); + + if (duration !== undefined) { + this.setDuration(duration); + } + + var start = WrapDegrees(RadToDeg(carousel.rotationY)); + var end = WrapDegrees(RadToDeg(((carousel.rtl) ? 1 : -1) * carousel.faceAngle * index)); + var delta = ShortestBetween(start, end); // Degrees + this.start(DegToRad(delta)); + + carousel.currentFaceIndex = index; + return this; + } + + toNext(duration) { + var index = this.parent.currentFaceIndex + 1; + this.to(index, duration); + return this; + } + + toPrevious(duration) { + var index = this.parent.currentFaceIndex - 1; + this.to(index, duration); + return this; + } + + toRight(duration) { + if (!this.parent.rtl) { + this.toNext(duration); + } else { + this.toPrevious(duration); + } + return this; + } + + toLeft(duration) { + if (!this.parent.rtl) { + this.toPrevious(duration); + } else { + this.toNext(duration); + } + return this; + } + + updateGameObject(gameObject, timer) { + var t = this.easeFn(timer.t); + gameObject.rotationY = Linear(this.startRotationY, this.endRotationY, t); + } +} + +export default Roll; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Creator.js new file mode 100644 index 000000000..5ee4f4895 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Creator.js @@ -0,0 +1,17 @@ +import Image from './Image.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var key = GetAdvancedValue(config, 'key', null); + var frame = GetAdvancedValue(config, 'frame', null); + var gameObject = new Image(this.scene, 0, 0, key, frame, config); + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Factory.js new file mode 100644 index 000000000..ce4f317ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Factory.js @@ -0,0 +1,7 @@ +import Image from './Image.js'; + +export default function (x, y, texture, frame, config) { + var gameObject = new Image(this.scene, x, y, texture, frame, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Image.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Image.d.ts new file mode 100644 index 000000000..a010b7426 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Image.d.ts @@ -0,0 +1,47 @@ +// import * as Phaser from 'phaser'; + +export default Image; + +declare namespace Image { + + interface IConfig { + x: number, y: number, + key?: string, + frame?: string, + hideCCW?: boolean, + gridWidth?: number, + girdHeight?: number + } + +} + +declare class Image extends Phaser.GameObjects.Mesh { + constructor( + scene: Phaser.Scene, + x?: number | Image.IConfig, + y?: number, + key?: string, + frame?: string | null, + config?: Image.IConfig + ) + + readonly originX: number; + readonly originY: number; + readonly displayOriginX: number; + readonly displayOriginY: number; + + transformVerts( + x?: number, y?: number, z?: number, + rotateX?: number, rotateY?: number, rotateZ?: number + ): this; + + angleX: number; + angleY: number; + angleZ: number; + rotationX: number; + rotationY: number; + rotationZ: number; + + setTint(color: number): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Image.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Image.js new file mode 100644 index 000000000..1e22a0d29 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/image/Image.js @@ -0,0 +1,182 @@ +import TransformVerts from '../utils/TransformVerts'; + +const Mesh = Phaser.GameObjects.Mesh; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const GenerateGridVerts = Phaser.Geom.Mesh.GenerateGridVerts; +const RadToDeg = Phaser.Math.RadToDeg; +const DegToRad = Phaser.Math.DegToRad; + +const FOV = 45; +const PanZ = 1 + (1 / Math.sin(DegToRad(FOV))); + +class Image extends Mesh { + constructor(scene, x, y, key, frame, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + key = GetValue(config, 'key', null); + frame = GetValue(config, 'frame', null); + } + + super(scene, x, y, key, frame); + this.type = 'rexPerspectiveImage'; + this.setSizeToFrame(); + + this.resetPerspective(); + this.panZ(PanZ); + this.hideCCW = GetValue(config, 'hideCCW', true); + + var gridWidth = GetValue(config, 'gridWidth', 32); + var gridHeight = GetValue(config, 'gridHeight', gridWidth); + this.resetVerts(gridWidth, gridHeight); + } + + get originX() { + return 0.5; + } + + get originY() { + return 0.5; + } + + resetPerspective() { + this.setPerspective(this.width, this.height, FOV); + return this; + } + + resetVerts(gridWidth, gridHeight) { + if (gridWidth !== undefined) { + this.gridWidth = gridWidth; + } + if (gridHeight !== undefined) { + this.gridHeight = gridHeight; + } + + // Clear faces and vertices + this.clear(); + this.dirtyCache[9] = -1; + if ((this.width === 0) || (this.height === 0)) { + return this; + } + + // Generate faces and vertices + var frameWidth = this.frame.cutWidth, + frameHeight = this.frame.cutHeight; + GenerateGridVerts({ + mesh: this, + texture: this.texture.key, frame: this.frame.name, + + width: frameWidth / this.height, + height: frameHeight / this.height, + + widthSegments: Math.ceil(frameWidth / this.gridWidth), + heightSegments: Math.ceil(frameHeight / this.gridHeight), + }); + + // Recover vertices transform + var transformInfo = this.transformInfo; + if (transformInfo) { + this.transformVerts( + transformInfo.x, transformInfo.y, transformInfo.z, + transformInfo.rotateX, transformInfo.rotateY, transformInfo.rotateZ + ); + } + + return this; + } + + syncSize() { + this.setSizeToFrame(); // Reset size + this.resetPerspective(); // Reset perspective + this.resetVerts(); // Reset verts + return this; + } + + get rotationX() { + return this.modelRotation.x; + } + + set rotationX(value) { + this.modelRotation.x = value; + } + + get angleX() { + return RadToDeg(this.rotationX); + } + + set angleX(value) { + this.rotationX = DegToRad(value); + } + + get rotationY() { + return this.modelRotation.y; + } + + set rotationY(value) { + this.modelRotation.y = value; + } + + get angleY() { + return RadToDeg(this.rotationY); + } + + set angleY(value) { + this.rotationY = DegToRad(value); + } + + get rotationZ() { + return this.modelRotation.z; + } + + set rotationZ(value) { + this.modelRotation.z = value; + } + + get angleZ() { + return RadToDeg(this.rotationZ); + } + + set angleZ(value) { + this.rotationZ = DegToRad(value); + } + + transformVerts(x, y, z, rotateX, rotateY, rotateZ) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (z === undefined) { z = 0; } + if (rotateX === undefined) { rotateX = 0; } + if (rotateY === undefined) { rotateY = 0; } + if (rotateZ === undefined) { rotateZ = 0; } + + if (!this.transformInfo) { + this.transformInfo = {}; + } + + this.transformInfo.x = x; + this.transformInfo.y = y; + this.transformInfo.rotateX = rotateX; + this.transformInfo.rotateY = rotateY; + this.transformInfo.rotateZ = rotateZ; + + TransformVerts(this, x, y, z, rotateX, rotateY, rotateZ); + return this; + } + + forceUpdate() { + this.dirtyCache[10] = 1; + return this; + } + + get tint() { + if (this.vertices.length === 0) { + return 0xffffff; + } else { + return this.vertices[0].color; + } + } + +} + +export default Image; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/Creator.js new file mode 100644 index 000000000..747be9d3f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/Creator.js @@ -0,0 +1,14 @@ +import ImageCarousel from './ImageCarousel.js'; + +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var gameObject = new ImageCarousel(this.scene, config); + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/Factory.js new file mode 100644 index 000000000..fa7bbd697 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/Factory.js @@ -0,0 +1,7 @@ +import ImageCarousel from './ImageCarousel.js'; + +export default function (x, y, config) { + var gameObject = new ImageCarousel(this.scene, x, y, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/GetFaceSize.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/GetFaceSize.js new file mode 100644 index 000000000..5395d2b2b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/GetFaceSize.js @@ -0,0 +1,19 @@ +var GetFaceSize = function (scene, images) { + if (!images) { + return null; + } + if (Array.isArray(images)) { + var textureKey = images[0]; + var frame = scene.sys.textures.getFrame(textureKey.key, textureKey.frame); + result.width = frame.cutWidth; + result.height = frame.cutHeight; + } else { + result.width = images.width; + result.height = images.height; + } + return result; +} + +var result = {}; + +export default GetFaceSize; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/GetIndexOffsetMap.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/GetIndexOffsetMap.js new file mode 100644 index 000000000..6cf42ce41 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/GetIndexOffsetMap.js @@ -0,0 +1,9 @@ +var GetIndexOffsetMap = function (faceCount) { + var indexOffsetMap = [0]; + for (var i = 1, cnt = Math.floor((faceCount - 1) / 2); i <= cnt; i++) { + indexOffsetMap.push(i); + indexOffsetMap.push(-i); + } + return indexOffsetMap; +} +export default GetIndexOffsetMap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/ImageCarousel.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/ImageCarousel.d.ts new file mode 100644 index 000000000..638ed49cf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/ImageCarousel.d.ts @@ -0,0 +1,36 @@ +import Carousel from "../carousel/Carousel"; + +export default ImageCarousel; + +declare namespace ImageCarousel { + + interface IConfig { + x?: number, + y?: number, + + images?: ({ key: string, frame?: string })[], + index?: number, + rtl?: boolean, + repeat?: boolean, + + width?: number, + height?: number, + faceCount?: number, + + z?: number, + zEnd?: number, + + roll?: Carousel.IConfigRoll | false, + } + + namespace Events { + type RollCompleteCallbackType = () => void; + } +} + +declare class ImageCarousel extends Carousel { + constructor( + scene: Phaser.Scene, + config?: ImageCarousel.IConfig + ); +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/ImageCarousel.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/ImageCarousel.js new file mode 100644 index 000000000..f1f68306a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/ImageCarousel.js @@ -0,0 +1,86 @@ +import Carousel from '../carousel/Carousel.js'; +import Roll from './Roll.js'; +import RenderTexture from '../rendertexture/RenderTexture.js'; +import GetFaceSize from './GetFaceSize.js'; +import GetIndexOffsetMap from './GetIndexOffsetMap.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const Wrap = Phaser.Math.Wrap; + +class ImageCarousel extends Carousel { + constructor(scene, x, y, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + } + + if (config === undefined) { + config = {}; + } + + var faceWidth, faceHeight; + var images = GetValue(config, 'images'); + var faceSize = GetFaceSize(scene, images); + if (faceSize) { + faceWidth = faceSize.width; + faceHeight = faceSize.height; + } else { + faceWidth = GetValue(config, 'width'); + faceHeight = GetValue(config, 'height'); + } + + // Create 4 render-texture faces + var faceCount = GetValue(config, 'faceCount', 4); + var face, faces = []; + for (var i = 0; i < faceCount; i++) { + face = new RenderTexture(scene, 0, 0, faceWidth, faceHeight, config); + scene.add.existing(face); + faces.push(face); + } + + config.faces = faces; + config.rollClass = Roll; + super(scene, x, y, config); + this.type = 'rexPerspectiveImageCarousel'; + + this.images = images; + this.indexOffsetMap = GetIndexOffsetMap(faceCount); + this.repeat = GetValue(config, 'repeat', true); + this + .setImageIndex(GetValue(config, 'index', 0)) + .updateTexture(); + } + + setImageIndex(index) { + this.currentImageIndex = Wrap(index, 0, this.images.length); + return this; + } + + get isFirstImage() { + return (this.images.length === 0) || (this.currentImageIndex === 0); + } + + get isLastImage() { + return (this.images.length === 0) || (this.currentImageIndex === (this.images.length - 1)); + } + + updateTexture() { + var totalKeys = this.images.length; + var totalFaces = this.faces.length; + + this.indexOffsetMap.forEach(function (indexOffset) { + var textureIndex = Wrap(this.currentImageIndex + indexOffset, 0, totalKeys); + var faceIndex = Wrap(this.currentFaceIndex + indexOffset, 0, totalFaces); + + var textureKey = this.images[textureIndex]; + this.faces[faceIndex].rt.drawFrame(textureKey.key, textureKey.frame); + }, this) + + return this; + } + +} + +export default ImageCarousel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/Roll.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/Roll.js new file mode 100644 index 000000000..a7fb8b8f2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/imagecarousel/Roll.js @@ -0,0 +1,42 @@ +import Base from '../carousel/Roll.js'; + +class Roll extends Base { + + toNext(duration) { + var gameObject = this.parent; + if (!gameObject.repeat && gameObject.isLastImage) { + return this; + } + + if (this.isRunning) { + return this; + } + + gameObject.setImageIndex(gameObject.currentImageIndex + 1); + super + .toNext(duration) + .once('complete', gameObject.updateTexture, gameObject) + + return this; + } + + toPrevious(duration) { + var gameObject = this.parent; + if (!gameObject.repeat && gameObject.isFirstImage) { + return this; + } + + if (this.isRunning) { + return this; + } + + gameObject.setImageIndex(gameObject.currentImageIndex - 1); + super + .toPrevious(duration) + .once('complete', gameObject.updateTexture, gameObject) + + return this; + } +} + +export default Roll; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/Creator.js new file mode 100644 index 000000000..390131750 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/Creator.js @@ -0,0 +1,19 @@ +import RenderTexture from './RenderTexture.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var x = GetAdvancedValue(config, 'x', 0); + var y = GetAdvancedValue(config, 'y', 0); + var width = GetAdvancedValue(config, 'width', 32); + var height = GetAdvancedValue(config, 'height', 32); + var gameObject = new RenderTexture(this.scene, x, y, width, height, config); + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/Factory.js new file mode 100644 index 000000000..01c3e84e5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/Factory.js @@ -0,0 +1,7 @@ +import RenderTexture from './RenderTexture.js'; + +export default function (x, y, width, height, config) { + var gameObject = new RenderTexture(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/RenderTexture.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/RenderTexture.d.ts new file mode 100644 index 000000000..f46a9673b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/RenderTexture.d.ts @@ -0,0 +1,35 @@ +// import * as Phaser from 'phaser'; +import Image from '../image/Image'; +export default RenderTexture; + +declare namespace RenderTexture { + + interface IConfig extends Image.IConfig { + width?: number, height?: number, + } + + interface SnapshotIConfig { + x?: number, y?: number, + width?: number, height?: number, + padding?: number, + } + +} + +declare class RenderTexture extends Image { + constructor( + scene: Phaser.Scene, + x?: number | RenderTexture.IConfig, + y?: number, + width?: number, + height?: number, + config?: RenderTexture.IConfig + ) + + readonly rt: Phaser.GameObjects.RenderTexture; + + snapshot( + gameObjects: Phaser.GameObjects.GameObject[], + config?: RenderTexture.SnapshotIConfig + ): this; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/RenderTexture.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/RenderTexture.js new file mode 100644 index 000000000..3f9cfc2a9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/rendertexture/RenderTexture.js @@ -0,0 +1,55 @@ +import Image from '../image/Image.js'; +import CreateDynamicTexture from '../../../../utils/rendertexture/CreateDynamicTexture.js'; +import Snapshot from '../../../../utils/rendertexture/Snapshot.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class RenderTexture extends Image { + constructor(scene, x, y, width, height, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 32); + height = GetValue(config, 'height', 32); + } + + // dynamic-texture -> quad-image + var texture = CreateDynamicTexture(scene, width, height); + + super(scene, x, y, texture, null, config); + this.type = 'rexPerspectiveRenderTexture'; + this.rt = this.texture; + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + super.destroy(fromScene); + + this.rt.destroy(); + this.rt = null; + } + + snapshot(gameObjects, config) { + if (config === undefined) { + config = {}; + } + config.gameObjects = gameObjects; + config.renderTexture = this.rt; + + Snapshot(config); + + if ((this.width !== this.frame.realWidth) || (this.height !== this.frame.realHeight)) { + this.syncSize(); + } + + return this; + } +} + +export default RenderTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Creator.js new file mode 100644 index 000000000..7366aa8d4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Creator.js @@ -0,0 +1,17 @@ +import Sprite from './Sprite.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var key = GetAdvancedValue(config, 'key', null); + var frame = GetAdvancedValue(config, 'frame', null); + var gameObject = new Sprite(this.scene, 0, 0, key, frame, config); + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Factory.js new file mode 100644 index 000000000..9b1bc3b7d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Factory.js @@ -0,0 +1,7 @@ +import Sprite from './Sprite.js'; + +export default function (x, y, texture, frame, config) { + var gameObject = new Sprite(this.scene, x, y, texture, frame, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Sprite.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Sprite.d.ts new file mode 100644 index 000000000..16a25607e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Sprite.d.ts @@ -0,0 +1,62 @@ +// import * as Phaser from 'phaser'; +import Image from '../image/Image'; + +export default Sprite; + +declare namespace Sprite { + + interface IConfig extends Image.IConfig { + } + +} + +declare class Sprite extends Image { + constructor( + scene: Phaser.Scene, + x?: number | Sprite.IConfig, + y?: number, + key?: string, + frame?: string | null, + config?: Sprite.IConfig + ) + + play( + key: string | Phaser.Animations.Animation | Phaser.Types.Animations.PlayAnimationConfig, + ignoreIfPlaying?: boolean + ): this; + + playReverse( + key: string | Phaser.Animations.Animation | Phaser.Types.Animations.PlayAnimationConfig, + ignoreIfPlaying?: boolean + ): this; + + playAfterDelay( + key: string | Phaser.Animations.Animation | Phaser.Types.Animations.PlayAnimationConfig, + delay: number + ): this; + + playAfterRepeat( + key: string | Phaser.Animations.Animation | Phaser.Types.Animations.PlayAnimationConfig, + repeatCount?: number + ): this; + + chain( + key: string | Phaser.Animations.Animation | Phaser.Types.Animations.PlayAnimationConfig | string[] | Phaser.Animations.Animation[] | Phaser.Types.Animations.PlayAnimationConfig[] + ): this; + + stop( + ): this; + + stopAfterDelay( + delay: number + ): this; + + stopAfterRepeat( + repeatCount?: number + ): this; + + stopOnFrame( + frame: Phaser.Animations.AnimationFrame + ): this; + +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Sprite.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Sprite.js new file mode 100644 index 000000000..725dc43d7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/sprite/Sprite.js @@ -0,0 +1,76 @@ +import PerspectiveImage from '../image/Image.js'; + +const AnimationState = Phaser.Animations.AnimationState; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class Sprite extends PerspectiveImage { + constructor(scene, x, y, key, frame, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + key = GetValue(config, 'key', null); + frame = GetValue(config, 'frame', null); + } + + super(scene, x, y, key, frame, config); + this.type = 'rexPerspectiveSprite'; + this.anims = new AnimationState(this); + } + + preDestroy() { + super.preDestroy(); + + this.anims.destroy(); + this.anims = undefined; + } + + preUpdate(time, delta) { + var prevFrame = this.anims.currentFrame; + this.anims.update(time, delta); + if (this.anims.currentFrame !== prevFrame) { + this.syncSize(); + } + + super.preUpdate(time, delta); + } + + play(key, ignoreIfPlaying, startFrame) { + return this.anims.play(key, ignoreIfPlaying, startFrame); + } + + playReverse(key, ignoreIfPlaying) { + return this.anims.playReverse(key, ignoreIfPlaying); + } + + playAfterDelay(key, delay) { + return this.anims.playAfterDelay(key, delay); + } + + playAfterRepeat(key, repeatCount) { + return this.anims.playAfterRepeat(key, repeatCount); + } + + chain(key) { + return this.anims.chain(key); + } + + stop() { + return this.anims.stop(); + } + + stopAfterDelay(delay) { + return this.anims.stopAfterDelay(delay); + } + + stopAfterRepeat(repeatCount) { + return this.anims.stopAfterRepeat(repeatCount); + } + + stopOnFrame(frame) { + return this.anims.stopOnFrame(frame); + } +} + +export default Sprite; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/CreateFaces.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/CreateFaces.js new file mode 100644 index 000000000..708c82183 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/CreateFaces.js @@ -0,0 +1,35 @@ +import CreatePerspectiveObject from './CreatePerspectiveObject.js'; + +var CreateFaces = function (scene, config, faceNames) { + var faces; + if (faceNames === undefined) { // Return an array of faces + faces = []; + var face, faceConfig; + for (var i = 0, cnt = config.length; i < cnt; i++) { + faceConfig = config[i]; + if (faceConfig) { + face = CreatePerspectiveObject(scene, faceConfig); + } else { + face = null; + } + faces.push(face); + } + } else { // Return a face map + faces = {}; + var face, name; + for (var i = 0, cnt = faceNames.length; i < cnt; i++) { + name = faceNames[i]; + if (config.hasOwnProperty(name)) { + face = CreatePerspectiveObject(scene, config[name]); + } else { + face = null; + } + + faces[name] = face; + } + } + + return faces; +} + +export default CreateFaces; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/CreatePerspectiveObject.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/CreatePerspectiveObject.js new file mode 100644 index 000000000..ff273ef11 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/CreatePerspectiveObject.js @@ -0,0 +1,35 @@ +import Image from '../image/Image.js'; +import RenderTexture from '../rendertexture/RenderTexture.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const DefaultImageConfig = { key: '__WHITE' }; +const ClassMap = { + image: Image, + rendertexture: RenderTexture +} + +var CreatePerspectiveObject = function (scene, config) { + if (config === undefined) { + config = DefaultImageConfig; + } + + var perspectiveObject; + if (IsPlainObject(config)) { + if (!config.hasOwnProperty('type')) { + if (config.hasOwnProperty('key')) { + config.type = 'image'; + } else if (config.hasOwnProperty('width')) { + config.type = 'rendertexture'; + } + } + + perspectiveObject = new (ClassMap[config.type])(scene, config); + scene.add.existing(perspectiveObject); + } else { + perspectiveObject = config; + } + + return perspectiveObject; +} + +export default CreatePerspectiveObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/FaceContainer.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/FaceContainer.d.ts new file mode 100644 index 000000000..aa4e09f4e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/FaceContainer.d.ts @@ -0,0 +1,41 @@ +import Container from '../../../container/containerlite/ContainerLite'; +import Image from '../image/Image'; +import RenderTexture from '../rendertexture/RenderTexture'; + +type FaceTypes = Image | RenderTexture; +type FacesTypes = FaceTypes[] | { [name: string]: FaceTypes } + +export default class FaceContainer extends Container { + constructor(scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + faces: FacesTypes + ) + + rotationX: number; + rotationY: number; + rotationZ: number; + angleX: number; + angleY: number; + angleZ: number; + + panX(value: number): this; + panY(value: number): this; + panZ(value: number): this; + + transformVerts( + x?: number, y?: number, z?: number, + rotateX?: number, rotateY?: number, rotateZ?: number + ): this; + + setDebug( + graphic: Phaser.GameObjects.Graphics, + callback?: Function + ): this; + + forEachFace( + callback: (face: FaceTypes, i: number, faces: FacesTypes) => boolean | undefined, + scope?: object, + ignoreInvalid?: boolean + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/FaceContainer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/FaceContainer.js new file mode 100644 index 000000000..a5184d360 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/FaceContainer.js @@ -0,0 +1,114 @@ +import Container from '../../../container/containerlite/ContainerLite.js'; +import ForEachFace from './ForEachFace.js'; + +const RadToDeg = Phaser.Math.RadToDeg; +const DegToRad = Phaser.Math.DegToRad; + +class FaceContainer extends Container { + constructor(scene, x, y, width, height, faces) { + super(scene, x, y, width, height); + this.faces = faces; // Face Dictionary, or array + + ForEachFace(faces, function (face) { + face.setPosition(x, y); + this.add(face); + }, this, true); + } + + // Override + get rotationX() { + return 0; + } + + // Override + set rotationX(value) { + // rad + } + + get angleX() { + return RadToDeg(this.rotationX); + } + + set angleX(value) { + this.rotationX = DegToRad(value); + } + + // Override + get rotationY() { + return 0; + } + + // Override + set rotationY(value) { + // rad + } + + get angleY() { + return RadToDeg(this.rotationY); + } + + set angleY(value) { + this.rotationY = DegToRad(value); + } + + // Override + get rotationZ() { + return 0; + } + + // Override + set rotationZ(value) { + // rad + } + + get angleZ() { + return RadToDeg(this.rotationZ); + } + + set angleZ(value) { + this.rotationZ = DegToRad(value); + } + + setDebug(graphic, callback) { + ForEachFace(this.faces, function (face) { + face.setDebug(graphic, callback); + }, null, true); + return this; + } + + panX(v) { + ForEachFace(this.faces, function (face) { + face.panX(v); + }, null, true); + return this; + } + + panY(v) { + ForEachFace(this.faces, function (face) { + face.panY(v); + }, null, true); + return this; + } + + panZ(v) { + ForEachFace(this.faces, function (face) { + face.panZ(v); + }, null, true); + return this; + } + + transformVerts(x, y, z, rotateX, rotateY, rotateZ) { + ForEachFace(this.faces, function (face) { + face.transformVerts(x, y, z, rotateX, rotateY, rotateZ); + }, null, true); + return this; + } + + forEachFace(callback, scope, ignoreInvalid) { + ForEachFace(this.faces, callback, scope, ignoreInvalid); + return this; + } + +} + +export default FaceContainer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/ForEachFace.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/ForEachFace.js new file mode 100644 index 000000000..e66e343b1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/ForEachFace.js @@ -0,0 +1,39 @@ +var ForEachFace = function (faces, callback, scope, ignoreInvalid) { + if (Array.isArray(faces)) { + var isBreak = false; + for (var i = 0, cnt = faces.length; i < cnt; i++) { + var face = faces[i]; + if (ignoreInvalid && !face) { + continue; + } + if (scope) { + isBreak = callback.call(scope, face, i, faces); + } else { + isBreak = callback(face, i, faces); + } + + if (isBreak) { + return; + } + } + } else { + var isBreak = false; + for (var name in faces) { + var face = faces[name]; + if (ignoreInvalid && !face) { + continue; + } + if (scope) { + isBreak = callback.call(scope, face, name, faces); + } else { + isBreak = callback(face, name, faces); + } + + if (isBreak) { + return; + } + } + } +} + +export default ForEachFace; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/TransformVerts.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/TransformVerts.js new file mode 100644 index 000000000..ab6a5caa9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/perspective/utils/TransformVerts.js @@ -0,0 +1,25 @@ +const Vector3 = Phaser.Math.Vector3; +const Matrix4 = Phaser.Math.Matrix4; + +var tempPosition = new Vector3(); +var tempRotation = new Vector3(); +var tempMatrix = new Matrix4(); + +var TransformVerts = function (mesh, x, y, z, rotateX, rotateY, rotateZ) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (z === undefined) { z = 0; } + if (rotateX === undefined) { rotateX = 0; } + if (rotateY === undefined) { rotateY = 0; } + if (rotateZ === undefined) { rotateZ = 0; } + + tempPosition.set(x, y, z); + tempRotation.set(rotateX, rotateY, rotateZ); + tempMatrix.fromRotationXYTranslation(tempRotation, tempPosition, true); + + for (var i = 0, cnt = mesh.vertices.length; i < cnt; i++) { + mesh.vertices[i].transformMat4(tempMatrix); + } +} + +export default TransformVerts; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Creator.js new file mode 100644 index 000000000..5ee4f4895 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Creator.js @@ -0,0 +1,17 @@ +import Image from './Image.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var key = GetAdvancedValue(config, 'key', null); + var frame = GetAdvancedValue(config, 'frame', null); + var gameObject = new Image(this.scene, 0, 0, key, frame, config); + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Factory.js new file mode 100644 index 000000000..ce4f317ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Factory.js @@ -0,0 +1,7 @@ +import Image from './Image.js'; + +export default function (x, y, texture, frame, config) { + var gameObject = new Image(this.scene, x, y, texture, frame, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Image.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Image.d.ts new file mode 100644 index 000000000..fef44f467 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Image.d.ts @@ -0,0 +1,53 @@ +// import * as Phaser from 'phaser'; + +export default Image; + +declare namespace Image { + + interface IConfig { + x: number, y: number, + key?: string, + frame?: string, + + ninePointMode?: boolean, + rtl?: boolean, + } + + class ControlPoint { + setWorldXY(x: number, y: number): this; + setPosition(x: number, y: number): this; + getWorldXY(): { x: number, y: number }; + + x: number; + y: number; + + } + +} + +declare class Image extends Phaser.GameObjects.Mesh { + constructor( + scene: Phaser.Scene, + x?: number, + y?: number, + key?: string, + frame?: string | null, + config?: Image.IConfig + ) + + constructor( + scene: Phaser.Scene, + config?: Image.IConfig + ) + + readonly controlPoints: Image.ControlPoint[]; + readonly topLeft: Image.ControlPoint; + readonly topCenter: Image.ControlPoint; + readonly topRight: Image.ControlPoint; + readonly centerLeft: Image.ControlPoint; + readonly center: Image.ControlPoint; + readonly centerRight: Image.ControlPoint; + readonly bottomLeft: Image.ControlPoint; + readonly bottomCenter: Image.ControlPoint; + readonly bottomRight: Image.ControlPoint; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Image.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Image.js new file mode 100644 index 000000000..536744e4c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/Image.js @@ -0,0 +1,104 @@ +import InitFaces from './methods/InitFaces.js'; +import GetPointPosition from './methods/GetPointPosition.js'; + +const Mesh = Phaser.GameObjects.Mesh; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class Image extends Mesh { + constructor(scene, x, y, key, frame, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + key = GetValue(config, 'key', null); + frame = GetValue(config, 'frame', null); + } + + super(scene, x, y, key, frame); + this.type = 'rexQuadImage'; + this.isNinePointMode = GetValue(config, 'ninePointMode', false); + this.fourPointsModeRTL = GetValue(config, 'rtl', false); + this.controlPoints = []; + + InitFaces(this); + this.hideCCW = false; + this.syncSize(); + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + super.destroy(fromScene); + + for (var i = 0, cnt = this.controlPoints.length; i < cnt; i++) { + this.controlPoints[i].destroy(); + } + this.controlPoints = undefined; + } + + resetVerts() { + // Clear faces and vertices + this.dirtyCache[9] = -1; + + var points = GetPointPosition(this); + + // Calculate vertex data + var srcWidth = this.width; + var srcHeight = this.height; + var vHalfWidth = (this.frame.cutWidth / srcHeight) / 2; + var vHalfHeight = (this.frame.cutHeight / srcHeight) / 2; + + var frameU0 = this.frame.u0; + var frameU1 = this.frame.u1; + var frameV0 = this.frame.v0; + var frameV1 = this.frame.v1; + var frameU = frameU1 - frameU0; + var frameV = frameV1 - frameV0; + + // Update vertex + var controlPoints = this.controlPoints; + for (var i = 0, cnt = points.length; i < cnt; i += 2) { + var px = points[i + 0]; + var py = points[i + 1]; + var vertexIndex = i / 2; + + var x = (px / srcHeight) - vHalfWidth; + var y = (py / srcHeight) - vHalfHeight; + var u = frameU0 + (frameU * (px / srcWidth)); + var v = frameV0 + (frameV * (py / srcHeight)); + this.vertices[vertexIndex] + .set(x, -y, 0) + .setUVs(u, v) + + controlPoints[vertexIndex].resetLocalXY(px, py); + } + + return this; + } + + syncSize() { + this.setSizeToFrame(); // Reset size + this.setOrtho(this.width / this.height, 1); + this.resetVerts(); // Reset verts + return this; + } + + forceUpdate() { + this.dirtyCache[10] = 1; + return this; + } + + get tint() { + if (this.vertices.length === 0) { + return 0xffffff; + } else { + return this.vertices[0].color; + } + } +} + +export default Image; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/methods/ControlPoint.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/methods/ControlPoint.js new file mode 100644 index 000000000..238ce6e3f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/methods/ControlPoint.js @@ -0,0 +1,118 @@ +import { LocalXYToWorldXY, WorldXYToLocalXY } from '../../../utils/LocalXY.js'; + +class ControlPoint { + constructor(parent, vertex) { + this.parent = parent; + this.vertex = vertex; + this._localX = undefined; + this._localY = undefined; + } + + destroy() { + this.parent = undefined; + this.vertex = undefined; + } + + updateVertexPosition(x, y) { + var gameObject = this.parent; + var srcHeight = gameObject.height; + var vHalfWidth = (gameObject.frame.cutWidth / srcHeight) / 2; + var vHalfHeight = (gameObject.frame.cutHeight / srcHeight) / 2; + + var vx = (x / srcHeight) - vHalfWidth; + var vy = (y / srcHeight) - vHalfHeight; + + var vertex = this.vertex; + vertex.x = vx; + vertex.y = -vy; + gameObject.forceUpdate(); + return this; + } + + get localX() { + return this._localX; + } + + set localX(x) { + this.setLocalXY(x, this._localY); + } + + get localY() { + return this._localY; + } + + set localY(y) { + this.setLocalXY(this._localX, y); + } + + get localXOrigin() { + return this._localXOrigin; + } + + get localYOrigin() { + return this._localYOrigin; + } + + resetLocalXY(x, y) { + this._localXOrigin = x; + this._localYOrigin = y; + this._localX = x; + this._localY = y; + return this; + } + + setLocalXY(x, y, ignoreUpdateVertex) { + if ((this._localX === x) && (this._localY === y)) { + return this; + } + + this._localX = x; + this._localY = y; + + if (!ignoreUpdateVertex) { + this.updateVertexPosition(x, y); + } + + return this; + } + + setWorldXY(x, y) { + if ((this._worldX === x) && (this._worldY === y)) { + return this; + } + + var localXY = WorldXYToLocalXY(this.parent, x, y); + this.setLocalXY(localXY.x, localXY.y); + + return this; + } + + setPosition(x, y) { + this.setWorldXY(x, y); + return this; + } + + getWorldXY() { + return LocalXYToWorldXY(this.parent, this._localX, this._localY); + } + + get x() { + var worldXY = LocalXYToWorldXY(this.parent, this._localX, this._localY); + return worldXY.x; + } + + set x(x) { + this.setWorldXY(x, this.y); + } + + get y() { + var worldXY = LocalXYToWorldXY(this.parent, this._localX, this._localY); + return worldXY.y; + } + + set y(y) { + this.setWorldXY(this.x, y); + } +} + +export default ControlPoint; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/methods/GetPointPosition.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/methods/GetPointPosition.js new file mode 100644 index 000000000..4820312cc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/methods/GetPointPosition.js @@ -0,0 +1,30 @@ +var GetPointPosition = function (quad) { + var points; + var top = 0, bottom = quad.height, + left = 0, right = quad.width; + if (quad.isNinePointMode) { + var centerX = (left + right) / 2; + var centerY = (top + bottom) / 2; + points = [ + left, top, // top-left + centerX, top, // top-center + right, top, // top-right + left, centerY, // center-left + centerX, centerY, // center-center + right, centerY, // top-right + left, bottom, // center-left + centerX, bottom, // bottom-center + right, bottom // bottom-right + ]; + } else { + points = [ + left, top, // top-left + right, top, // top-right + left, bottom, // bottom-left + right, bottom // bottom-right + ]; + } + + return points; +} +export default GetPointPosition; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/methods/InitFaces.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/methods/InitFaces.js new file mode 100644 index 000000000..0e2507680 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/image/methods/InitFaces.js @@ -0,0 +1,85 @@ +import ControlPoint from './ControlPoint.js'; + +const Vertex = Phaser.Geom.Mesh.Vertex; +const Face = Phaser.Geom.Mesh.Face; + +var InitFaces = function (quad) { + var isNinePointMode = quad.isNinePointMode; + var pointCount = (isNinePointMode) ? 9 : 4; + + var vertices = quad.vertices; + var faces = quad.faces; + var controlPoints = quad.controlPoints; + for (var i = 0; i < pointCount; i++) { + var vertex = new Vertex(); + vertices.push(vertex); + controlPoints.push(new ControlPoint(quad, vertex)); + } + var indices; + if (isNinePointMode) { + indices = NinePointsIndices; + } else { + if (!quad.fourPointsModeRTL) { + indices = FourPointsIndices; + } else { + indices = FourPointsIndicesRTL; + } + } + + for (var i = 0, cnt = indices.length; i < cnt; i += 3) { + var vert1 = vertices[indices[i + 0]]; + var vert2 = vertices[indices[i + 1]]; + var vert3 = vertices[indices[i + 2]]; + faces.push(new Face(vert1, vert2, vert3)); + } + + if (isNinePointMode) { + quad.topLeft = controlPoints[0]; + quad.topCenter = controlPoints[1]; + quad.topRight = controlPoints[2]; + quad.centerLeft = controlPoints[3]; + quad.center = controlPoints[4]; + quad.centerRight = controlPoints[5]; + quad.bottomLeft = controlPoints[6]; + quad.bottomCenter = controlPoints[7]; + quad.bottomRight = controlPoints[8]; + } else { + quad.topLeft = controlPoints[0]; + quad.topRight = controlPoints[1]; + quad.bottomLeft = controlPoints[2]; + quad.bottomRight = controlPoints[3]; + } +} + +/* +0, 1, +2, 3, +*/ +const FourPointsIndices = [ + 0, 2, 3, + 0, 3, 1 +]; + +const FourPointsIndicesRTL = [ + 1, 3, 2, + 1, 2, 0 +]; + + +/* +0, 1, 2, +3, 4, 5, +6, 7, 8 +*/ +const NinePointsIndices = [ + 0, 3, 4, + 0, 4, 1, + 1, 4, 2, + 4, 5, 2, + 3, 6, 4, + 6, 7, 4, + 4, 7, 8, + 4, 8, 5 +]; + +export default InitFaces; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/Creator.js new file mode 100644 index 000000000..390131750 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/Creator.js @@ -0,0 +1,19 @@ +import RenderTexture from './RenderTexture.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var x = GetAdvancedValue(config, 'x', 0); + var y = GetAdvancedValue(config, 'y', 0); + var width = GetAdvancedValue(config, 'width', 32); + var height = GetAdvancedValue(config, 'height', 32); + var gameObject = new RenderTexture(this.scene, x, y, width, height, config); + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/Factory.js new file mode 100644 index 000000000..01c3e84e5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/Factory.js @@ -0,0 +1,7 @@ +import RenderTexture from './RenderTexture.js'; + +export default function (x, y, width, height, config) { + var gameObject = new RenderTexture(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/RenderTexture.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/RenderTexture.d.ts new file mode 100644 index 000000000..422876ff5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/RenderTexture.d.ts @@ -0,0 +1,35 @@ +// import * as Phaser from 'phaser'; +import Image from '../image/Image'; + +export default RenderTexture; + +declare namespace RenderTexture { + + interface IConfig extends Image.IConfig { + width?: number, height?: number, + } + + interface SnapshotIConfig { + x?: number, y?: number, + width?: number, height?: number, + padding?: number, + } +} + +declare class RenderTexture extends Image { + constructor( + scene: Phaser.Scene, + x?: number | RenderTexture.IConfig, + y?: number, + width?: number, + height?: number, + config?: RenderTexture.IConfig + ) + + readonly rt: Phaser.GameObjects.RenderTexture; + + snapshot( + gameObjects: Phaser.GameObjects.GameObject[], + config?: RenderTexture.SnapshotIConfig + ): this; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/RenderTexture.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/RenderTexture.js new file mode 100644 index 000000000..aa1af6378 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/rendertexture/RenderTexture.js @@ -0,0 +1,55 @@ +import Image from '../image/Image.js'; +import CreateDynamicTexture from '../../../../utils/rendertexture/CreateDynamicTexture.js'; +import Snapshot from '../../../../utils/rendertexture/Snapshot.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class RenderTexture extends Image { + constructor(scene, x, y, width, height, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 32); + height = GetValue(config, 'height', 32); + } + + // dynamic-texture -> quad-image + var texture = CreateDynamicTexture(scene, width, height); + + super(scene, x, y, texture, null, config); + this.type = 'rexQuadRenderTexture'; + this.rt = this.texture; + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + super.destroy(fromScene); + + this.rt.destroy(); + this.rt = null; + } + + snapshot(gameObjects, config) { + if (config === undefined) { + config = {}; + } + config.gameObjects = gameObjects; + config.renderTexture = this.rt; + + Snapshot(config); + + if ((this.width !== this.frame.realWidth) || (this.height !== this.frame.realHeight)) { + this.syncSize(); + } + + return this; + } +} + +export default RenderTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/Creator.js new file mode 100644 index 000000000..fd094b886 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/Creator.js @@ -0,0 +1,17 @@ +import SkewImage from './SkewImage.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var key = GetAdvancedValue(config, 'key', null); + var frame = GetAdvancedValue(config, 'frame', null); + var gameObject = new SkewImage(this.scene, 0, 0, key, frame); + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/Factory.js new file mode 100644 index 000000000..a2cc8f681 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/Factory.js @@ -0,0 +1,7 @@ +import SkewImage from './SkewImage.js'; + +export default function (x, y, texture, frame) { + var gameObject = new SkewImage(this.scene, x, y, texture, frame); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/Skew.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/Skew.js new file mode 100644 index 000000000..ce4656b6a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/Skew.js @@ -0,0 +1,25 @@ +var Skew = function (gameObject, skewX, skewY) { + if (skewX === undefined) { + skewX = 0; + } + if (skewY === undefined) { + skewY = 0; + } + + var width = gameObject.width, + height = gameObject.height; + var ox = width * 0.5; + var oy = height * 0.5; + var xOffset = Math.tan(skewX) * oy; + var yOffset = Math.tan(skewY) * ox; + var controlPoints = gameObject.controlPoints; + for (var i = 0, cnt = controlPoints.length; i < cnt; i++) { + var controlPoint = controlPoints[i]; + var x = controlPoint.localXOrigin; + var y = controlPoint.localYOrigin; + controlPoint.localX = x + ((y > oy) ? xOffset : -xOffset); + controlPoint.localY = y + ((x > ox) ? yOffset : -yOffset); + } +} + +export default Skew; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/SkewImage.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/SkewImage.d.ts new file mode 100644 index 000000000..5f11c58f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/SkewImage.d.ts @@ -0,0 +1,41 @@ +// import * as Phaser from 'phaser'; +import Image from '../image/Image'; + +export default SkewImage; + +declare namespace SkewImage { + + interface IConfig { + x: number, y: number, + key?: string, + frame?: string + } + +} + +declare class SkewImage extends Image { + constructor( + scene: Phaser.Scene, + x?: number, + y?: number, + key?: string, + frame?: string | null + ) + + constructor( + scene: Phaser.Scene, + config?: SkewImage.IConfig + ) + + skewX: number; + skewY: number; + skewXDeg: number; + skewYDeg: number; + + setSkewX(skewX: number): this; + setSkewY(skewY: number): this; + setSkew(skewX: number, skewY?: number): this; + setSkewXDeg(skewX: number): this; + setSkewYDeg(skewY: number): this; + setSkewDeg(skewX: number, skewY?: number): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/SkewImage.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/SkewImage.js new file mode 100644 index 000000000..a60331b7c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewimage/SkewImage.js @@ -0,0 +1,100 @@ +import Image from '../image/Image.js'; +import Skew from './Skew.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const DegToRad = Phaser.Math.DegToRad; +const RadToDeg = Phaser.Math.RadToDeg; + +class SkewImage extends Image { + constructor(scene, x, y, key, frame) { + if (IsPlainObject(x)) { + var config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + key = GetValue(config, 'key', null); + frame = GetValue(config, 'frame', null); + } + + super(scene, x, y, key, frame); + this.type = 'rexSkewmage'; + + this._skewX = 0; + this._skewY = 0; + } + + get skewX() { + return this._skewX; + } + + set skewX(value) { + this._skewX = value; + Skew(this, this._skewX, this._skewY); + } + + get skewXDeg() { + return RadToDeg(this._skewX); + } + + set skewXDeg(value) { + this.skewX = DegToRad(value); + } + + get skewY() { + return this._skewY; + } + + set skewY(value) { + this._skewY = value; + Skew(this, this._skewX, this._skewY) + } + + get skewYDeg() { + return RadToDeg(this._skewY); + } + + set skewYDeg(value) { + this.skewY = DegToRad(value); + } + + setSkewX(skewX) { + this.skewX = skewX; + return this; + } + + setSkewY(skewY) { + this.skewY = skewY; + return this; + } + + setSkew(skewX, skewY) { + if (skewY === undefined) { + skewY = skewX; + } + this.skewX = skewX; + this.skewY = skewY; + return this; + } + + setSkewXDeg(skewX) { + this.skewXDeg = skewX; + return this; + } + + setSkewYDeg(skewY) { + this.skewYDeg = skewY; + return this; + } + + setSkewDeg(skewX, skewY) { + if (skewY === undefined) { + skewY = skewX; + } + this.skewXDeg = skewX; + this.skewYDeg = skewY; + return this; + } + +} + +export default SkewImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/Creator.js new file mode 100644 index 000000000..040c49ba4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/Creator.js @@ -0,0 +1,19 @@ +import SkewRenderTexture from './SkewRenderTexture.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var x = GetAdvancedValue(config, 'x', 0); + var y = GetAdvancedValue(config, 'y', 0); + var width = GetAdvancedValue(config, 'width', 32); + var height = GetAdvancedValue(config, 'height', 32); + var gameObject = new SkewRenderTexture(this.scene, x, y, width, height); + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/Factory.js new file mode 100644 index 000000000..0a48ab4f0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/Factory.js @@ -0,0 +1,7 @@ +import SkewRenderTexture from './SkewRenderTexture.js'; + +export default function (x, y, width, height) { + var gameObject = new SkewRenderTexture(this.scene, x, y, width, height); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture.d.ts new file mode 100644 index 000000000..0637edc43 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture.d.ts @@ -0,0 +1,29 @@ +// import * as Phaser from 'phaser'; +import SkewImage from '../skewimage/SkewImage'; + +export default SkewRenderTexture; + +declare namespace SkewRenderTexture { + + interface IConfig extends SkewImage.IConfig { + width?: number, height?: number, + } + +} + +declare class SkewRenderTexture extends SkewImage { + constructor( + scene: Phaser.Scene, + x?: number, + y?: number, + width?: number, + height?: number + ) + + constructor( + scene: Phaser.Scene, + config?: SkewRenderTexture.IConfig + ) + + readonly rt: Phaser.GameObjects.RenderTexture; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture.js new file mode 100644 index 000000000..d50f456b0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture.js @@ -0,0 +1,38 @@ +import SkewImage from '../skewimage/SkewImage.js'; +import CreateDynamicTexture from '../../../../utils/rendertexture/CreateDynamicTexture.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class SkewRenderTexture extends SkewImage { + constructor(scene, x, y, width, height) { + if (IsPlainObject(x)) { + var config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 32); + height = GetValue(config, 'height', 32); + } + + // dynamic-texture -> quad-image + var texture = CreateDynamicTexture(scene, width, height); + + super(scene, x, y, texture, null); + this.type = 'rexSkewRenderTexture'; + this.rt = this.texture; + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + super.destroy(fromScene); + + this.rt.destroy(); + this.rt = null; + } +} + +export default SkewRenderTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Creator.js new file mode 100644 index 000000000..5ee4f4895 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Creator.js @@ -0,0 +1,17 @@ +import Image from './Image.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var key = GetAdvancedValue(config, 'key', null); + var frame = GetAdvancedValue(config, 'frame', null); + var gameObject = new Image(this.scene, 0, 0, key, frame, config); + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Face.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Face.js new file mode 100644 index 000000000..a011d4317 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Face.js @@ -0,0 +1,66 @@ +const Base = Phaser.Geom.Mesh.Face; +const DegToRad = Phaser.Math.DegToRad; +const RadToDeg = Phaser.Math.RadToDeg; +const RotateFace = Phaser.Geom.Mesh.RotateFace; + +class Face extends Base { + constructor(vertex1, vertex2, vertex3) { + super(vertex1, vertex2, vertex3); + + this._rotation = 0; + } + + get rotation() { + return this._rotation; + } + + set rotation(value) { + RotateFace(this, (value - this._rotation)); + this._rotation = value; + } + + setRotation(value) { + this.rotation = value; + return this; + } + + get angle() { + return RadToDeg(this.rotation); + } + + set angle(value) { + this.rotation = DegToRad(value); + } + + setAngle(value) { + this.angle = value; + return this; + } + + setAlpha(alpha) { + this.alpha = alpha; + return this; + } + + get tint() { + var tint1 = this.vertex1.color; + var tint2 = this.vertex2.color; + var tint3 = this.vertex3.color; + return (((((tint1 >> 0) & 0xff) + ((tint2 >> 0) & 0xff) + ((tint3 >> 0) & 0xff)) / 3) << 0) + + (((((tint1 >> 8) & 0xff) + ((tint2 >> 8) & 0xff) + ((tint3 >> 8) & 0xff)) / 3) << 8) + + (((((tint1 >> 16) & 0xff) + ((tint2 >> 16) & 0xff) + ((tint3 >> 16) & 0xff)) / 3) << 16); + } + + set tint(value) { + this.vertex1.color = value; + this.vertex2.color = value; + this.vertex3.color = value; + } + + setTint(value) { + this.tint = value; + return this; + } +} + +export default Face; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Factory.js new file mode 100644 index 000000000..ce4f317ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Factory.js @@ -0,0 +1,7 @@ +import Image from './Image.js'; + +export default function (x, y, texture, frame, config) { + var gameObject = new Image(this.scene, x, y, texture, frame, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Image.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Image.d.ts new file mode 100644 index 000000000..2e247477a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Image.d.ts @@ -0,0 +1,63 @@ +export default ShatterImage; + +declare namespace ShatterImage { + type GetRingRadiusListCallback = (width: number, height: number) => number[]; + + interface IConfig { + x?: number, + y?: number, + key: string, + frame?: string, + + ringRadiusList?: number[] | GetRingRadiusListCallback, + samplesPerRing?: number, + variation?: number, + + } + + interface ShatterIConfig { + centerX?: number, + centerY?: number, + ringRadiusList?: number[] | GetRingRadiusListCallback, + samplesPerRing?: number, + variation?: number, + } +} + +declare class ShatterImage extends Phaser.GameObjects.Mesh { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + key?: string, frame?: string, + config?: ShatterImage.IConfig + ); + + constructor( + scene: Phaser.Scene, + config?: ShatterImage.IConfig + ); + + shatter( + centerX?: number, + centerY?: number, + config?: ShatterImage.ShatterIConfig + ): this; + shatter( + config?: ShatterImage.ShatterIConfig + ): this; + readonly shatterCenter: { x: number, y: number }; + + ringRadiusList: number[] | ShatterImage.GetRingRadiusListCallback; + setRingRadiusList(ringRadiusList: number[] | ShatterImage.GetRingRadiusListCallback): this; + + samplesPerRing: number; + setSamplesPerRing(samplesPerRing: number): this; + + variation: number; + setVariation(variation: number): this; + + startUpdate(): this; + stopUpdate(): this; + + resetImage(): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Image.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Image.js new file mode 100644 index 000000000..8d64732c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/image/Image.js @@ -0,0 +1,195 @@ +import ShatterRectangleToTriangles from '../../../../utils/math/triangulate/ShatterRectangleToTriangles.js'; +import Face from './Face.js'; +import { WorldXYToLocalXY } from '../../utils/LocalXY.js'; + +const Mesh = Phaser.GameObjects.Mesh; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const GenerateGridVerts = Phaser.Geom.Mesh.GenerateGridVerts; +const Vertex = Phaser.Geom.Mesh.Vertex; +const DistanceSquared = Phaser.Math.Distance.Squared; +const DefaultRingRadiusList = [1 / 27, 3 / 27, 9 / 27]; + +class ShatterImage extends Mesh { + constructor(scene, x, y, key, frame, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + key = GetValue(config, 'key', null); + frame = GetValue(config, 'frame', null); + } + + super(scene, x, y, key, frame); + this.type = 'rexShatterImage'; + this.hideCCW = false; + this.resetImage(); + + this.shatterCenter = { x: null, y: null }; + + this.setRingRadiusList(GetValue(config, 'ringRadiusList', DefaultRingRadiusList)); + this.setSamplesPerRing(GetValue(config, 'samplesPerRing', 12)); + this.setVariation(GetValue(config, 'variation', 0.25)); + } + + setRingRadiusList(ringRadiusList) { + this.ringRadiusList = ringRadiusList; + return this; + } + + setSamplesPerRing(samples) { + this.samplesPerRing = samples; + return this; + } + + setVariation(variation) { + this.variation = variation; + return this; + } + + resetImage() { + this.setSizeToFrame(); + + this.clear(); + this.dirtyCache[9] = -1; + GenerateGridVerts({ + mesh: this, + texture: this.texture.key, frame: this.frame.name, + width: this.frame.cutWidth / this.height, + height: this.frame.cutHeight / this.height, + }); + + this.setOrtho(this.width / this.height, 1); + return this + } + + shatter(centerX, centerY, config) { + if (IsPlainObject(centerX)) { + config = centerX; + centerX = undefined; + centerY = undefined; + } + + if (IsPlainObject(config)) { + if (config.hasOwnProperty('centerX')) { + centerX = config.centerX; + } + if (config.hasOwnProperty('centerY')) { + centerY = config.centerY; + } + if (config.hasOwnProperty('ringRadiusList')) { + this.setRingRadiusList(config.ringRadiusList); + } + if (config.hasOwnProperty('samplesPerRing')) { + this.setSamplesPerRing(config.samplesPerRing); + } + if (config.hasOwnProperty('variation')) { + this.setVariation(config.variation); + } + } + + if (centerX === undefined) { + centerX = this.width / 2; + centerY = this.height / 2; + } else { + var worldXY = WorldXYToLocalXY(this, centerX, centerY); + centerX = worldXY.x; + centerY = worldXY.y; + } + + this.shatterCenter.x = centerX; + this.shatterCenter.y = centerY; + + // Clear faces and vertices + this.clear(); + this.dirtyCache[9] = -1; + if ((this.width === 0) || (this.height === 0)) { + return this; + } + + var result = ShatterRectangleToTriangles({ + width: this.width, + height: this.height, + center: this.shatterCenter, + triangleOutput: false, + ringRadiusList: this.ringRadiusList, + variation: this.variation, + samplesPerRing: this.samplesPerRing + }) + var vertices = result.vertices; + var indices = result.indices; + + // Calculate vertex data + var verticesData = []; + var srcWidth = this.width; + var srcHeight = this.height; + var vHalfWidth = (this.frame.cutWidth / srcHeight) / 2; + var vHalfHeight = (this.frame.cutHeight / srcHeight) / 2; + + var frameU0 = this.frame.u0; + var frameU1 = this.frame.u1; + var frameV0 = this.frame.v0; + var frameV1 = this.frame.v1; + var frameU = frameU1 - frameU0; + var frameV = frameV1 - frameV0; + + for (var i = 0, cnt = vertices.length; i < cnt; i++) { + var p = vertices[i]; + var px = p[0]; + var py = p[1]; + + verticesData.push({ + g: DistanceSquared(centerX, centerY, px, py), + x: (px / srcHeight) - vHalfWidth, + y: (py / srcHeight) - vHalfHeight, + u: frameU0 + (frameU * (px / srcWidth)), + v: frameV0 + (frameV * (py / srcHeight)) + }) + } + + // Build face + for (var i = 0, cnt = indices.length; i < cnt; i += 3) { + var v0 = verticesData[indices[i + 0]]; + var v1 = verticesData[indices[i + 1]]; + var v2 = verticesData[indices[i + 2]]; + + var vert1 = new Vertex(v0.x, -v0.y, 0, v0.u, v0.v); + var vert2 = new Vertex(v1.x, -v1.y, 0, v1.u, v1.v); + var vert3 = new Vertex(v2.x, -v2.y, 0, v2.u, v2.v); + var face = new Face(vert1, vert2, vert3); + + this.vertices.push(vert1, vert2, vert3); + this.faces.push(face); + + // Sort faces from center + face.g = Math.min(v0.g, v1.g, v2.g); + } + + // Sort faces from center + this.faces.sort(function (faceA, faceB) { + return faceA.g - faceB.g; + }) + + return this; + } + + startUpdate() { + this.ignoreDirtyCache = true; + return this; + } + + stopUpdate() { + this.ignoreDirtyCache = false; + return this; + } + + get tint() { + if (this.vertices.length === 0) { + return 0xffffff; + } else { + return this.vertices[0].color; + } + } +} + +export default ShatterImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/Creator.js new file mode 100644 index 000000000..390131750 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/Creator.js @@ -0,0 +1,19 @@ +import RenderTexture from './RenderTexture.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var x = GetAdvancedValue(config, 'x', 0); + var y = GetAdvancedValue(config, 'y', 0); + var width = GetAdvancedValue(config, 'width', 32); + var height = GetAdvancedValue(config, 'height', 32); + var gameObject = new RenderTexture(this.scene, x, y, width, height, config); + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/Factory.js new file mode 100644 index 000000000..01c3e84e5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/Factory.js @@ -0,0 +1,7 @@ +import RenderTexture from './RenderTexture.js'; + +export default function (x, y, width, height, config) { + var gameObject = new RenderTexture(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/RenderTexture.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/RenderTexture.d.ts new file mode 100644 index 000000000..edc0edef6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/RenderTexture.d.ts @@ -0,0 +1,25 @@ +// import * as Phaser from 'phaser'; +import Image from '../image/Image'; + +export default RenderTexture; + +declare namespace RenderTexture { + + interface IConfig extends Image.IConfig { + width?: number, height?: number, + } + +} + +declare class RenderTexture extends Image { + constructor( + scene: Phaser.Scene, + x?: number | RenderTexture.IConfig, + y?: number, + width?: number, + height?: number, + config?: RenderTexture.IConfig + ) + + readonly rt: Phaser.GameObjects.RenderTexture; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/RenderTexture.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/RenderTexture.js new file mode 100644 index 000000000..1390af5e2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/shatter/rendertexture/RenderTexture.js @@ -0,0 +1,38 @@ +import Image from '../image/Image.js'; +import CreateDynamicTexture from '../../../../utils/rendertexture/CreateDynamicTexture.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class RenderTexture extends Image { + constructor(scene, x, y, width, height, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 32); + height = GetValue(config, 'height', 32); + } + + // dynamic-texture -> quad-image + var texture = CreateDynamicTexture(scene, width, height); + + super(scene, x, y, texture, null, config); + this.type = 'rexShatterRenderTexture'; + this.rt = this.texture; + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + super.destroy(fromScene); + + this.rt.destroy(); + this.rt = null; + } +} + +export default RenderTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/utils/LocalXY.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/utils/LocalXY.js new file mode 100644 index 000000000..34f5fed8b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/mesh/utils/LocalXY.js @@ -0,0 +1,36 @@ +const RotateAround = Phaser.Math.RotateAround; + +var LocalXYToWorldXY = function (gameObject, localX, localY) { + var ox = gameObject.width / 2; + var oy = gameObject.height / 2; + out.x = localX - ox; + out.y = localY - oy; + RotateAround(out, 0, 0, gameObject.rotation); + out.x *= gameObject.scaleX; + out.y *= gameObject.scaleY; + out.x += gameObject.x; + out.y += gameObject.y; + + return out; +} + +var WorldXYToLocalXY = function (gameObject, worldX, worldY) { + var ox = gameObject.width / 2; + var oy = gameObject.height / 2; + + out.x = worldX - gameObject.x; + out.y = worldY - gameObject.y; + out.x /= gameObject.scaleX; + out.y /= gameObject.scaleY; + RotateAround(out, 0, 0, -gameObject.rotation); + out.x += ox; + out.y += oy; + + return out; +} + +var out = { x: 0, y: 0 }; + +export { + LocalXYToWorldXY, WorldXYToLocalXY +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Creator.js new file mode 100644 index 000000000..c3c928e1c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Creator.js @@ -0,0 +1,13 @@ +import Line from './Line.js'; + +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var gameObject = new Line(this.scene, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Factory.js new file mode 100644 index 000000000..7532d7ba3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Factory.js @@ -0,0 +1,7 @@ +import Line from './Line.js'; + +export default function (config) { + var gameObject = new Line(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Line.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Line.d.ts new file mode 100644 index 000000000..b6f29c2b7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Line.d.ts @@ -0,0 +1,51 @@ +export default Line; + +declare namespace Line { + + type ExtendModeTyp = 0 | 1 | 'scale' | 'repeat'; + + interface IConfig { + start?: { + x?: number, y?: number, + key?: string, frame?: string, origin?: number + } | + string, + + end?: { + x?: number, y?: number, + key?: string, frame?: string, origin?: number + } | + string, + + body?: { + key?: string, frame?: string, + extendMode?: ExtendModeTyp, width?: number, + } | + string, + } +} + +declare class Line extends Phaser.GameObjects.RenderTexture { + constructor( + scene: Phaser.Scene, + config?: Line.IConfig + ) + + setLineStartPosition(x?: number, y?: number): this; + x0: number; + y0: number; + + setLineEndPosition(x?: number, y?: number): this; + x1: number; + y1: number; + + setLineStartTexture(key?: string, frameName?: string): this; + setLineEndTexture(key?: string, frameName?: string): this; + setLineBodyTexture(key?: string, frameName?: string): this; + + setLineStartOrigin(origin: number): this; + setLineEndOrigin(origin: number): this; + setLineBodyExtendMode(mode: Line.ExtendModeTyp): this; + setLineBodyWidth(width: number); + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Line.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Line.js new file mode 100644 index 000000000..795815bc6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/Line.js @@ -0,0 +1,184 @@ +import UpdateTexture from './UpdateTexture.js'; + +const RenderTexture = Phaser.GameObjects.RenderTexture; +const GetValue = Phaser.Utils.Objects.GetValue; + +class Line extends RenderTexture { + constructor(scene, config) { + super(scene); + this.redraw = false; + this._tileSprite = undefined; // Reserved for drawing image + this._image = undefined; // Reserved for drawing image + + var lineStart = GetValue(config, 'start', undefined); + if (typeof (lineStart) === 'string') { + this.setLineStartPosition(0, 0); + this.setLineStartTexture(lineStart, undefined); + this.setLineStartOrigin(undefined); + } else { + this.setLineStartPosition(GetValue(lineStart, 'x', 0), GetValue(lineStart, 'y', 0)); + this.setLineStartTexture(GetValue(lineStart, 'key', undefined), GetValue(lineStart, 'frame', undefined)); + this.setLineStartOrigin(GetValue(lineStart, 'origin', undefined)); + } + + var lineEnd = GetValue(config, 'end', undefined); + if (typeof (lineEnd) === 'string') { + this.setLineEndPosition(0, 0); + this.setLineEndTexture(lineEnd, undefined); + this.setLineEndOrigin(undefined); + } else { + this.setLineEndPosition(GetValue(lineEnd, 'x', 0), GetValue(lineEnd, 'y', 0)); + this.setLineEndTexture(GetValue(lineEnd, 'key', undefined), GetValue(lineEnd, 'frame', undefined)); + this.setLineEndOrigin(GetValue(lineEnd, 'origin', undefined)); + } + + var lineBody = GetValue(config, 'body', undefined); + if (typeof (lineBody) === 'string') { + this.setLineBodyTexture(lineBody, undefined); + this.setLineBodyExtendMode(0); + this.setLineBodyWidth(undefined); + } else { + this.setLineBodyTexture(GetValue(lineBody, 'key', undefined), GetValue(lineBody, 'frame', undefined)); + this.setLineBodyExtendMode(GetValue(lineBody, 'extendMode', 1)); + this.setLineBodyWidth(GetValue(lineBody, 'width', undefined)); + } + } + + get x0() { + return this._x0; + } + + set x0(value) { + this.redraw |= (this._x0 !== value); + this._x0 = value; + } + + get y0() { + return this._y0; + } + + set y0(value) { + this.redraw |= (this._y0 !== value); + this._y0 = value; + } + + get x1() { + return this._x1; + } + + set x1(value) { + this.redraw |= (this._x1 !== value); + this._x1 = value; + } + + get y1() { + return this._y1; + } + + set y1(value) { + this.redraw |= (this._y1 !== value); + this._y1 = value; + } + + setLineStartPosition(x, y) { + this.x0 = x; + this.y0 = y; + return this; + } + + setLineEndPosition(x, y) { + this.x1 = x; + this.y1 = y; + return this; + } + + setLineStartTexture(key, frame) { + this.lineStartTexture = key; + this.lineStartFrameName = frame; + this.redraw = true; + return this; + } + + setLineStartOrigin(origin) { + if (origin === undefined) { + origin = 0; + } + this.lineStartOrigin = origin; + this.redraw = true; + return this; + } + + get lineStartFrame() { + return this.scene.sys.textures.getFrame(this.lineStartTexture, this.lineStartFrameName); + } + + setLineEndTexture(key, frame) { + this.lineEndTexture = key; + this.lineEndFrameName = frame; + this.redraw = true; + return this; + } + + setLineEndOrigin(origin) { + if (origin === undefined) { + origin = 1; + } + this.lineEndOrigin = origin; + this.redraw = true; + return this; + } + + get lineEndFrame() { + return this.scene.sys.textures.getFrame(this.lineEndTexture, this.lineEndFrameName); + } + + setLineBodyTexture(key, frame) { + this.lineBodyTexture = key; + this.lineBodyFrameName = frame; + this.redraw = true; + return this; + } + + setLineBodyWidth(width) { + this.lineBodyWidth = width; + this.redraw = true; + return this; + } + + setLineBodyExtendMode(mode) { + if (typeof (mode) === 'string') { + mode = EXTENDMODE[mode]; + } + this.lineBodyExtendMode = mode; + return this; + } + + get lineBodyFrame() { + return this.scene.sys.textures.getFrame(this.lineBodyTexture, this.lineBodyFrameName); + } + + renderWebGL(renderer, src, camera, parentMatrix) { + this.updateTexture(); + super.renderWebGL(renderer, src, camera, parentMatrix); + } + + renderCanvas(renderer, src, camera, parentMatrix) { + this.updateTexture(); + super.renderCanvas(renderer, src, camera, parentMatrix); + } +} + +const EXTENDMODE = { + scale: 0, + repeat: 1, +} + +var methods = { + updateTexture: UpdateTexture, +} +Object.assign( + Line.prototype, + methods +); + +export default Line; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/UpdateTexture.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/UpdateTexture.js new file mode 100644 index 000000000..d3ae84603 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/line/UpdateTexture.js @@ -0,0 +1,105 @@ +import DrawImage from '../utils/DrawImage.js'; +import DrawTileSprite from '../utils/DrawTileSprite.js'; + +const DistanceBetween = Phaser.Math.Distance.Between; +const AngleBetween = Phaser.Math.Angle.Between; + +var UpdateTexture = function () { + if (!this.redraw) { + return this; + } + this.redraw = false; + + // Note: Don't use clear method here + // this.clear(); // this.setSize(w,h) will clear content + + var lineStartFrame = this.lineStartFrame; + var lineEndFrame = this.lineEndFrame; + var lineBodyFrame = this.lineBodyFrame; + var lineStartOffset = 0; + var width = DistanceBetween(this.x0, this.y0, this.x1, this.y1), + height = 0, + rotation = AngleBetween(this.x0, this.y0, this.x1, this.y1); + if (lineStartFrame) { + lineStartOffset = this.lineStartOrigin * lineStartFrame.cutWidth; + width += lineStartOffset; + height = lineStartFrame.cutHeight; + } + if (lineEndFrame) { + width += ((1 - this.lineEndOrigin) * lineEndFrame.cutWidth); + height = Math.max(height, lineEndFrame.cutHeight); + } + if (lineBodyFrame) { + var lineBodyHeight = (this.lineBodyWidth !== undefined) ? this.lineBodyWidth : lineBodyFrame.cutHeight; + height = Math.max(height, lineBodyHeight); + } + + width = Math.floor(width); + height = Math.floor(height); + + // no line + if ((width <= 0) || (height <= 0)) { + this + .setPosition(this.x0, this.y0) + .setSize(1, 1) + .setRotation(rotation); + return this; + } + + if ((this.width === width) && (this.height === height)) { + this.setSize(width + 1, height + 1); // Force size changing, to clear content + } + + this + .setSize(width, height) + .setPosition(this.x0, this.y0) + .setRotation(rotation) + .setOrigin(0, 0); // Set origin to (0,0) before pasting textures + + var offsetX, offsetY; + var remainderWidth = this.width; + + this.beginDraw(); + + // Draw line start + if (lineStartFrame) { + offsetX = 0; + offsetY = (this.height - lineStartFrame.cutHeight) / 2; + this.drawFrame(this.lineStartTexture, this.lineStartFrameName, offsetX, offsetY); + remainderWidth -= lineStartFrame.cutWidth; + } + // Draw line end + if (lineEndFrame) { + offsetX = this.width - lineEndFrame.cutWidth; + offsetY = (this.height - lineEndFrame.cutHeight) / 2; + this.drawFrame(this.lineEndTexture, this.lineEndFrameName, offsetX, offsetY); + remainderWidth -= lineEndFrame.cutWidth; + } + + // Draw line body + if (lineBodyFrame && (remainderWidth > 0) && (lineBodyHeight > 0)) { + offsetX = (lineStartFrame) ? lineStartFrame.cutWidth : 0; + offsetY = (this.height - lineBodyHeight) / 2; + + if (this.lineBodyExtendMode === 0) { + DrawImage.call(this, + this.lineBodyTexture, this.lineBodyFrameName, + offsetX, offsetY, + remainderWidth, lineBodyHeight + ); + } else { + DrawTileSprite.call(this, + this.lineBodyTexture, this.lineBodyFrameName, + offsetX, offsetY, + remainderWidth, lineBodyHeight + ); + } + } + + this.endDraw(); + + var originX = 1 - ((width - lineStartOffset) / width); + this.setOrigin(originX, 0.5); +} + +export default UpdateTexture; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/Creator.js new file mode 100644 index 000000000..515ea877b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/Creator.js @@ -0,0 +1,13 @@ +import NinePatch from './NinePatch.js'; + +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var gameObject = new NinePatch(this.scene, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/Factory.js new file mode 100644 index 000000000..aaa324623 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/Factory.js @@ -0,0 +1,7 @@ +import NinePatch from './NinePatch.js'; + +export default function (x, y, width, height, key, baseFrame, columns, rows, config) { + var gameObject = new NinePatch(this.scene, x, y, width, height, key, baseFrame, columns, rows, config); + this.scene.add.existing(gameObject); + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/NinePatch.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/NinePatch.d.ts new file mode 100644 index 000000000..50391b7e1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/NinePatch.d.ts @@ -0,0 +1,114 @@ +// import * as Phaser from 'phaser'; + +export default NinePatch; + +declare namespace NinePatch { + + interface IConfig { + x?: number, y?: number, + width?: number, height?: number, + + key?: string, baseFrame?: string, + getFrameNameCallback?: (colIndex: number, rowIndex: number, baseFrame: string) => (string | undefined), + + columns?: (number | undefined)[], + rows?: (number | undefined)[], + + stretchMode?: 0 | 1 | 'scale' | 'repeat' | + { + edge?: 0 | 1 | 'scale' | 'repeat', + internal?: 0 | 1 | 'scale' | 'repeat', + }, + + maxFixedPartScale?: number, + maxFixedPartScaleX?: number, + maxFixedPartScaleY?: number, + + preserveRatio?: boolean, + } + +} + +declare class NinePatch extends Phaser.GameObjects.RenderTexture { + constructor( + scene: Phaser.Scene, + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + key: string, + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + key: string, + columns: (number | undefined)[], rows: (number | undefined)[], + config?: NinePatch.IConfig + ) + + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + key: string, baseFrame: string, + columns: (number | undefined)[], rows: (number | undefined)[], + config?: NinePatch.IConfig + ) + + resize(width: number, height: number): this; + + setBaseTexture( + key: string, + baseFrame: string | undefined, + columns: (number | undefined)[], + rows: (number | undefined)[] + ): this; + + setStretchMode( + mode: 0 | 1 | 'scale' | 'repeat' | + { + edge?: 0 | 1 | 'scale' | 'repeat', + internal?: 0 | 1 | 'scale' | 'repeat', + } + ): this; + + setGetFrameNameCallback( + callback: (colIndex: number, rowIndex: number, baseFrame: string) => (string | undefined) + ): this; + + updateTexture(): this; + + setPreserveRatio(enable?: boolean): this; + preserveRatio: boolean; + + setMaxFixedPartScale(scaleX: number, scaleY?: number): this; + maxFixedPartScaleX: number; + maxFixedPartScaleY: number; + + readonly minWidth: number; + + readonly minHeight: number; + + readonly fixedPartScaleX: number; + + readonly fixedPartScaleY: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/NinePatch.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/NinePatch.js new file mode 100644 index 000000000..35c014d69 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/ninepatch/NinePatch.js @@ -0,0 +1,21 @@ +import NinePatchBase from '../../../utils/ninepatch/NinePatch.js'; +import DrawImage from '../utils/DrawImage.js'; +import DrawTileSprite from '../utils/DrawTileSprite.js'; + +const RenderTexture = Phaser.GameObjects.RenderTexture; + +class NinePatch extends NinePatchBase(RenderTexture, 'rexNinePatch') { +} + +var Methods = { + _beginDraw: RenderTexture.prototype.beginDraw, + _endDraw: RenderTexture.prototype.endDraw, + _drawImage: DrawImage, + _drawTileSprite: DrawTileSprite, +} +Object.assign( + NinePatch.prototype, + Methods +); + +export default NinePatch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/utils/DrawImage.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/utils/DrawImage.js new file mode 100644 index 000000000..b12078b75 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/utils/DrawImage.js @@ -0,0 +1,11 @@ +import GetStampGameObject from './GetStampGameObject.js'; + +var DrawImage = function (key, frame, x, y, width, height) { + var gameObject = GetStampGameObject(this, 'Image') + .setTexture(key, frame) + .setDisplaySize(width, height); + + this.batchDraw(gameObject, x, y); +} + +export default DrawImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/utils/DrawTileSprite.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/utils/DrawTileSprite.js new file mode 100644 index 000000000..1dfebc447 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/utils/DrawTileSprite.js @@ -0,0 +1,11 @@ +import GetStampGameObject from './GetStampGameObject.js'; + +var DrawTileSprite = function (key, frame, x, y, width, height) { + var gameObject = GetStampGameObject(this, 'TileSprite') + .setTexture(key, frame) + .setSize(width, height); + + this.batchDraw(gameObject, x, y); +} + +export default DrawTileSprite; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/utils/GetStampGameObject.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/utils/GetStampGameObject.js new file mode 100644 index 000000000..f72ac6849 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/rendertexture/utils/GetStampGameObject.js @@ -0,0 +1,30 @@ +import GetGame from '../../../utils/system/GetGame'; + +const GameObjectClasses = Phaser.GameObjects; + +var GameObjects = undefined; + +var GetStampGameObject = function (gameObject, className) { + if (!GameObjects) { + GameObjects = {}; + + GetGame(gameObject).events.once('destroy', function () { + for (var name in GameObjects) { + GameObjects[name].destroy(); + } + GameObjects = undefined; + }); + } + + if (!GameObjects.hasOwnProperty(className)) { + var scene = GetGame(gameObject).scene.systemScene; + var gameObject = new GameObjectClasses[className](scene); + gameObject.setOrigin(0); + + GameObjects[className] = gameObject; + } + + return GameObjects[className]; +}; + +export default GetStampGameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/effectlayer/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/effectlayer/Creator.js new file mode 100644 index 000000000..acf577142 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/effectlayer/Creator.js @@ -0,0 +1,18 @@ +import EffectLayer from './EffectLayer.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var key = GetAdvancedValue(config, 'key', undefined); + var x = GetAdvancedValue(config, 'x', undefined); + var y = GetAdvancedValue(config, 'y', undefined); + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', undefined); + var gameObject = new EffectLayer(this.scene, key, x, y, width, height); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/effectlayer/EffectLayer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/effectlayer/EffectLayer.js new file mode 100644 index 000000000..bba2e1727 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/effectlayer/EffectLayer.js @@ -0,0 +1,179 @@ +import RoundUpPowerOf2 from '../../../../utils/math/RoundUpPowerOf2.js'; + +const Shader = Phaser.GameObjects.Shader; +const AddItem = Phaser.Utils.Array.Add; +const RemoveItem = Phaser.Utils.Array.Remove; + +class EffectLayer extends Shader { + constructor(scene, key, x, y, width, height) { + // gameObjects -> render-texture -> shader + + if (typeof (x) === 'object') { + var config = x; + ({ x, y, width, height } = config) + } + + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (width === undefined) { width = scene.sys.scale.width; } + if (height === undefined) { height = scene.sys.scale.height; } + + // render-texture -> shader + width = RoundUpPowerOf2(width); + height = RoundUpPowerOf2(height); + var rt = scene.make.renderTexture({ x: x, y: y, width: width, height: height, add: false }); + + super(scene, key, x, y, width, height); + this.type = 'rexEffectLayer'; + + this + .setSampler2DBuffer('iChannel0', rt.frame.glTexture, width, height, 0) + .setScrollFactor(0) + .setOrigin(0); + + this.rt = rt; + + this.children = []; + + this.boot(); + } + + boot() { + this.scene.game.events.on('prerender', this.drawTargets, this); + this.scene.sys.scale.on('resize', this.onWindowResize, this); + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + this.scene.game.events.off('prerender', this.drawTargets, this); + this.scene.sys.scale.off('resize', this.onWindowResize, this); + // Private texture will be removed by shader game object + this.clear(); + + super.destroy(fromScene); + + this.rt.destroy(fromScene); + this.rt = null; + } + + drawTargets() { + // Assume that game objects are displayed on main camera. + var camera = this.scene.sys.cameras.main; + var offsetX = camera.scrollX + this.x; + var offsetY = camera.scrollY + this.y; + + var rt = this.rt; + rt.clear(); + var child; + for (var i = 0, cnt = this.children.length; i < cnt; i++) { + child = this.children[i]; + rt + .draw( + child, + child.x - offsetX, + child.y - offsetY + ) + } + } + + set1f(key, value) { + this.setUniform(`${key}.value`, value); + return this; + } + + set2f(key, x, y) { + this.setUniform(`${key}.value.x`, x); + this.setUniform(`${key}.value.y`, y); + return this; + } + + set3f(key, x, y, z) { + this.setUniform(`${key}.value.x`, x); + this.setUniform(`${key}.value.y`, y); + this.setUniform(`${key}.value.z`, z); + return this; + } + + setFloat4(key, x, y, z, w) { + this.setUniform(`${key}.value.x`, x); + this.setUniform(`${key}.value.y`, y); + this.setUniform(`${key}.value.z`, z); + this.setUniform(`${key}.value.w`, w); + return this; + } + + contains(gameObject) { + return (this.children.indexOf(gameObject) !== -1); + } + + add(gameObjects) { + AddItem(this.children, gameObjects, 0, + // Callback of item added + function (gameObject) { + gameObject.once('destroy', this.onChildDestroy, this); + }, this); + return this; + } + + remove(gameObjects, destroyChild) { + if (destroyChild === undefined) { + destroyChild = false; + } + RemoveItem(this.children, gameObjects, + // Callback of item removed + function (gameObject) { + gameObject.off('destroy', this.onChildDestroy, this); + if (destroyChild) { + gameObject.destroy(); + } + } + ); + return this; + } + + clear(destroyChild) { + var gameObject; + for (var i = 0, cnt = this.children.length; i < cnt; i++) { + gameObject = this.children[i]; + gameObject.off('destroy', this.onChildDestroy, this); + if (destroyChild) { + gameObject.destroy(); + } + } + this.children.length = 0; + return this; + } + + onChildDestroy(child, fromScene) { + this.remove(child, !fromScene); + } + + resize(width, height) { + width = RoundUpPowerOf2(width); + height = RoundUpPowerOf2(height); + + var rt = this.rt; + + // Set size of render texture + rt.setSize(width, height); + this.setSampler2DBuffer('iChannel0', rt.frame.glTexture, width, height, 0); + + // Set size of shader + this.setSize(width, height); + return this; + } + + onWindowResize() { + + // Get new window size + var width = this.scene.sys.scale.width; + var height = this.scene.sys.scale.height; + this.resize(width, height); + } +} + +export default EffectLayer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/effectlayer/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/effectlayer/Factory.js new file mode 100644 index 000000000..bf62fa535 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/effectlayer/Factory.js @@ -0,0 +1,7 @@ +import EffectLayer from './EffectLayer.js'; + +export default function (key, x, y, width, height) { + var gameObject = new EffectLayer(this.scene, key, x, y, width, height); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/Creator.js new file mode 100644 index 000000000..2b9c13f9f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/Creator.js @@ -0,0 +1,11 @@ +import OutlineEffectLayer from './OutlineEffectLayer.js'; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var gameObject = new OutlineEffectLayer(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/Factory.js new file mode 100644 index 000000000..2dde9fc34 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/Factory.js @@ -0,0 +1,7 @@ +import OutlineEffectLayer from './OutlineEffectLayer.js'; + +export default function (config) { + var gameObject = new OutlineEffectLayer(this.scene, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/OutlineEffectLayer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/OutlineEffectLayer.js new file mode 100644 index 000000000..c680a41e2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/OutlineEffectLayer.js @@ -0,0 +1,100 @@ +import EffectLayer from '../effectlayer/EffectLayer.js'; +import { GetFrag } from './outline-frag.js'; + +const BaseShader = Phaser.Display.BaseShader; +const GetValue = Phaser.Utils.Objects.GetValue; +const IntegerToRGB = Phaser.Display.Color.IntegerToRGB; +const Color = Phaser.Display.Color; + +class OutlineEffectLayer extends EffectLayer { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + // Note: quality can't be changed during runtime + var frag = GetFrag(config) // GLSL shader + var uniforms = { + knockout: { type: '1f', value: true }, + thickness: { type: '2f', value: { x: 0, y: 0 } }, + outlineColor: { type: '3f', value: { x: 0, y: 0, z: 0 } } + } + var baseShader = new BaseShader('Outline', frag, undefined, uniforms); + super(scene, baseShader, config); + this.type = 'rexOutlineEffectLayer'; + + this._knockout = 0; + this._thickness = 0; + this._outlineColor = new Color(); + + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.setKnockout(GetValue(o, 'knockout', false)); + this.setThickness(GetValue(o, 'thickness', 3)); + this.setOutlineColor(GetValue(o, 'outlineColor', 0xffffff)); + return this; + } + + // knockout + get knockout() { + return this._knockout; + } + + set knockout(value) { + value = !!value; + if (this._knockout === value) { + return; + } + + this._knockout = value; + this.set1f('knockout', value); + } + + setKnockout(value) { + this.knockout = value; + return this; + } + + // thickness + get thickness() { + return this._thickness; + } + + set thickness(value) { + if (this._thickness === value) { + return; + } + + this._thickness = value; + this.set2f('thickness', value, value); + } + + setThickness(value) { + this.thickness = value; + return this; + } + + // outlineColor + get outlineColor() { + return this._outlineColor; + } + + set outlineColor(value) { + if (typeof (value) === 'number') { + value = IntegerToRGB(value); + } + // value: {r, g, b} + var color = this._outlineColor; + color.setFromRGB(value); + this.set3f('outlineColor', color.redGL, color.greenGL, color.blueGL); + } + + setOutlineColor(value) { + this.outlineColor = value; + return this; + } +} + +export default OutlineEffectLayer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/outline-frag.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/outline-frag.js new file mode 100644 index 000000000..6aa3ab3da --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shader/effectlayer/outline/outline-frag.js @@ -0,0 +1,63 @@ +// Reference: https://github.com/pixijs/pixi-filters/blob/master/filters/outline/src/outline.frag + +const frag = ` +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D iChannel0; +varying vec2 fragCoord; +uniform vec2 resolution; + +// Effect parameters +uniform bool knockout; +uniform vec2 thickness; +uniform vec3 outlineColor; // (0, 0, 0); + +const float DOUBLE_PI = 3.14159265358979323846264 * 2.; + +void main() { + vec2 uv = fragCoord / resolution; + if ((thickness.x > 0.0) || (thickness.y > 0.0)) { + vec4 front = texture2D(iChannel0, uv); + vec2 mag = thickness/resolution; + vec4 curColor; + float maxAlpha = 0.; + vec2 offset; + for (float angle = 0.; angle <= DOUBLE_PI; angle += #{angleStep}) { + offset = vec2(mag.x * cos(angle), mag.y * sin(angle)); + curColor = texture2D(iChannel0, uv + offset); + maxAlpha = max(maxAlpha, curColor.a); + } + float resultAlpha = max(maxAlpha, front.a); + vec4 resultColor = vec4((front.rgb + (outlineColor.rgb * (1. - front.a) * resultAlpha)), resultAlpha); + + if (knockout && (resultColor == front)) { + gl_FragColor = vec4(0); + } else { + gl_FragColor = resultColor; + } + + } else { + if (knockout) { + gl_FragColor = vec4(0); + } else { + gl_FragColor = texture2D(iChannel0, uv); + } + + } + +}`; + + +const MAX_SAMPLES = 100; +const MIN_SAMPLES = 1; +export function GetFrag({ quality = 0.1 }) { + var samples = Math.max((quality * MAX_SAMPLES), MIN_SAMPLES); + var angleStep = (Math.PI * 2 / samples).toFixed(7); + return frag.replace(/\#\{angleStep\}/, angleStep); +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Checkbox.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Checkbox.d.ts new file mode 100644 index 000000000..b0a8ceb8f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Checkbox.d.ts @@ -0,0 +1,36 @@ +import CheckboxShape from './CheckboxShape'; +import Click from '../../../input/button/Button'; + +export default Checkbox; + +declare namespace Checkbox { + interface IConfig extends CheckboxShape.IConfig { + readOnly?: boolean; + input?: Click.IConfig, + } +} + +declare class Checkbox extends CheckboxShape { + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + color?: number, + config?: Checkbox.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + config?: Checkbox.IConfig + ); + + constructor( + scene: Phaser.Scene, + config?: Checkbox.IConfig + ); + + setReadOnly(readOnly?: boolean): this; + readOnly: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Checkbox.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Checkbox.js new file mode 100644 index 000000000..38c83f8c7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Checkbox.js @@ -0,0 +1,35 @@ +import CheckboxShape from './CheckboxShape.js'; +import Click from '../../../input/button/Button.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Checkbox extends CheckboxShape { + constructor(scene, x, y, width, height, color, config) { + super(scene, x, y, width, height, color, config); + + this._click = new Click(this, GetValue(config, 'click')); + this._click.on('click', function () { + this.toggleValue(); + }, this); + + this.setReadOnly(GetValue(config, 'readOnly', false)); + } + + get readOnly() { + return !this._click.enable; + } + + set readOnly(value) { + this._click.enable = !value; + } + + setReadOnly(enable) { + if (enable === undefined) { + enable = true; + } + this.readOnly = enable; + return this; + } +} + +export default Checkbox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShape.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShape.d.ts new file mode 100644 index 000000000..4595eb539 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShape.d.ts @@ -0,0 +1,84 @@ +import BaseShapes from '../shapes/BaseShapes'; + +export default Checkbox; + +declare namespace Checkbox { + interface IConfig { + x: number, y: number, + width: number, height: number, + + color?: number, boxFillAlpha?: number, + uncheckedColor?: number, uncheckedBoxFillAlpha?: number, + + boxLineWidth?: number, boxStrokeColor?: number, boxStrokeAlpha?: number, + uncheckedBoxStrokeColor?: number, uncheckedBoxStrokeAlpha?: number, + + boxSize?: number, + checkerSize?: number, + + checkerColor?: number, checkerAlpha?: number, + + circleBox?: boolean, + + animationDuration?: number, + + checked?: boolean, + value?: boolean, + } +} + +declare class Checkbox extends BaseShapes { + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + color?: number, + config?: Checkbox.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + config?: Checkbox.IConfig + ); + + constructor( + scene: Phaser.Scene, + config?: Checkbox.IConfig + ); + + setValue(value: boolean): this; + toggleValue(): this; + value: boolean; + + setChecked(checked?: boolean): this; + toggleChecked(): this; + checked: boolean; + + setBoxShape(isCircleShape?: boolean): this; + + setBoxFillStyle(color: number, alpha?: number): this; + boxFillColor: number; + boxFillAlpha: number; + setUncheckedBoxFillStyle(color: number, alpha?: number): this; + uncheckedBoxFillColor: number; + uncheckedBoxFillAlpha: number; + + setBoxStrokeStyle(lineWidth: number, color: number, alpha?: number): this; + boxLineWidth: number; + boxStrokeColor: number; + boxStrokeAlpha: number; + + setUncheckedBoxStrokeStyle(lineWidth: number, color: number, alpha?: number): this; + uncheckedBoxLineWidth: number; + uncheckedBoxStrokeColor: number; + uncheckedBoxStrokeAlpha: number; + + setCheckerStyle(color: number, alpha?: number): this; + checkerColor: number; + checkAlpha: number; + + setCheckerAnimationDuration(duration: number): this; + checkerAnimDuration: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShape.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShape.js new file mode 100644 index 000000000..14057682a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShape.js @@ -0,0 +1,156 @@ +import BaseShapes from '../shapes/BaseShapes.js'; +import Methods from './methods/Methods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const DefaultBoxFillColor = 0x005cb2; +const DefaultCheckerColor = 0xffffff; + +class CheckboxShape extends BaseShapes { + constructor(scene, x, y, width, height, color, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 2); + height = GetValue(config, 'height', 2); + color = GetValue(config, 'color', DefaultBoxFillColor); + } else if (IsPlainObject(color)) { + config = color; + color = GetValue(config, 'color', DefaultBoxFillColor); + } + + super(scene, x, y, width, height); + this.type = 'rexCheckbox'; + + if (color === undefined) { + color = DefaultBoxFillColor; + } + + this.setBoxShape( + GetValue(config, 'circleBox', false), + ); + + this.setBoxFillStyle( + color, + GetValue(config, 'boxFillAlpha', 1) + ); + + this.setUncheckedBoxFillStyle( + GetValue(config, 'uncheckedColor', null), + GetValue(config, 'uncheckedBoxFillAlpha', 1) + ) + + this.setBoxStrokeStyle( + GetValue(config, 'boxLineWidth', 4), + GetValue(config, 'boxStrokeColor', color), + GetValue(config, 'boxStrokeAlpha', 1) + ); + + this.setUncheckedBoxStrokeStyle( + this.boxLineWidth, + GetValue(config, 'uncheckedBoxStrokeColor', this.boxStrokeColor), + GetValue(config, 'uncheckedBoxStrokeAlpha', this.boxStrokeAlpha) + ); + + + this.setCheckerStyle( + GetValue(config, 'checkerColor', DefaultCheckerColor), + GetValue(config, 'checkerAlpha', 1) + ); + + this.setBoxSize( + GetValue(config, 'boxSize', 1) + ); + + this.setCheckerSize( + GetValue(config, 'checkerSize', 1) + ) + + this.setCheckerAnimationDuration( + GetValue(config, 'animationDuration', 150) + ); + + this.buildShapes(); + + var value = GetValue(config, 'checked'); + if (value === undefined) { + value = GetValue(config, 'value', false); + } + this.setValue(value); + } + + get value() { + return this._value; + } + + set value(value) { + value = !!value; + + if (this._value === value) { + return; + } + + this.dirty = true; + this._value = value; + + if (value) { + this.playCheckerAnimation(); + } else { + this.stopCheckerAnimation(); + } + + this.emit('valuechange', value); + } + + setValue(value) { + this.value = value; + return this; + } + + toggleValue() { + this.setValue(!this.value); + return this; + } + + get checked() { + return this.value; + } + + set checked(value) { + this.value = value; + } + + setChecked(checked) { + if (checked === undefined) { + checked = true; + } + this.setValue(checked); + return this; + } + + toggleChecked() { + this.toggleValue(); + return this; + } + + get checkerAnimProgress() { + return this._checkerAnimProgress; + } + + set checkerAnimProgress(value) { + if (this._checkerAnimProgress === value) { + return; + } + + this._checkerAnimProgress = value; + this.dirty = true; + } +} + +Object.assign( + CheckboxShape.prototype, + Methods, +) + +export default CheckboxShape; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShapeCreator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShapeCreator.js new file mode 100644 index 000000000..c47a8c43b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShapeCreator.js @@ -0,0 +1,19 @@ +import CheckboxShape from './CheckboxShape.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', width); + var color = GetAdvancedValue(config, 'color', 0x005cb2); + var gameObject = new CheckboxShape(this.scene, 0, 0, width, height, color, config); + + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShapeFactory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShapeFactory.js new file mode 100644 index 000000000..a550a60cf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/CheckboxShapeFactory.js @@ -0,0 +1,7 @@ +import CheckboxShape from './CheckboxShape.js'; + +export default function (x, y, width, height, color, config) { + var gameObject = new CheckboxShape(this.scene, x, y, width, height, color, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Creator.js new file mode 100644 index 000000000..6e050bad1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Creator.js @@ -0,0 +1,19 @@ +import Checkbox from './Checkbox.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', width); + var color = GetAdvancedValue(config, 'color', 0x005cb2); + var gameObject = new Checkbox(this.scene, 0, 0, width, height, color, config); + + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Factory.js new file mode 100644 index 000000000..ed6376e0c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/Factory.js @@ -0,0 +1,7 @@ +import Checkbox from './Checkbox.js'; + +export default function (x, y, width, height, color, config) { + var gameObject = new Checkbox(this.scene, x, y, width, height, color, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/CheckerAnimationMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/CheckerAnimationMethods.js new file mode 100644 index 000000000..a28fe6b50 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/CheckerAnimationMethods.js @@ -0,0 +1,36 @@ +import EaseValueTask from '../../../../utils/ease/EaseValueTask.js'; + +export default { + setCheckerAnimationDuration(duration) { + if (duration === undefined) { + duration = 0; + } + this.checkerAnimDuration = duration; + return this; + }, + + playCheckerAnimation() { + if (this.checkerAnimProgressTask === undefined) { + this.checkerAnimProgressTask = new EaseValueTask(this, { eventEmitter: null }); + } + + this.checkerAnimProgressTask.restart({ + key: 'checkerAnimProgress', + from: 0, + to: 1, + duration: this.checkerAnimDuration, + }); + + return this; + }, + + stopCheckerAnimation() { + if (this.checkerAnimProgressTask === undefined) { + return this; + } + + this.checkerAnimProgressTask.stop(); + return this; + }, + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/Methods.js new file mode 100644 index 000000000..9dc8c900c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/Methods.js @@ -0,0 +1,17 @@ +import StyleMethods from './StyleMethods.js'; +import SizeMethods from './SizeMethods.js'; +import ShapesUpdateMethods from './ShapesUpdateMethods.js'; +import CheckerAnimationMethods from './CheckerAnimationMethods.js'; + +var methods = { + +} +Object.assign( + methods, + StyleMethods, + SizeMethods, + ShapesUpdateMethods, + CheckerAnimationMethods, +) + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/ShapesUpdateMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/ShapesUpdateMethods.js new file mode 100644 index 000000000..05164c313 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/ShapesUpdateMethods.js @@ -0,0 +1,74 @@ +import { Lines, RoundRectangle } from '../../shapes/geoms'; + +export default { + buildShapes() { + this + .addShape(new RoundRectangle().setName('box')) + .addShape(new Lines().setName('checker')) + }, + + updateShapes() { + var centerX = this.width / 2, + centerY = this.height / 2, + radius = Math.min(centerX, centerY); + var width = radius * 2; + var x = centerX - radius, + y = centerY - radius; + + var boxLineWidth = this.boxLineWidth; + var checkLineWidth = Math.max(width / 10, 2); + + var boxShape = this.getShape('box'); + var checkerShape = this.getShape('checker'); + + // Setup shapes + if (this.isSizeChanged) { + // Box + var posOffset = width * (1 - this.boxSize) / 2; + var halfBoxLineWidth = boxLineWidth / 2; + var boxInnerWidth = (width * this.boxSize) - boxLineWidth; + boxShape + .setTopLeftPosition(x + halfBoxLineWidth + posOffset, y + halfBoxLineWidth + posOffset) + .setSize(boxInnerWidth, boxInnerWidth) + + if (this.isCircleShape) { + boxShape.setRadius(boxInnerWidth / 2); + } else { + boxShape.setRadius(0); + } + + // Checker + var posOffset = width * (1 - this.checkerSize) / 2; + var unit = (width * this.checkerSize) / 4; + var u0 = 0, u1 = unit * 1, u2 = unit * 2, u3 = unit * 3; + checkerShape + .startAt(u1, u2) + .lineTo(u2, u3) + .lineTo(u3, u1) + .offset(x + posOffset, y + posOffset) + .end() + } + + // Set styles + if (this.checked) { + boxShape + .fillStyle(this.boxFillColor, this.boxFillAlpha) + .lineStyle(boxLineWidth, this.boxStrokeColor, this.boxStrokeAlpha) + + checkerShape + .lineStyle(checkLineWidth, this.checkerColor) + } else { + boxShape + .fillStyle(this.uncheckedBoxFillColor, this.uncheckedBoxFillAlpha) + .lineStyle(boxLineWidth, this.uncheckedBoxStrokeColor, this.uncheckedBoxStrokeAlpha) + + checkerShape + .lineStyle() + } + + // Play checker animation + if (this.checked) { + checkerShape.setDisplayPathSegment(this.checkerAnimProgress); + } + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/SizeMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/SizeMethods.js new file mode 100644 index 000000000..6c8ec05b3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/SizeMethods.js @@ -0,0 +1,17 @@ +export default { + setBoxSize(size) { + this.dirty = this.dirty || + (this.boxSize !== size); + + this.boxSize = size; + return this; + }, + + setCheckerSize(size) { + this.dirty = this.dirty || + (this.checkerSize !== size); + + this.checkerSize = size; + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/StyleMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/StyleMethods.js new file mode 100644 index 000000000..feb681a83 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/checkbox/methods/StyleMethods.js @@ -0,0 +1,84 @@ +export default { + setBoxFillStyle(color, alpha) { + if (alpha === undefined) { + alpha = 1; + } + this.dirty = this.dirty || + (this.boxFillColor !== color) || + (this.boxFillAlpha !== alpha); + + this.boxFillColor = color; + this.boxFillAlpha = alpha; + return this; + }, + + setUncheckedBoxFillStyle(color, alpha) { + if (alpha === undefined) { + alpha = 1; + } + this.dirty = this.dirty || + (this.uncheckedBoxFillColor !== color) || + (this.uncheckedBoxFillAlpha !== alpha); + + this.uncheckedBoxFillColor = color; + this.uncheckedBoxFillAlpha = alpha; + return this; + }, + + setBoxStrokeStyle(lineWidth, color, alpha) { + if (alpha === undefined) { + alpha = 1; + } + this.dirty = this.dirty || + (this.boxLineWidth !== lineWidth) || + (this.boxStrokeColor !== color) || + (this.boxStrokeAlpha !== alpha); + + this.boxLineWidth = lineWidth; + this.boxStrokeColor = color; + this.boxStrokeAlpha = alpha; + return this; + }, + + setUncheckedBoxStrokeStyle(lineWidth, color, alpha) { + if (alpha === undefined) { + alpha = 1; + } + this.dirty = this.dirty || + (this.uncheckedBoxLineWidth !== lineWidth) || + (this.uncheckedBoxStrokeColor !== color) || + (this.uncheckedBoxStrokeAlpha !== alpha); + + this.uncheckedBoxLineWidth = lineWidth; + this.uncheckedBoxStrokeColor = color; + this.uncheckedBoxStrokeAlpha = alpha; + return this; + }, + + setCheckerStyle(color, alpha) { + if (alpha === undefined) { + alpha = 1; + } + this.dirty = this.dirty || + (this.checkerColor !== color) || + (this.checkAlpha !== alpha); + + this.checkerColor = color; + this.checkAlpha = alpha; + return this; + }, + + setBoxShape(isCircleShape) { + if (isCircleShape === undefined) { + isCircleShape = false; + } + if (this.isCircleShape === isCircleShape) { + return this; + } + + this.isCircleShape = isCircleShape; + this.isSizeChanged = true; + this.dirty = true; + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/CircularProgress.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/CircularProgress.d.ts new file mode 100644 index 000000000..1b121c3ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/CircularProgress.d.ts @@ -0,0 +1,89 @@ +import BaseShapes from '../shapes/BaseShapes'; + +// import * as Phaser from 'phaser'; +export default CircularProgress; + +declare namespace CircularProgress { + + type ValueChangeCallbackType = ( + newValue: number, + oldValue: number, + circularProgress: CircularProgress + ) => void; + + interface IConfig { + x?: number, y?: number, + radius?: number, + + barColor?: string | number, + trackColor?: string | number, + centerColor?: string | number, + thickness?: number, + startAngle?: number, + anticlockwise?: boolean, + + value?: number, + + easeValue?: { + duration?: number, + ease?: string + }, + + valuechangeCallback: ValueChangeCallbackType, + + } + + namespace Events { + type ValueChangeCallbackType = ( + newValue: number, + oldValue: number, + circularProgress: CircularProgress + ) => void; + } +} + +declare class CircularProgress extends BaseShapes { + constructor( + scene: Phaser.Scene, + config?: CircularProgress.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + radius?: number, + barColor?: string | number, + value?: number, + config?: CircularProgress.IConfig + ); + + value: number; + getValue(min?: number, max?: number): number; + setValue(value?: number, min?: number, max?: number): this; + addValue(inc?: number, min?: number, max?: number): this; + + easeValueTo(value?: number, min?: number, max?: number): this; + stopEaseValue(): this; + setEaseValueDuration(duration: number): this; + setEaseValueFunction(ease: string): this; + + radius: number; + setRadius(radius: number): this; + + trackColor: string; + setTrackColor(trackColor?: string | number): this; + + setThickness(thickness: number): this; + + barColor: string; + setBarColor(barColor?: string | number): this; + + startAngle: number; + setStartAngle(startAngle: number): this; + + anticlockwise: boolean; + setAnticlockwise(anticlockwise: boolean): this; + + centerColor: string; + setCenterColor(centerColor?: string | number): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/CircularProgress.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/CircularProgress.js new file mode 100644 index 000000000..3d239b897 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/CircularProgress.js @@ -0,0 +1,166 @@ +import BaseShapes from '../shapes/BaseShapes.js'; +import ProgressBase from '../../../utils/progressbase/ProgressBase.js'; +import ShapesUpdateMethods from './ShapesUpdateMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const Clamp = Phaser.Math.Clamp; + +const DefaultStartAngle = Phaser.Math.DegToRad(270); + +class CircularProgress extends ProgressBase(BaseShapes) { + constructor(scene, x, y, radius, barColor, value, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + radius = GetValue(config, 'radius', 1); + barColor = GetValue(config, 'barColor', undefined); + value = GetValue(config, 'value', 0); + } + + if (radius === undefined) { radius = 1; } + + var width = radius * 2; + super(scene, x, y, width, width); + this.type = 'rexCircularProgress'; + + this.bootProgressBase(config); + + this.setRadius(radius); + this.setTrackColor(GetValue(config, 'trackColor', undefined)); + this.setBarColor(barColor); + this.setCenterColor(GetValue(config, 'centerColor', undefined)); + + this.setThickness(GetValue(config, 'thickness', 0.2)); + this.setStartAngle(GetValue(config, 'startAngle', DefaultStartAngle)); + this.setAnticlockwise(GetValue(config, 'anticlockwise', false)); + + this.buildShapes(); + + this.setValue(value); + } + + resize(width, height) { + width = Math.floor(Math.min(width, height)); + if (width === this.width) { + return this; + } + + super.resize(width, width); + this.setRadius(width / 2); + return this; + } + + get radius() { + return this._radius; + } + + set radius(value) { + this.dirty = this.dirty || (this._radius != value); + this._radius = value; + var width = value * 2; + this.resize(width, width); + } + + setRadius(radius) { + this.radius = radius; + return this; + } + + get trackColor() { + return this._trackColor; + } + + set trackColor(value) { + this.dirty = this.dirty || (this._trackColor != value); + this._trackColor = value; + } + + setTrackColor(color) { + this.trackColor = color; + return this; + } + + get barColor() { + return this._barColor; + } + + set barColor(value) { + this.dirty = this.dirty || (this._barColor != value); + this._barColor = value; + } + + setBarColor(color) { + this.barColor = color; + return this; + } + + get startAngle() { + return this._startAngle; + } + + set startAngle(value) { + this.dirty = this.dirty || (this._startAngle != value); + this._startAngle = value; + } + + setStartAngle(angle) { + this.startAngle = angle; + return this; + } + + get anticlockwise() { + return this._anticlockwise; + } + + set anticlockwise(value) { + this.dirty = this.dirty || (this._anticlockwise != value); + this._anticlockwise = value; + } + + setAnticlockwise(anticlockwise) { + if (anticlockwise === undefined) { + anticlockwise = true; + } + this.anticlockwise = anticlockwise; + return this; + } + + get thickness() { + return this._thickness; + } + + set thickness(value) { + value = Clamp(value, 0, 1); + this.dirty = this.dirty || (this._thickness != value); + this._thickness = value; + } + + setThickness(thickness) { + this.thickness = thickness; + return this; + } + + get centerColor() { + return this._centerColor; + } + + set centerColor(value) { + this.dirty = this.dirty || (this._centerColor != value); + this._centerColor = value; + } + + setCenterColor(color) { + this.centerColor = color; + return this; + } + +} + +Object.assign( + CircularProgress.prototype, + ShapesUpdateMethods, +) + +export default CircularProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/Creator.js new file mode 100644 index 000000000..ddb2252eb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/Creator.js @@ -0,0 +1,13 @@ +import CircularProgress from './CircularProgress.js'; + +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var gameObject = new CircularProgress(this.scene, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/Factory.js new file mode 100644 index 000000000..15c3a0717 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/Factory.js @@ -0,0 +1,7 @@ +import CircularProgress from './CircularProgress.js'; + +export default function (x, y, radius, barColor, value, config) { + var gameObject = new CircularProgress(this.scene, x, y, radius, barColor, value, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/ShapesUpdateMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/ShapesUpdateMethods.js new file mode 100644 index 000000000..50849ad21 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/circularprogress/ShapesUpdateMethods.js @@ -0,0 +1,64 @@ +import { Arc, Circle } from '../shapes/geoms'; + +const RadToDeg = Phaser.Math.RadToDeg; + +export default { + buildShapes() { + this + .addShape((new Circle()).setName('track')) + .addShape((new Arc()).setName('bar')) + .addShape((new Circle()).setName('center')) + }, + + updateShapes() { + var x = this.radius; + var lineWidth = this.thickness * this.radius; + var barRadius = this.radius - (lineWidth / 2); + var centerRadius = this.radius - lineWidth; + + // Track shape + var trackShape = this.getShape('track'); + if ((this.trackColor != null) && (lineWidth > 0)) { + trackShape + .setCenterPosition(x, x) + .setRadius(barRadius) + .lineStyle(lineWidth, this.trackColor); + } else { + trackShape.reset(); + } + + // Bar shape + var barShape = this.getShape('bar'); + if ((this.barColor != null) && (barRadius > 0)) { + var anticlockwise, startAngle, endAngle; + if (this.value === 1) { + anticlockwise = false; + startAngle = 0; + endAngle = 361; // overshoot 1 + } else { + anticlockwise = this.anticlockwise; + startAngle = RadToDeg(this.startAngle); + var deltaAngle = 360 * ((anticlockwise) ? (1 - this.value) : this.value); + endAngle = deltaAngle + startAngle; + } + barShape + .setCenterPosition(x, x) + .setRadius(barRadius) + .setAngle(startAngle, endAngle, anticlockwise) + .lineStyle(lineWidth, this.barColor); + } else { + barShape.reset(); + } + + // Center shape + var centerShape = this.getShape('center'); + if (this.centerColor && (centerRadius > 0)) { + centerShape + .setCenterPosition(x, x) + .setRadius(centerRadius) + .fillStyle(this.centerColor); + } else { + centerShape.reset(); + } + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/Creator.js new file mode 100644 index 000000000..1d59d5aae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/Creator.js @@ -0,0 +1,18 @@ +import CustomProgress from './CustomProgress.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', width); + var gameObject = new CustomProgress(this.scene, 0, 0, width, height, config); + + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/CustomProgress.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/CustomProgress.d.ts new file mode 100644 index 000000000..d71a2aa40 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/CustomProgress.d.ts @@ -0,0 +1,72 @@ +import CustomShapes from '../customshapes/CustomShapes'; + +export default CustomProgress; + +declare namespace CustomProgress { + type Arc = CustomShapes.Arc; + type Circle = CustomShapes.Circle; + type Curve = CustomShapes.Curve; + type Ellipse = CustomShapes.Ellipse; + type Line = CustomShapes.Line; + type Lines = CustomShapes.Lines; + type Rectangle = CustomShapes.Rectangle; + type RoundRectangle = CustomShapes.RoundRectangle; + type Triangle = CustomShapes.Triangle; + type ShapeTypes = Arc | Circle | Curve | Ellipse | + Line | Lines | Rectangle | RoundRectangle | Triangle; + + type ValueChangeCallbackType = ( + newValue: number, + oldValue: number, + customProgress: CustomProgress + ) => void; + + interface IConfig extends Omit { + value?: number, + + update?: (this: CustomProgress) => void; + + easeValue?: { + duration?: number, + ease?: string + }, + + valuechangeCallback?: ValueChangeCallbackType, + } + + namespace Events { + type ValueChangeCallbackType = ( + newValue: number, + oldValue: number, + customProgress: CustomProgress + ) => void; + } +} + +declare class CustomProgress extends CustomShapes { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + config?: CustomProgress.IConfig + ); + + constructor( + scene: Phaser.Scene, + config?: CustomProgress.IConfig + ); + + readonly centerX: number; + readonly centerY: number; + readonly radius: number; + + value: number; + getValue(min?: number, max?: number): number; + setValue(value?: number, min?: number, max?: number): this; + addValue(inc?: number, min?: number, max?: number): this; + + easeValueTo(value?: number, min?: number, max?: number): this; + stopEaseValue(): this; + setEaseValueDuration(duration: number): this; + setEaseValueFunction(ease: string): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/CustomProgress.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/CustomProgress.js new file mode 100644 index 000000000..c5c83d205 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/CustomProgress.js @@ -0,0 +1,43 @@ +import CustomShapes from '../customshapes/CustomShapes.js'; +import ProgressBase from '../../../utils/progressbase/ProgressBase.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +class CustomProgress extends ProgressBase(CustomShapes) { + constructor(scene, x, y, width, height, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 2); + height = GetValue(config, 'height', 2); + } + if (config === undefined) { + config = {}; + } + if (!config.type) { + config.type = 'rexCustomProgress'; + } + + super(scene, x, y, width, height, config); + + this.bootProgressBase(config); + + this.setValue(GetValue(config, 'value', 0)); + } + + get centerX() { + return this.width / 2;; + } + + get centerY() { + return this.height / 2; + } + + get radius() { + return Math.min(this.centerX, this.centerY); + } +} + +export default CustomProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/Factory.js new file mode 100644 index 000000000..7dc0de2db --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customprogress/Factory.js @@ -0,0 +1,7 @@ +import CustomProgress from './CustomProgress.js'; + +export default function (x, y, width, height, config) { + var gameObject = new CustomProgress(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/Creator.js new file mode 100644 index 000000000..cc955e42a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/Creator.js @@ -0,0 +1,18 @@ +import CustomShapes from './CustomShapes.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', width); + var gameObject = new CustomShapes(this.scene, 0, 0, width, height, config); + + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/CustomShapes.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/CustomShapes.d.ts new file mode 100644 index 000000000..e1d28b4ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/CustomShapes.d.ts @@ -0,0 +1,70 @@ +import BaseShapes from '../shapes/BaseShapes'; +import * as Geoms from '../shapes/geoms/'; + +export default CustomShapes; + +declare namespace CustomShapes { + + type NameTypes = string | string[] | number; + + type Arc = Geoms.Arc; + type Circle = Geoms.Circle; + type Curve = Geoms.Curve; + type Ellipse = Geoms.Ellipse; + type Line = Geoms.Line; + type Lines = Geoms.Lines; + type Rectangle = Geoms.Rectangle; + type RoundRectangle = Geoms.RoundRectangle; + type Triangle = Geoms.Triangle; + type ShapeTypes = Arc | Circle | Curve | Ellipse | + Line | Lines | Rectangle | RoundRectangle | Triangle; + + type ShapeMapType = { + arc?: NameTypes, + circle?: NameTypes, + curve?: NameTypes, + ellipse?: NameTypes, + line?: NameTypes, + lines?: NameTypes, + rectangle?: NameTypes, + roundRectangle?: NameTypes, + triangle?: NameTypes, + }; + type ShapeArrayType = { + name: string, + type: 'arc' | 'circle' | 'curve' | 'ellipse' | 'line' | 'lines' | 'rectangle' | 'triangle' + }[]; + type CreatrShapeCallbackType = (this: CustomShapes) => void + + interface IConfig { + x?: number, y?: number, + width?: number, height?: number, + + create?: ShapeMapType | ShapeArrayType | CreatrShapeCallbackType; + + update?: (this: CustomShapes) => void; + + type?: string, + } +} + +declare class CustomShapes extends BaseShapes { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + config?: CustomShapes.IConfig + ); + + constructor( + scene: Phaser.Scene, + config?: CustomShapes.IConfig + ); + + setUpdateShapesCallback( + callback: (this: CustomShapes) => void + ): this; + + getShape(name: string): CustomShapes.ShapeTypes; + getShapes(): CustomShapes.ShapeTypes[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/CustomShapes.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/CustomShapes.js new file mode 100644 index 000000000..1ea4253d9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/CustomShapes.js @@ -0,0 +1,46 @@ +import BaseShapes from '../shapes/BaseShapes.js'; +import ShapesUpdateMethods from './ShapesUpdateMethods.js'; +import WorldXYToGameObjectLocalXY from '../../../utils/position/WorldXYToGameObjectLocalXY.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +class CustomShapes extends BaseShapes { + constructor(scene, x, y, width, height, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 2); + height = GetValue(config, 'height', 2); + } + + super(scene, x, y, width, height); + this.type = GetValue(config, 'type', 'rexCustomShapes'); + this.buildShapes(config); + } + + get centerX() { + return this.width / 2; + } + + get centerY() { + return this.height / 2; + } + + worldToLocalXY(worldX, worldY, camera, out) { + if (typeof (camera) === 'boolean') { + out = camera; + camera = undefined; + } + + return WorldXYToGameObjectLocalXY(this, worldX, worldY, camera, out); + } +} + +Object.assign( + CustomShapes.prototype, + ShapesUpdateMethods +); + +export default CustomShapes; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/Factory.js new file mode 100644 index 000000000..e1701baf2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/Factory.js @@ -0,0 +1,7 @@ +import CustomShapes from './CustomShapes.js'; + +export default function (x, y, width, height, config) { + var gameObject = new CustomShapes(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/ShapesUpdateMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/ShapesUpdateMethods.js new file mode 100644 index 000000000..74ee6425d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/customshapes/ShapesUpdateMethods.js @@ -0,0 +1,98 @@ +import { + Arc, + Circle, + Curve, + Ellipse, + Line, + Lines, + Rectangle, + RoundRectangle, + Triangle +} from '../shapes/geoms'; + +const ShapeClasses = { + arc: Arc, + circle: Circle, + curve: Curve, + ellipse: Ellipse, + line: Line, + lines: Lines, + rectangle: Rectangle, + roundRectangle: RoundRectangle, + triangle: Triangle +} + +const GetValue = Phaser.Utils.Objects.GetValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +const ClearAll = function () { + var shapes = this.getShapes(); + for (var i = 0, cnt = shapes.length; i < cnt; i++) { + shapes[i].lineStyle().fillStyle(); + } +}; + +export default { + createShape(shapeType, name) { + var ShapeClass = ShapeClasses[shapeType]; + var shape = new ShapeClass(); + if (name) { + shape.setName(name); + } + return shape; + }, + + buildShapes(config) { + var createCallback = GetValue(config, 'create', undefined); + + if (IsPlainObject(createCallback)) { + var shapes = createCallback; + for (var shapeType in shapes) { + var name = shapes[shapeType]; + switch (typeof (name)) { + case 'number': + for (var i = 0; i < name; i++) { + this.addShape(this.createShape(shapeType)); + } + break; + + case 'string': + this.addShape(this.createShape(shapeType, name)); + break; + + default: //Array + var names = name; + for (var i = 0, cnt = names.length; i < cnt; i++) { + this.addShape(this.createShape(shapeType, names[i])); + } + break; + } + } + } else if (Array.isArray(createCallback)) { + var shapes = createCallback; + for (var i = 0, cnt = shapes.length; i < cnt; i++) { + var shape = shapes[i]; + this.addShape(this.createShape(shape.type, shape.name)); + } + + } else if (typeof (createCallback) === 'function') { + createCallback.call(this); + + } + + this.setUpdateShapesCallback(GetValue(config, 'update')); + }, + + setUpdateShapesCallback(callback) { + if (callback === undefined) { + callback = ClearAll; + } + this.dirty = this.dirty || (this.updateCallback !== callback); + this.updateCallback = callback; + return this; + }, + + updateShapes() { + this.updateCallback.call(this); + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/Creator.js new file mode 100644 index 000000000..bb250421e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/Creator.js @@ -0,0 +1,18 @@ +import FullWindowRectangle from './FullWindowRectangle.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var fillColor = GetAdvancedValue(config, 'color', undefined); + var fillAlpha = GetAdvancedValue(config, 'alpha', undefined); + var gameObject = new FullWindowRectangle(this.scene, fillColor, fillAlpha); + + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/Factory.js new file mode 100644 index 000000000..c52e2cacb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/Factory.js @@ -0,0 +1,7 @@ +import FullWindowRectangle from './FullWindowRectangle.js'; + +export default function (fillColor, fillAlpha) { + var gameObject = new FullWindowRectangle(this.scene, fillColor, fillAlpha); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/FullWindowRectangle.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/FullWindowRectangle.d.ts new file mode 100644 index 000000000..9dd702e98 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/FullWindowRectangle.d.ts @@ -0,0 +1,14 @@ +// import * as Phaser from 'phaser'; + +export default FullWindowRectangle; + +declare class FullWindowRectangle extends Phaser.GameObjects.Rectangle { + constructor( + scene: Phaser.Scene, + fillColor?: number, + fillAlpha?: number + ); + + alpha: number; + tint: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/FullWindowRectangle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/FullWindowRectangle.js new file mode 100644 index 000000000..37c79536a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/fullwindowrectangle/FullWindowRectangle.js @@ -0,0 +1,62 @@ +const Rectangle = Phaser.GameObjects.Rectangle; + +class FullWindowRectangle extends Rectangle { + constructor(scene, color, alpha) { + super(scene, 0, 0, 2, 2, color, 1); + + this.setAlpha(alpha) + this.setScrollFactor(0); + this.boot(); + } + + boot() { + var scene = this.scene; + scene.sys.events.on('prerender', this.resize, this); + } + + destroy(fromScene) { // preDestroy method does not have fromScene parameter + // This Game Object has already been destroyed + if (!this.scene || this.ignoreDestroy) { + return; + } + + this.scene.sys.events.off('prerender', this.resize, this); + + super.destroy(fromScene); + } + + get tint() { + return this.fillColor; + } + + set tint(value) { + this.setFillStyle(value, this.fillAlpha); + } + + resize() { + var scene = this.scene; + var gameSize = scene.sys.scale.gameSize; + var camera = scene.sys.cameras.main; + + var gameWidth = gameSize.width, + gameHeight = gameSize.height, + scale = 1 / camera.zoom; + + var x = gameWidth / 2, + y = gameHeight / 2, + width = gameWidth * scale, + height = gameHeight * scale; + + if ((this.x !== x) || (this.y !== y)) { + this.setPosition(x, y); + } + + if ((this.width !== width) || (this.height !== height)) { + this.setSize(width, height).setOrigin(0.5); + } + + } + +} + +export default FullWindowRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/Creator.js new file mode 100644 index 000000000..c1555fb7c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/Creator.js @@ -0,0 +1,13 @@ +import LineProgress from './LineProgress.js'; + +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var gameObject = new LineProgress(this.scene, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/Factory.js new file mode 100644 index 000000000..084a4206d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/Factory.js @@ -0,0 +1,7 @@ +import LineProgress from './LineProgress.js'; + +export default function (x, y, width, height, barColor, value, config) { + var gameObject = new LineProgress(this.scene, x, y, width, height, barColor, value, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/LineProgress.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/LineProgress.d.ts new file mode 100644 index 000000000..803e9ac5d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/LineProgress.d.ts @@ -0,0 +1,96 @@ +import BaseShapes from '../shapes/BaseShapes'; + +// import * as Phaser from 'phaser'; +export default LineProgress; + +declare namespace LineProgress { + + type ValueChangeCallbackType = ( + newValue: number, + oldValue: number, + circularProgress: LineProgress + ) => void; + + interface IConfig { + x?: number, y?: number, + width?: number, height?: number, + + trackColor?: string | number, + trackThickness?: number, + trackStrokeColor?: string | number, + barColor?: string | number, + + skewX?: number, + + rtl?: boolean, + + value?: number, + + easeValue?: { + duration?: number, + ease?: string + }, + + valuechangeCallback: ValueChangeCallbackType, + } + + namespace Events { + type ValueChangeCallbackType = ( + newValue: number, + oldValue: number, + circularProgress: LineProgress + ) => void; + } +} + +declare class LineProgress extends BaseShapes { + constructor( + scene: Phaser.Scene, + config?: LineProgress.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + config?: LineProgress.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + barColor?: string | number, + value?: number, + config?: LineProgress.IConfig + ); + + value: number; + getValue(min?: number, max?: number): number; + setValue(value?: number, min?: number, max?: number): this; + addValue(inc?: number, min?: number, max?: number): this; + + easeValueTo(value?: number, min?: number, max?: number): this; + stopEaseValue(): this; + setEaseValueDuration(duration: number): this; + setEaseValueFunction(ease: string): this; + + trackColor: string; + setTrackColor(radius?: string | number): this; + + trackStrokeThickness: number; + trackStrokeColor: string; + setTrackStroke( + lineWidth?: number, + color?: string | number + ): this; + + barColor: string; + setBarColor(barColor?: string | number): this; + + skewX: number; + setSkewX(skewX: number): this; + + rtl: boolean; + setRTL(enable?: boolean): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/LineProgress.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/LineProgress.js new file mode 100644 index 000000000..5ad446921 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/LineProgress.js @@ -0,0 +1,147 @@ +import BaseShapes from '../shapes/BaseShapes.js'; +import ProgressBase from '../../../utils/progressbase/ProgressBase.js'; +import { Lines } from '../shapes/geoms'; +import UpdateShapes from './UpdateShapes.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +class LineProgress extends ProgressBase(BaseShapes) { + constructor(scene, x, y, width, height, barColor, value, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 2); + height = GetValue(config, 'height', 2); + barColor = GetValue(config, 'barColor', undefined); + value = GetValue(config, 'value', 0); + } else if (IsPlainObject(width)) { + config = width; + width = GetValue(config, 'width', 2); + height = GetValue(config, 'height', 2); + barColor = GetValue(config, 'barColor', undefined); + value = GetValue(config, 'value', 0); + } else if (IsPlainObject(barColor)) { + config = barColor; + barColor = GetValue(config, 'barColor', undefined); + value = GetValue(config, 'value', 0); + } + + super(scene, x, y, width, height, config); + this.type = 'rexLineProgress'; + + this.bootProgressBase(config); + + this + .addShape((new Lines()).setName('trackFill')) + .addShape((new Lines()).setName('bar')) + .addShape((new Lines()).setName('trackStroke')) + + this.setTrackColor(GetValue(config, 'trackColor', undefined)); + this.setBarColor(barColor); + this.setTrackStroke(GetValue(config, 'trackStrokeThickness', 2), GetValue(config, 'trackStrokeColor', undefined)); + + this.setSkewX(GetValue(config, 'skewX', 0)); + + this.setRTL(GetValue(config, 'rtl', false)); + + this.setValue(value); + } + + get trackColor() { + return this._trackColor; + } + + set trackColor(value) { + this.dirty = this.dirty || (this._trackColor != value); + this._trackColor = value; + } + + setTrackColor(color) { + this.trackColor = color; + return this; + } + + get trackStrokeColor() { + return this._trackStrokeColor; + } + + set trackStrokeColor(value) { + this.dirty = this.dirty || (this._trackStrokeColor != value); + this._trackStrokeColor = value; + } + + get trackStrokeThickness() { + return this._trackStrokeThickness; + } + + set trackStrokeThickness(value) { + this.dirty = this.dirty || (this._trackStrokeThickness != value); + this._trackStrokeThickness = value; + } + + setTrackStroke(lineWidth, color) { + this.trackStrokeThickness = lineWidth; + this.trackStrokeColor = color; + return this; + } + + get barColor() { + return this._barColor; + } + + set barColor(value) { + this.dirty = this.dirty || (this._barColor != value); + this._barColor = value; + } + + setBarColor(color) { + this.barColor = color; + return this; + } + + get skewX() { + return this._skewX; + } + + set skewX(value) { + this.dirty = this.dirty || (this._skewX != value); + this._skewX = value; + } + + setSkewX(value) { + this.skewX = value; + return this; + } + + get rtl() { + return this._rtl; + } + + set rtl(value) { + value = !!value; + this.dirty = this.dirty || (this._rtl != value); + this._rtl = value; + } + + setRTL(enable) { + if (enable === undefined) { + enable = true; + } + this.rtl = enable; + return this; + } + +} + +var Methods = { + updateShapes: UpdateShapes, +} + +Object.assign( + LineProgress.prototype, + Methods, +) + +export default LineProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/UpdateShapes.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/UpdateShapes.js new file mode 100644 index 000000000..d707c5e45 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/lineprogress/UpdateShapes.js @@ -0,0 +1,69 @@ +var UpdateShapes = function () { + var skewX = this.skewX; + var width = this.width - Math.abs(skewX); + var height = this.height; + var trackFill = this.getShape('trackFill'); + trackFill.fillStyle(this.trackColor); + if (trackFill.isFilled) { + BuildRectangle( + trackFill, // lines + 0, 0, // x0, y0 + width, height, // x1, y1 + skewX // skewX + ) + .close() + } + + var bar = this.getShape('bar'); + bar.fillStyle(this.barColor); + if (bar.isFilled) { + var barX0, barX1; + if (!this.rtl) { + barX0 = 0; + barX1 = width * this.value; + } else { + barX0 = width * (1 - this.value); + barX1 = width; + } + + BuildRectangle( + bar, // lines + barX0, 0, // x0, y0 + barX1, height, // x1, y1 + skewX // skew + ) + .close() + } + + var trackStroke = this.getShape('trackStroke'); + trackStroke.lineStyle(this.trackStrokeThickness, this.trackStrokeColor); + if (trackStroke.isStroked) { + BuildRectangle( + trackStroke, // lines + 0, 0, // x0, y0 + width, height, // x1, y1 + skewX // skewX + ) + .end() + } +} + +var BuildRectangle = function (lines, x0, y0, x1, y1, skewX) { + if (skewX >= 0) { + lines + .startAt(x0 + skewX, y0).lineTo(x1 + skewX, y0) + .lineTo(x1, y1) + .lineTo(x0, y1) + .lineTo(x0 + skewX, y0) + } else { + lines + .startAt(x0, y0).lineTo(x1, y0) + .lineTo(x1 - skewX, y1) + .lineTo(x0 - skewX, y1) + .lineTo(x0, y0) + } + + return lines; +} + +export default UpdateShapes; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/Creator.js new file mode 100644 index 000000000..132ca9e27 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/Creator.js @@ -0,0 +1,22 @@ +import RoundRectangle from './RoundRectangle.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const GetValue = Phaser.Utils.Objects.GetValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', width); + var radiusConfig = GetValue(config, 'radius', undefined); + var fillColor = GetAdvancedValue(config, 'fillColor', undefined); + var fillAlpha = GetAdvancedValue(config, 'fillAlpha', undefined); + var gameObject = new RoundRectangle(this.scene, 0, 0, width, height, radiusConfig, fillColor, fillAlpha); + + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/Factory.js new file mode 100644 index 000000000..3b690a9c5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/Factory.js @@ -0,0 +1,7 @@ +import RoundRectangle from './RoundRectangle.js'; + +export default function (x, y, width, height, radiusConfig, fillColor, fillAlpha) { + var gameObject = new RoundRectangle(this.scene, x, y, width, height, radiusConfig, fillColor, fillAlpha); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/RoundRectangle.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/RoundRectangle.d.ts new file mode 100644 index 000000000..ae1ca2dc2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/RoundRectangle.d.ts @@ -0,0 +1,103 @@ +// import * as Phaser from 'phaser'; + +export default RoundRectangle; + +declare namespace RoundRectangle { + + type CornerRadiusType = { + x: number, + y: number, + convex: boolean + }; + + interface IRadiusConfig { + tl?: (number | { x?: number, y?: number }), + tr?: (number | { x?: number, y?: number }), + bl?: (number | { x?: number, y?: number }), + br?: (number | { x?: number, y?: number }), + + x?: number, + y?: number, + } + + interface IConfig { + x?: number, + y?: number, + width?: number, + height?: number, + radius?: number | RoundRectangle.IRadiusConfig | + ({ + radius?: (number | RoundRectangle.IRadiusConfig), + iteration?: number + }), + + color?: number, + alpha?: number, + + strokeColor?: number, + strokeAlpha?: number, + strokeWidth?: number, + + shape?: 0 | 'rectangle' | 1 | 'circle', + } + +} + +declare class RoundRectangle extends Phaser.GameObjects.Shape { + constructor( + scene: Phaser.Scene, + x?: number, + y?: number, + width?: number, + height?: number, + radiusConfig?: number | RoundRectangle.IRadiusConfig | + ({ + radius?: (number | RoundRectangle.IRadiusConfig), + iteration?: number + }), + fillColor?: number, + fillAlpha?: number + ); + + constructor( + scene: Phaser.Scene, + config?: RoundRectangle.IConfig + ) + + resize(width: number, height: number): this; + + setIteration(iteration: number): this; + iteration: number; + + setRadius( + value: number | RoundRectangle.IRadiusConfig + ): this; + radius: number; + + setRadiusTL( + value: number | RoundRectangle.IRadiusConfig + ): this; + radiusTL: number; + + setRadiusTR( + value: number | RoundRectangle.IRadiusConfig + ): this; + radiusTR: number; + + setRadiusBL( + value: number | RoundRectangle.IRadiusConfig + ): this; + radiusBL: number; + + setRadiusBR( + value: number | RoundRectangle.IRadiusConfig + ): this; + radiusBR: number; + + readonly cornerRadius: { + tl: RoundRectangle.CornerRadiusType, + tr: RoundRectangle.CornerRadiusType, + bl: RoundRectangle.CornerRadiusType, + br: RoundRectangle.CornerRadiusType, + }; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/RoundRectangle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/RoundRectangle.js new file mode 100644 index 000000000..634153c24 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/RoundRectangle.js @@ -0,0 +1,407 @@ +import GeomRoundRectangle from '../../../geom/roundrectangle/RoundRectangle.js'; +import LineTo from '../../../geom/pathdata/LineTo.js'; +import ArcTo from '../../../geom/pathdata/ArcTo.js'; +import Render from './render/Render.js'; + +const Shape = Phaser.GameObjects.Shape; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const Earcut = Phaser.Geom.Polygon.Earcut; + +class RoundRectangle extends Shape { + constructor(scene, x, y, width, height, radiusConfig, fillColor, fillAlpha) { + var strokeColor, strokeAlpha, strokeWidth, shapeType; + if (IsPlainObject(x)) { + var config = x; + + x = config.x; + y = config.y; + width = config.width; + height = config.height; + radiusConfig = config.radius; + fillColor = config.color; + fillAlpha = config.alpha; + + strokeColor = config.strokeColor; + strokeAlpha = config.strokeAlpha; + strokeWidth = config.strokeWidth; + + shapeType = config.shape; + } + + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (width === undefined) { width = 1; } + if (height === undefined) { height = width; } + if (radiusConfig === undefined) { radiusConfig = 0; } + if (shapeType === undefined) { shapeType = 0; } + + var geom = new GeomRoundRectangle(); // Configurate it later + super(scene, 'rexRoundRectangleShape', geom); + + this.setShapeType(shapeType); + + if (this.shapeType === 0) { + var radius = GetValue(radiusConfig, 'radius', radiusConfig); + geom.setTo(0, 0, width, height, radius); + } else { + var radius = { x: (width / 2), y: (height / 2) }; + geom.setTo(0, 0, width, height, radius); + } + + var iteration = GetValue(radiusConfig, 'iteration', undefined); + this.setIteration(iteration); + this.setPosition(x, y); + + this.setFillStyle(fillColor, fillAlpha); + + if ((strokeColor !== undefined) && (strokeWidth === undefined)) { + strokeWidth = 2; + } + this.setStrokeStyle(strokeWidth, strokeColor, strokeAlpha); + + this.updateDisplayOrigin(); + this.dirty = true; + } + + get fillColor() { + return this._fillColor; + } + + set fillColor(value) { + this._fillColor = value; + this.isFilled = (value != null) && (this._fillAlpha > 0); + } + + get fillAlpha() { + return this._fillAlpha; + } + + set fillAlpha(value) { + this._fillAlpha = value; + this.isFilled = (value > 0) && (this._fillColor != null); + } + + // Fully override setFillStyle method + setFillStyle(color, alpha) { + if (alpha === undefined) { + alpha = 1; + } + + this.fillColor = color; + this.fillAlpha = alpha; + + return this; + } + + get strokeColor() { + return this._strokeColor; + } + + set strokeColor(value) { + this._strokeColor = value; + this.isStroked = (value != null) && (this._lineWidth > 0); + } + + get lineWidth() { + return this._lineWidth; + } + + set lineWidth(value) { + this._lineWidth = value; + this.isStroked = (value > 0) && (this._strokeColor != null); + } + + // Fully override setStrokeStyle method + setStrokeStyle(lineWidth, color, alpha) { + if (alpha === undefined) { + alpha = 1; + } + + this.lineWidth = lineWidth; + this.strokeColor = color; + this.strokeAlpha = alpha; + + return this; + } + + updateData() { + var geom = this.geom; + var pathData = this.pathData; + + pathData.length = 0; + + var width = geom.width, height = geom.height, + cornerRadius = geom.cornerRadius, + radius, + iteration = this.iteration + 1; + + // Top-left + radius = cornerRadius.tl; + if (IsArcCorner(radius)) { + if (radius.convex) { + var centerX = radius.x; + var centerY = radius.y; + ArcTo(centerX, centerY, radius.x, radius.y, 180, 270, false, iteration, pathData); + } else { + var centerX = 0; + var centerY = 0; + ArcTo(centerX, centerY, radius.x, radius.y, 90, 0, true, iteration, pathData); + } + } else { + LineTo(0, 0, pathData); + } + + // Top-right + radius = cornerRadius.tr; + if (IsArcCorner(radius)) { + if (radius.convex) { + var centerX = width - radius.x; + var centerY = radius.y; + ArcTo(centerX, centerY, radius.x, radius.y, 270, 360, false, iteration, pathData); + } else { + var centerX = width; + var centerY = 0; + ArcTo(centerX, centerY, radius.x, radius.y, 180, 90, true, iteration, pathData); + } + } else { + LineTo(width, 0, pathData); + } + + // Bottom-right + radius = cornerRadius.br; + if (IsArcCorner(radius)) { + if (radius.convex) { + var centerX = width - radius.x; + var centerY = height - radius.y; + ArcTo(centerX, centerY, radius.x, radius.y, 0, 90, false, iteration, pathData); + } else { + var centerX = width; + var centerY = height; + ArcTo(centerX, centerY, radius.x, radius.y, 270, 180, true, iteration, pathData); + } + } else { + LineTo(width, height, pathData); + } + + // Bottom-left + radius = cornerRadius.bl; + if (IsArcCorner(radius)) { + if (radius.convex) { + var centerX = radius.x; + var centerY = height - radius.y; + ArcTo(centerX, centerY, radius.x, radius.y, 90, 180, false, iteration, pathData); + } else { + var centerX = 0; + var centerY = height; + ArcTo(centerX, centerY, radius.x, radius.y, 360, 270, true, iteration, pathData); + } + } else { + LineTo(0, height, pathData); + } + + pathData.push(pathData[0], pathData[1]); // Repeat first point to close curve + this.pathIndexes = Earcut(pathData); + return this; + } + + setShapeType(shapeType) { + if (typeof (shapeType) === 'string') { + shapeType = ShapeTypeMap[shapeType]; + } + + this.shapeType = shapeType; + return this; + } + + get width() { + return this.geom.width; + } + set width(value) { + this.resize(value, this.height); + } + + get height() { + return this.geom.height; + } + set height(value) { + this.resize(this.width, value); + } + + setSize(width, height) { + // Override Shape's setSize method + if (height === undefined) { + height = width; + } + if ((this.geom.width === width) && (this.geom.height === height)) { + return this; + } + this.geom.setSize(width, height); + + if (this.shapeType === 1) { + this.setRadius({ + x: (width / 2), + y: (height / 2) + }) + } + + this.updateDisplayOrigin(); + this.dirty = true; + + var input = this.input; + if (input && !input.customHitArea) { + input.hitArea.width = width; + input.hitArea.height = height; + } + return this; + } + + resize(width, height) { + this.setSize(width, height); + return this; + } + + get radius() { + return this.geom.radius; + } + + set radius(value) { + this.geom.setRadius(value); + this.updateDisplayOrigin(); + this.dirty = true; + } + + get radiusTL() { + return this.geom.radiusTL; + } + + set radiusTL(value) { + this.geom.radiusTL = value; + this.dirty = true; + } + + get radiusTR() { + return this.geom.radiusTR; + } + + set radiusTR(value) { + this.geom.radiusTR = value; + this.dirty = true; + } + + get radiusBL() { + return this.geom.radiusBL; + } + + set radiusBL(value) { + this.geom.radiusBL = value; + this.dirty = true; + } + + get radiusBR() { + return this.geom.radiusBR; + } + + set radiusBR(value) { + this.geom.radiusBR = value; + this.dirty = true; + } + + setRadius(value) { + if (value === undefined) { + value = 0; + } + this.radius = value; + return this; + } + + setRadiusTL(value) { + if (value === undefined) { + value = 0; + } + this.radiusTL = value; + return this; + } + + setRadiusTR(value) { + if (value === undefined) { + value = 0; + } + this.radiusTR = value; + return this; + } + + setRadiusBL(value) { + if (value === undefined) { + value = 0; + } + this.radiusBL = value; + return this; + } + + setRadiusBR(value) { + if (value === undefined) { + value = 0; + } + this.radiusBR = value; + return this; + } + + get cornerRadius() { + return this.geom.cornerRadius; + } + + set cornerRadius(value) { + this.radius = value; + } + + setCornerRadius(value) { + return this.setRadius(value); + } + + get iteration() { + return this._iteration; + } + + set iteration(value) { + // Set iteration first time + if (this._iteration === undefined) { + this._iteration = value; + return; + } + + // Change iteration value + if (this._iteration === value) { + return; + } + + this._iteration = value; + this.dirty = true; + } + + setIteration(iteration) { + if (iteration === undefined) { + iteration = 6; + } + this.iteration = iteration; + return this; + } + +} + +var IsArcCorner = function (radius) { + return ((radius.x > 0) && (radius.y > 0)); +} + +const ShapeTypeMap = { + rectangle: 0, + circle: 1 +} + + +Object.assign( + RoundRectangle.prototype, + Render +); + +export default RoundRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/render/CanvasRenderer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/render/CanvasRenderer.js new file mode 100644 index 000000000..43d6269a8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/render/CanvasRenderer.js @@ -0,0 +1,60 @@ +import FillStyleCanvas from '../../utils/render/FillStyleCanvas'; +import LineStyleCanvas from '../../utils/render/LineStyleCanvas'; + +const SetTransform = Phaser.Renderer.Canvas.SetTransform; + +var PolygonCanvasRenderer = function (renderer, src, camera, parentMatrix) { + if (src.dirty) { + src.updateData(); + src.dirty = false; + } + + camera.addToRenderList(src); + + var ctx = renderer.currentContext; + + if (SetTransform(renderer, ctx, src, camera, parentMatrix)) { + var dx = src._displayOriginX; + var dy = src._displayOriginY; + + var path = src.pathData; + var pathLength = path.length - 1; + + var px1 = path[0] - dx; + var py1 = path[1] - dy; + + ctx.beginPath(); + + ctx.moveTo(px1, py1); + + if (!src.closePath) { + pathLength -= 2; + } + + for (var i = 2; i < pathLength; i += 2) { + var px2 = path[i] - dx; + var py2 = path[i + 1] - dy; + + ctx.lineTo(px2, py2); + } + + ctx.closePath(); + + if (src.isFilled) { + FillStyleCanvas(ctx, src); + + ctx.fill(); + } + + if (src.isStroked) { + LineStyleCanvas(ctx, src); + + ctx.stroke(); + } + + // Restore the context saved in SetTransform + ctx.restore(); + } +}; + +export default PolygonCanvasRenderer; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/render/Render.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/render/Render.js new file mode 100644 index 000000000..404fc7750 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/render/Render.js @@ -0,0 +1,8 @@ +import WebGLRenderer from './WebGLRenderer.js'; +import CanvasRenderer from './CanvasRenderer.js'; + +export default { + renderWebGL: WebGLRenderer, + renderCanvas: CanvasRenderer + +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/render/WebGLRenderer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/render/WebGLRenderer.js new file mode 100644 index 000000000..03285aa71 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/roundrectangle/render/WebGLRenderer.js @@ -0,0 +1,38 @@ +import FillPathWebGL from '../../utils/render/FillPathWebGL.js'; +import StrokePathWebGL from '../../utils/render/StrokePathWebGL.js'; + +const GetCalcMatrix = Phaser.GameObjects.GetCalcMatrix; + +var PolygonWebGLRenderer = function (renderer, src, camera, parentMatrix) { + if (src.dirty) { + src.updateData(); + src.dirty = false; + } + + camera.addToRenderList(src); + + var pipeline = renderer.pipelines.set(src.pipeline); + + var result = GetCalcMatrix(src, camera, parentMatrix); + + var calcMatrix = pipeline.calcMatrix.copyFrom(result.calc); + + var dx = src._displayOriginX; + var dy = src._displayOriginY; + + var alpha = camera.alpha * src.alpha; + + renderer.pipelines.preBatch(src); + + if (src.isFilled) { + FillPathWebGL(pipeline, calcMatrix, src, alpha, dx, dy); + } + + if (src.isStroked) { + StrokePathWebGL(pipeline, src, alpha, dx, dy); + } + + renderer.pipelines.postBatch(src); +}; + +export default PolygonWebGLRenderer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/BaseShapes.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/BaseShapes.d.ts new file mode 100644 index 000000000..5ef89aeed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/BaseShapes.d.ts @@ -0,0 +1,35 @@ +// import * as Phaser from 'phaser'; +import BaseGeom from './geoms/base/BaseGeom'; + +export default class BaseShapes extends Phaser.GameObjects.Shape { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number + ); + + setSize(width: number, height: number): this; + resize(width: number, height: number): this; + width: number; + height: number; + isSizeChanged: boolean; + + setFillStyle(color: number, alpha: number): this; + fillColor: number; + fillAlpha: number; + setStrokeStyle(lineWidth: number, color: number, alpha: number): this; + lineWidth: number; + strokeColor: number; + strokeAlpha: number; + + setDirty(dirty?: boolean): this; + dirty: boolean; + + updateShapes(): this; + + getShape(name: string): BaseGeom; + getShapes(): BaseGeom[]; + addShape(shape: BaseGeom): this; + deleteShape(name: string): this; + clear(): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/BaseShapes.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/BaseShapes.js new file mode 100644 index 000000000..2e64e02a1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/BaseShapes.js @@ -0,0 +1,215 @@ +import Render from './render/Render.js'; +import Clear from '../../../utils/object/Clear.js'; + +const Shape = Phaser.GameObjects.Shape; +const RemoveItem = Phaser.Utils.Array.Remove; + +class BaseShapes extends Shape { + constructor(scene, x, y, width, height) { + if (x === undefined) { + x = 0; + } + if (y === undefined) { + y = 0; + } + if (width === undefined) { + width = 2; + } + if (height === undefined) { + height = width; + } + + super(scene, 'rexShapes', []); + + this._width = -1; + this._height = -1; + this.dirty = true; + this.isSizeChanged = true; + this.shapes = {}; + + this.setPosition(x, y); + this.setSize(width, height); + + this.updateDisplayOrigin(); + } + + get width() { + return this._width; + } + + set width(value) { + this.setSize(value, this._height); + } + + get height() { + return this._height; + } + + set height(value) { + this.setSize(this._width, value); + } + + setDirty(value) { + if (value === undefined) { + value = true; + } + this.dirty = value; + return this; + } + + setSize(width, height) { + this.isSizeChanged = this.isSizeChanged || (this._width !== width) || (this._height !== height); + this.dirty = this.dirty || this.isSizeChanged; + this._width = width; + this._height = height; + this.updateDisplayOrigin(); + var input = this.input; + if (input && !input.customHitArea) { + input.hitArea.width = width; + input.hitArea.height = height; + } + return this; + } + + resize(width, height) { + this.setSize(width, height); + return this; + } + + get fillColor() { + return this._fillColor; + } + + set fillColor(value) { + this.setFillStyle(value, this._fillAlpha); + } + + get fillAlpha() { + return this._fillAlpha; + } + + set fillAlpha(value) { + this.setFillStyle(this._fillColor, value); + } + + setFillStyle(color, alpha) { + if (alpha === undefined) { + alpha = 1; + } + + this.dirty = this.dirty || + (this.fillColor !== color) || + (this.fillAlpha !== alpha); + + this._fillColor = color; + this._fillAlpha = alpha; + + return this; + } + + get lineWidth() { + return this._lineWidth; + } + + set lineWidth(value) { + this.setStrokeStyle(value, this._strokeColor, this._strokeAlpha); + } + + get strokeColor() { + return this._strokeColor; + } + + set strokeColor(value) { + this.setStrokeStyle(this._lineWidth, value, this._strokeAlpha); + } + + get strokeAlpha() { + return this._strokeAlpha; + } + + set strokeAlpha(value) { + this.setStrokeStyle(this._lineWidth, this._strokeColor, value); + } + + setStrokeStyle(lineWidth, color, alpha) { + if (alpha === undefined) { + alpha = 1; + } + + this.dirty = this.dirty || + (this.lineWidth !== lineWidth) || + (this.strokeColor !== color) || + (this.strokeAlpha !== alpha); + + this._lineWidth = lineWidth; + this._strokeColor = color; + this._strokeAlpha = alpha; + + return this; + } + + updateShapes() { + + } + + updateData() { + if (!this.dirty) { + return this; + } + + this.updateShapes(); + var shapes = this.geom; + for (var i = 0, cnt = shapes.length; i < cnt; i++) { + var shape = shapes[i]; + if (shape.dirty) { + shape.updateData(); + } + } + + this.isSizeChanged = false; + this.dirty = false; + + + return this; + } + + clear() { + this.geom.length = 0; + Clear(this.shapes); + return this; + } + + getShape(name) { + return this.shapes[name]; + } + + getShapes() { + return this.geom; + } + + addShape(shape) { + this.geom.push(shape); + var name = shape.name; + if (name) { + this.shapes[name] = shape; + } + this.dirty = true; + return this; + } + + deleteShape(name) { + var shape = this.getShape(name); + if (shape) { + delete this.shapes[name]; + RemoveItem(this.geom, shape); + } + return this; + } +} + +Object.assign( + BaseShapes.prototype, + Render +); + +export default BaseShapes; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/base/BaseGeom.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/base/BaseGeom.d.ts new file mode 100644 index 000000000..86cea2c99 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/base/BaseGeom.d.ts @@ -0,0 +1,26 @@ +import DataMethods from '../../../../../utils/data/DataMethods.js'; + +export default class BaseGeom extends DataMethods { + name: string; + dirty: boolean; + data: { [name: string]: any } | undefined; + + isFilled: boolean; + fillColor: number; + fillAlpha: number; + + isStroked: boolean; + lineWidth: number; + strokeColor: number; + strokeAlpha: number; + + fillStyle( + color?: number, + alpha?: number + ): this; + lineStyle( + lineWidth?: number, + color?: number, + alpha?: number + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/base/BaseGeom.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/base/BaseGeom.js new file mode 100644 index 000000000..3f1c5b6fa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/base/BaseGeom.js @@ -0,0 +1,53 @@ +import StyleMethods from './StyleMethods.js'; +import DataMethods from '../../../../../utils/data/DataMethods.js'; + +class BaseGeom { + constructor() { + this.name = undefined; + this.dirty = true; + this.data = undefined; + + this.isFilled = false; + this.fillColor = undefined; + this.fillAlpha = 1; + + this.isStroked = false; + this.lineWidth = 1; + this.strokeColor = undefined; + this.strokeAlpha = 1; + } + + setName(name) { + this.name = name; + return this; + } + + reset() { + this + .fillStyle() + .lineStyle(); + + return this; + } + + webglRender(pipeline, calcMatrix, alpha, dx, dy) { + + } + + canvasRender(ctx, dx, dy) { + + } + + updateData() { + this.dirty = false; + } +} + +Object.assign( + BaseGeom.prototype, + StyleMethods, + DataMethods +); + + +export default BaseGeom; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/base/StyleMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/base/StyleMethods.js new file mode 100644 index 000000000..36eecb1ef --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/base/StyleMethods.js @@ -0,0 +1,33 @@ +var FillStyle = function (color, alpha) { + if (color == null) { + this.isFilled = false; + } else { + if (alpha === undefined) { + alpha = 1; + } + this.isFilled = true; + this.fillColor = color; + this.fillAlpha = alpha; + } + return this; +} + +var LineStyle = function (lineWidth, color, alpha) { + if ((lineWidth == null) || (color == null)) { + this.isStroked = false; + } else { + if (alpha === undefined) { + alpha = 1; + } + this.isStroked = true; + this.lineWidth = lineWidth; + this.strokeColor = color; + this.strokeAlpha = alpha; + } + return this; +} + +export default { + fillStyle: FillStyle, + lineStyle: LineStyle +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/index.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/index.d.ts new file mode 100644 index 000000000..9e3e8c257 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/index.d.ts @@ -0,0 +1,21 @@ +import Arc from './lines/arc/Arc'; +import Circle from './lines/arc/Circle'; +import Curve from './lines/Curve'; +import Ellipse from './lines/arc/Ellipse'; +import Line from './lines/Line'; +import Lines from './lines/Lines'; +import Rectangle from './rectangle/Rectangle'; +import RoundRectangle from './lines/roundrectangle/RoundRectangle'; +import Triangle from './triangle/Triangle'; + +export { + Arc, + Circle, + Curve, + Ellipse, + Line, + Lines, + Rectangle, + RoundRectangle, + Triangle +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/index.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/index.js new file mode 100644 index 000000000..bc0cc55a8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/index.js @@ -0,0 +1,21 @@ +import Arc from './lines/arc/Arc.js'; +import Circle from './lines/arc/Circle.js'; +import Curve from './lines/Curve.js'; +import Ellipse from './lines/arc/Ellipse.js'; +import Line from './lines/Line.js'; +import Lines from './lines/Lines.js'; +import Rectangle from './rectangle/Rectangle.js'; +import RoundRectangle from './lines/roundrectangle/RoundRectangle.js'; +import Triangle from './triangle/Triangle.js'; + +export { + Arc, + Circle, + Curve, + Ellipse, + Line, + Lines, + Rectangle, + RoundRectangle, + Triangle +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Curve.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Curve.d.ts new file mode 100644 index 000000000..10a477f9e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Curve.d.ts @@ -0,0 +1,15 @@ +// import * as Phaser from 'phaser'; +import PathBase from "./PathBase"; + +export default class Curve extends PathBase { + constructor( + curve: Phaser.Curves.Curve + ); + + setCurve(curve: Phaser.Curves.Curve): this; + curve: Phaser.Curves.Curve; + + setIterations(iterations: number): this; + iterations: number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Curve.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Curve.js new file mode 100644 index 000000000..0688f8715 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Curve.js @@ -0,0 +1,52 @@ +import PathBase from './PathBase.js'; + +class Curve extends PathBase { + constructor(curve) { + super(); + this.setCurve(curve); + this.setIterations(32); + } + + get curve() { + return this._curve; + } + + set curve(value) { + this.dirty = this.dirty || (this._curve !== value); + this._curve = value; + } + + setCurve(curve) { + this.curve = curve; + return this; + } + + get iterations() { + return this._iterations; + } + + set iterations(value) { + this.dirty = this.dirty || (this._iterations !== value); + this._iterations = value; + } + + setIterations(iterations) { + this.iterations = iterations; + return this; + } + + updateData() { + this.pathData.length = 0; + var points = this.curve.getPoints(this.iterations); + for (var i = 0, cnt = points.length; i < cnt; i++) { + this.pathData.push(points[i].x, points[i].y); + } + this.pathData.push(points[0].x, points[0].y); + + super.updateData(); + return this; + } + +} + +export default Curve; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Line.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Line.d.ts new file mode 100644 index 000000000..fad16c6db --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Line.d.ts @@ -0,0 +1,16 @@ +import PathBase from "./PathBase"; + +export default class Line extends PathBase { + constructor( + x0?: number, y0?: number, + x1?: number, y1?: number + ); + + setP0(x: number, y: number): this; + x0: number; + y0: number; + + setP1(x: number, y: number): this; + x1: number; + y1: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Line.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Line.js new file mode 100644 index 000000000..16cf235d3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Line.js @@ -0,0 +1,75 @@ +import PathBase from './PathBase.js'; + +class Line extends PathBase { + constructor(x0, y0, x1, y1) { + if (x0 === undefined) { x0 = 0; } + if (y0 === undefined) { y0 = 0; } + if (x1 === undefined) { x1 = 0; } + if (y1 === undefined) { y1 = 0; } + + super(); + + this.setP0(x0, y0); + this.setP1(x1, y1); + } + + get x0() { + return this._x0; + } + + set x0(value) { + this.dirty = this.dirty || (this._x0 !== value); + this._x0 = value; + } + + get y0() { + return this._y0; + } + + set y0(value) { + this.dirty = this.dirty || (this._y0 !== value); + this._y0 = value; + } + + setP0(x, y) { + this.x0 = x; + this.y0 = y; + return this; + } + + get x1() { + return this._x1; + } + + set x1(value) { + this.dirty = this.dirty || (this._x1 !== value); + this._x1 = value; + } + + get y1() { + return this._y1; + } + + set y1(value) { + this.dirty = this.dirty || (this._y1 !== value); + this._y1 = value; + } + + setP1(x, y) { + this.x1 = x; + this.y1 = y; + return this; + } + + updateData() { + this.pathData.length = 0; + this.pathData.push(this.x0, this.y0); + this.pathData.push(this.x1, this.y1); + this.pathData.push(this.x0, this.y0); + + super.updateData(); + return this; + } +} + +export default Line; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Lines.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Lines.d.ts new file mode 100644 index 000000000..fd6797fae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Lines.d.ts @@ -0,0 +1,78 @@ +import PathBase from "./PathBase"; + +export default class Lines extends PathBase { + setIterations(iterations: number): this; + iterations: number; + + firstPointX: number; + firstPointY: number; + lastPointX: number; + lastPointY: number; + + startAt(x: number, y: number): this; + + lineTo(x: number, y: number, relative?: boolean): this; + verticalLineTo(x: number, relative?: boolean): this; + horizontalLineTo(y: number, relative?: boolean): this; + + ellipticalArc( + centerX: number, centerY: number, + radiusX: number, radiusY: number, + startAngle: number, endAngle: number, + anticlockwise?: boolean + ): this; + arc( + centerX: number, centerY: number, + radius: number, + startAngle: number, endAngle: number, + anticlockwise?: boolean + ): this; + + quadraticBezierTo( + cx: number, cy: number, + x: number, y: number + ): this; + smoothQuadraticBezierTo( + x: number, y: number + ): this; + + cubicBezierCurveTo( + cx0: number, cy0: number, + cx1: number, cy1: number, + x: number, y: number + ): this; + smoothCubicBezierCurveTo( + cx1: number, cy1: number, + x: number, y: number + ): this; + + close(): this; + + end(): this; + + rotateAround( + centerX: number, centerY: number, + angle: number + ): this; + + scale( + centerX: number, centerY: number, + scaleX: number, scaleY: number + ): this; + + offset(x: number, y: number): this; + + savePathData(): this; + restorePathData(): this; + + appendPathFrom(src: Lines): this; + appendPathFrom(src: Lines, endT: number): this; + appendPathFrom(src: Lines, startT: number, endT: number): this; + + copyPathFrom(src: Lines): this; + copyPathFrom(src: Lines, endT: number): this; + copyPathFrom(src: Lines, startT: number, endT: number): this; + + setDisplayPathSegment(endT: number): this; + setDisplayPathSegment(startT: number, endT: number): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Lines.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Lines.js new file mode 100644 index 000000000..f4e9c579b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/Lines.js @@ -0,0 +1,164 @@ +import PathBase from './PathBase.js'; +import PathDataBuilder from '../../../../../geom/pathdata/PathDataBuilder/PathDataBuilder.js'; + +class Lines extends PathBase { + constructor() { + super(); + this.builder = new PathDataBuilder(this.pathData); + } + + get iterations() { + return this.builder.iterations; + } + + set iterations(value) { + this.dirty = this.dirty || (this.builder.iterations !== value); + this.builder.setIterations(value); + } + + setIterations(iterations) { + this.iterations = iterations; + return this; + } + + get lastPointX() { + return this.builder.lastPointX; + } + + get lastPointY() { + return this.builder.lastPointY; + } + + start() { + this.builder.start(); + + this.dirty = true; + return this; + } + + startAt(x, y) { + this.builder.startAt(x, y); + + this.dirty = true; + return this; + } + + lineTo(x, y, relative) { + this.builder.lineTo(x, y, relative); + + this.dirty = true; + return this; + } + + verticalLineTo(x, relative) { + this.builder.verticalLineTo(x, relative); + + this.dirty = true; + return this; + } + + horizontalLineTo(y, relative) { + this.builder.horizontalLineTo(y, relative); + + this.dirty = true; + return this; + } + + ellipticalArc(centerX, centerY, radiusX, radiusY, startAngle, endAngle, anticlockwise) { + this.builder.ellipticalArc(centerX, centerY, radiusX, radiusY, startAngle, endAngle, anticlockwise); + + this.dirty = true; + return this; + } + + arc(centerX, centerY, radius, startAngle, endAngle, anticlockwise) { + this.builder.arc(centerX, centerY, radius, startAngle, endAngle, anticlockwise); + + this.dirty = true; + return this; + } + + quadraticBezierTo(cx, cy, x, y) { + this.builder.quadraticBezierTo(cx, cy, x, y); + + this.dirty = true; + return this; + } + + smoothQuadraticBezierTo(x, y) { + this.builder.smoothQuadraticBezierTo(x, y); + + this.dirty = true; + return this; + } + + cubicBezierCurveTo(cx0, cy0, cx1, cy1, x, y) { + this.builder.cubicBezierCurveTo(cx0, cy0, cx1, cy1, x, y); + + this.dirty = true; + return this; + } + + smoothCubicBezierCurveTo(cx1, cy1, x, y) { + this.builder.smoothCubicBezierCurveTo(cx1, cy1, x, y); + + this.dirty = true; + return this; + } + + close() { + this.builder.close(); + + this.closePath = this.builder.closePath; + this.dirty = true; + return this; + } + + end() { + this.builder.end(); + this.dirty = true; + return this; + } + + rotateAround(centerX, centerY, angle) { + this.builder.rotateAround(centerX, centerY, angle); + + this.dirty = true; + return this; + } + + scale(centerX, centerY, scaleX, scaleY) { + this.builder.scale(centerX, centerY, scaleX, scaleY); + + this.dirty = true; + return this; + } + + offset(x, y) { + this.builder.offset(x, y); + + this.dirty = true; + return this; + } + + toPolygon(polygon) { + return this.builder.toPolygon(polygon); + } + + appendPathFrom(src, startT, endT) { + this.builder.appendFromPathSegment(src.builder, startT, endT); + return this; + } + + copyPathFrom(src, startT, endT) { + this.builder.clear().appendFromPathSegment(src.builder, startT, endT); + return this; + } + + setDisplayPathSegment(startT, endT) { + this.builder.setDisplayPathSegment(startT, endT); + return this; + } +} + +export default Lines; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/PathBase.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/PathBase.d.ts new file mode 100644 index 000000000..3fb08d79a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/PathBase.d.ts @@ -0,0 +1,5 @@ +import BaseGeom from '../base/BaseGeom'; + +export default class PathBase extends BaseGeom { + closePath: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/PathBase.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/PathBase.js new file mode 100644 index 000000000..ca5f4ba1d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/PathBase.js @@ -0,0 +1,73 @@ +import BaseGeom from '../base/BaseGeom.js'; +import FillPathWebGL from '../../../utils/render/FillPathWebGL.js'; +import StrokePathWebGL from '../../../utils/render/StrokePathWebGL.js'; +import FillStyleCanvas from '../../../utils/render/FillStyleCanvas.js'; +import LineStyleCanvas from '../../../utils/render/LineStyleCanvas.js'; + +const Earcut = Phaser.Geom.Polygon.Earcut; + +class PathBase extends BaseGeom { + constructor() { + super(); + + this.pathData = []; + this.pathIndexes = []; + this.closePath = false; + } + + updateData() { + this.pathIndexes = Earcut(this.pathData); + + super.updateData(); + return this; + } + + webglRender(pipeline, calcMatrix, alpha, dx, dy) { + if (this.isFilled) { + FillPathWebGL(pipeline, calcMatrix, this, alpha, dx, dy); + } + + if (this.isStroked) { + StrokePathWebGL(pipeline, this, alpha, dx, dy); + } + } + + canvasRender(ctx, dx, dy) { + var path = this.pathData; + var pathLength = path.length - 1; + + var px1 = path[0] - dx; + var py1 = path[1] - dy; + + ctx.beginPath(); + + ctx.moveTo(px1, py1); + + if (!this.closePath) { + pathLength -= 2; + } + + for (var i = 2; i < pathLength; i += 2) { + var px2 = path[i] - dx; + var py2 = path[i + 1] - dy; + ctx.lineTo(px2, py2); + } + + if (this.closePath) { + ctx.closePath(); + } + + + if (this.isFilled) { + FillStyleCanvas(ctx, this); + ctx.fill(); + } + + if (this.isStroked) { + LineStyleCanvas(ctx, this); + ctx.stroke(); + } + } +} + +export default PathBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Arc.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Arc.d.ts new file mode 100644 index 000000000..3a4860c42 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Arc.d.ts @@ -0,0 +1,32 @@ +import PathBase from "../PathBase"; + +export default class Arc extends PathBase { + constructor( + x?: number, y?: number, + radiusX?: number, radiusY?: number, + startAngle?: number, endAngle?: number, + anticlockwise?: boolean, pie?: boolean + ); + + setCenterPosition(x: number, y: number): this; + x: number; + y: number; + + setRadius(radiusX: number, radiusY?: number): this; + radiusX: number; + radiusY: number; + + setAngle( + startAngle: number, endAngle: number, + anticlockwise?: boolean + ): this; + startAngle: number; + endAngle: number; + anticlockwise: boolean; + + setPie(pie: boolean): this; + pie: boolean; + + setIterations(iterations:number):this; + iterations:number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Arc.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Arc.js new file mode 100644 index 000000000..5845b6f65 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Arc.js @@ -0,0 +1,205 @@ +import PathBase from '../PathBase.js'; +import ArcTo from '../../../../../../geom/pathdata/ArcTo.js'; +import FillStyleCanvas from '../../../../utils/render/FillStyleCanvas.js'; +import LineStyleCanvas from '../../../../utils/render/LineStyleCanvas.js'; +const DegToRad = Phaser.Math.DegToRad; + +class Arc extends PathBase { + constructor(x, y, radiusX, radiusY, startAngle, endAngle, anticlockwise, pie) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (radiusX === undefined) { radiusX = 0; } + if (radiusY === undefined) { radiusY = 0; } + if (startAngle === undefined) { startAngle = 0; } + if (endAngle === undefined) { endAngle = 360; } + if (anticlockwise === undefined) { anticlockwise = false; } + if (pie === undefined) { pie = false; } + + super(); + + this.setCenterPosition(x, y); + this.setRadius(radiusX, radiusY); + this.setAngle(startAngle, endAngle, anticlockwise); + this.setPie(pie); + this.setIterations(32); + } + + get x() { + return this._x; + } + + set x(value) { + this.dirty = this.dirty || (this._x !== value); + this._x = value; + } + + get y() { + return this._y; + } + + set y(value) { + this.dirty = this.dirty || (this._y !== value); + this._y = value; + } + + setCenterPosition(x, y) { + if (y === undefined) { + y = x; + } + this.x = x; + this.y = y; + return this; + } + + get radiusX() { + return this._radiusX; + } + + set radiusX(value) { + this.dirty = this.dirty || (this._radiusX !== value); + this._radiusX = value; + } + + get radiusY() { + return this._radiusY; + } + + set radiusY(value) { + this.dirty = this.dirty || (this._radiusY !== value); + this._radiusY = value; + } + + setRadius(radiusX, radiusY) { + if (radiusY === undefined) { + radiusY = radiusX; + } + this.radiusX = radiusX; + this.radiusY = radiusY; + return this; + } + + get startAngle() { + return this._startAngle; + } + + set startAngle(value) { + this.dirty = this.dirty || (this._startAngle !== value); + this._startAngle = value; + } + + get endAngle() { + return this._endAngle; + } + + set endAngle(value) { + this.dirty = this.dirty || (this._endAngle !== value); + this._endAngle = value; + } + + get anticlockwise() { + return this._anticlockwise; + } + + set anticlockwise(value) { + this.dirty = this.dirty || (this._anticlockwise !== value); + this._anticlockwise = value; + } + + setAngle(startAngle, endAngle, anticlockwise) { + // startAngle, endAngle in degrees + if (anticlockwise === undefined) { + anticlockwise = false; + } + + this.startAngle = startAngle; + this.endAngle = endAngle; + this.anticlockwise = anticlockwise; + return this; + } + + get pie() { + return this._pie; + } + + set pie(value) { + this.dirty = this.dirty || (this._pie !== value); + this._pie = value; + } + + setPie(pie) { + if (pie === undefined) { + pie = true; + } + this.pie = pie; + return this; + } + + get iterations() { + return this._iterations; + } + + set iterations(value) { + this.dirty = this.dirty || (this._iterations !== value); + this._iterations = value; + } + + setIterations(iterations) { + this.iterations = iterations; + return this; + } + + updateData() { + this.pathData.length = 0; + if (this.pie) { + this.pathData.push(this.x, this.y); + } + ArcTo( + this.x, this.y, + this.radiusX, this.radiusY, + this.startAngle, this.endAngle, this.anticlockwise, + this.iterations, + this.pathData + ); + if (this.pie) { + this.pathData.push(this.x, this.y); + } + this.pathData.push(this.pathData[0], this.pathData[1]); + + super.updateData(); + return this; + } + + canvasRender(ctx, dx, dy) { + ctx.beginPath(); + var x = this.x - dx, + y = this.y - dy, + startAngle = DegToRad(this.startAngle), + endAngle = DegToRad(this.endAngle); + if (this.pie) { + ctx.moveTo(x, y); + ctx.lineTo( + x + Math.cos(startAngle) * this.radiusX, + y + Math.sin(startAngle) * this.radiusY + ); + } + ctx.ellipse( + x, y, + this.radiusX, this.radiusY, + 0, + startAngle, endAngle, this.anticlockwise + ); + if (this.pie) { + ctx.lineTo(x, y); + } + if (this.isFilled) { + FillStyleCanvas(ctx, this); + ctx.fill(); + } + if (this.isStroked) { + LineStyleCanvas(ctx, this); + ctx.stroke(); + } + } +} + +export default Arc; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Circle.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Circle.d.ts new file mode 100644 index 000000000..bb6fe5786 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Circle.d.ts @@ -0,0 +1,5 @@ +import Arc from './Arc'; + +export default class Circle extends Arc { + constructor(x?: number, y?: number, radius?: number); +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Circle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Circle.js new file mode 100644 index 000000000..1438b54ec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Circle.js @@ -0,0 +1,9 @@ +import Arc from './Arc.js'; + +class Circle extends Arc { + constructor(x, y, radius) { + super(x, y, radius, radius, 0, 360); + } +} + +export default Circle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Ellipse.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Ellipse.d.ts new file mode 100644 index 000000000..a9e5791c9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Ellipse.d.ts @@ -0,0 +1,5 @@ +import Arc from './Arc'; + +export default class Ellipse extends Arc { + constructor(x?: number, y?: number, radiusX?: number, radiusY?: number); +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Ellipse.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Ellipse.js new file mode 100644 index 000000000..b7f5692f4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/arc/Ellipse.js @@ -0,0 +1,9 @@ +import Arc from './Arc.js'; + +class Ellipse extends Arc { + constructor(x, y, radiusX, radiusY) { + super(x, y, radiusX, radiusY, 0, 360); + } +} + +export default Ellipse; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/roundrectangle/RoundRectangle.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/roundrectangle/RoundRectangle.d.ts new file mode 100644 index 000000000..f9f4c859a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/roundrectangle/RoundRectangle.d.ts @@ -0,0 +1,42 @@ +import PathBase from "../PathBase"; + +export default RoundRectangle; + +declare namespace RoundRectangle { + interface IRadius { + tl?: number, + tr?: number, + bl?: number, + br?: number + } +} + +declare class RoundRectangle extends PathBase { + constructor( + x?: number, y?: number, + width?: number, height?: number, + radius?: number, + iteration?: number + ); + + setTopLeftPosition(x: number, y: number): this; + x: number; + y: number; + + setSize(width: number, height: number): this; + width: number; + height: number; + + setCenterPosition(x: number, y: number): this; + centerX: number; + centerY: number; + + setRadius(radius: number | RoundRectangle.IRadius): this; + radiusTL: number; + radiusTR: number; + radiusBL: number; + radiusBR: number; + + setIterations(iterations: number): this; + iterations: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/roundrectangle/RoundRectangle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/roundrectangle/RoundRectangle.js new file mode 100644 index 000000000..3ae83b04a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/lines/roundrectangle/RoundRectangle.js @@ -0,0 +1,261 @@ +import PathBase from '../PathBase.js'; +import ArcTo from '../../../../../../geom/pathdata/ArcTo.js'; +import LineTo from '../../../../../../geom/pathdata/LineTo.js'; +import Offset from '../../../../../../geom/pathdata/Offset.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class RoundRectangle extends PathBase { + constructor(x, y, width, height, radius, iterations) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (width === undefined) { width = 0; } + if (height === undefined) { height = width; } + if (radius === undefined) { radius = 0; } + if (iterations === undefined) { iterations = 6; } + + super(); + + this.setTopLeftPosition(x, y); + this.setSize(width, height); + this.setRadius(radius); + this.setIterations(iterations); + this.closePath = true; + } + + get x() { + return this._x; + } + + set x(value) { + this.dirty = this.dirty || (this._x !== value); + this._x = value; + } + + get y() { + return this._y; + } + + set y(value) { + this.dirty = this.dirty || (this._y !== value); + this._y = value; + } + + setTopLeftPosition(x, y) { + this.x = x; + this.y = y; + return this; + } + + get width() { + return this._width; + } + + set width(value) { + this.dirty = this.dirty || (this._width !== value); + this._width = value; + } + + get height() { + return this._height; + } + + set height(value) { + this.dirty = this.dirty || (this._height !== value); + this._height = value; + } + + setSize(width, height) { + this.width = width; + this.height = height; + return this; + } + + get centerX() { + return this.x + (this.width / 2); + } + + set centerX(value) { + this.x = value - (this.width / 2); + } + + get centerY() { + return this.y + (this.height / 2); + } + + set centerY(value) { + this.y = value - (this.height / 2); + } + + setCenterPosition(x, y) { + this.centerX = x; + this.centerY = y; + return this; + } + + get radiusTL() { + return this._radiusTL; + } + + set radiusTL(value) { + var isConvex = (value > 0); + this.dirty = this.dirty || (this._radiusTL !== value) || (this._convexTL !== isConvex); + this._convexTL = isConvex; + this._radiusTL = Math.abs(value); + + } + + get radiusTR() { + return this._radiusTR; + } + + set radiusTR(value) { + var isConvex = (value > 0); + this.dirty = this.dirty || (this._radiusTR !== value) || (this._convexTR !== isConvex); + this._convexTR = isConvex; + this._radiusTR = Math.abs(value); + } + + get radiusBL() { + return this._radiusBL; + } + + set radiusBL(value) { + var isConvex = (value > 0); + this.dirty = this.dirty || (this._radiusBL !== value) || (this._convexBL !== isConvex); + this._convexBL = isConvex; + this._radiusBL = Math.abs(value); + } + + get radiusBR() { + return this._radiusBR; + } + + set radiusBR(value) { + var isConvex = (value > 0); + this.dirty = this.dirty || (this._radiusBR !== value) || (this._convexBR !== isConvex); + this._convexBR = isConvex; + this._radiusBR = Math.abs(value); + } + + get radius() { + return Math.max(this.radiusTL, this.radiusTR, this.radiusBL, this.radiusBR,); + } + + set radius(value) { + if (typeof (value) === 'number') { + this.radiusTL = value; + this.radiusTR = value; + this.radiusBL = value; + this.radiusBR = value; + } else { + this.radiusTL = GetValue(value, 'tl', 0); + this.radiusTR = GetValue(value, 'tr', 0); + this.radiusBL = GetValue(value, 'bl', 0); + this.radiusBR = GetValue(value, 'br', 0); + } + } + + setRadius(radius) { + if (radius === undefined) { + radius = 0; + } + this.radius = radius; + return this; + } + + get iterations() { + return this._iterations; + } + + set iterations(value) { + this.dirty = this.dirty || (this._iterations !== value); + this._iterations = value; + } + + setIterations(iterations) { + this.iterations = iterations; + return this; + } + + updateData() { + var pathData = this.pathData; + pathData.length = 0; + + var width = this.width, height = this.height, + radius, + iterations = this.iterations + 1; + + // top-left + radius = this.radiusTL; + if (radius > 0) { + if (this._convexTL) { + var centerX = radius; + var centerY = radius; + ArcTo(centerX, centerY, radius, radius, 180, 270, false, iterations, pathData); + } else { + var centerX = 0; + var centerY = 0; + ArcTo(centerX, centerY, radius, radius, 90, 0, true, iterations, pathData); + } + } else { + LineTo(0, 0, pathData); + } + + // top-right + radius = this.radiusTR; + if (radius > 0) { + if (this._convexTR) { + var centerX = width - radius; + var centerY = radius; + ArcTo(centerX, centerY, radius, radius, 270, 360, false, iterations, pathData); + } else { + var centerX = width; + var centerY = 0; + ArcTo(centerX, centerY, radius, radius, 180, 90, true, iterations, pathData); + } + } else { + LineTo(width, 0, pathData); + } + + // bottom-right + radius = this.radiusBR; + if (radius > 0) { + if (this._convexBR) { + var centerX = width - radius; + var centerY = height - radius; + ArcTo(centerX, centerY, radius, radius, 0, 90, false, iterations, pathData); + } else { + var centerX = width; + var centerY = height; + ArcTo(centerX, centerY, radius, radius, 270, 180, true, iterations, pathData); + } + } else { + LineTo(width, height, pathData); + } + + // bottom-left + radius = this.radiusBL; + if (radius > 0) { + if (this._convexBL) { + var centerX = radius; + var centerY = height - radius; + ArcTo(centerX, centerY, radius, radius, 90, 180, false, iterations, pathData); + } else { + var centerX = 0; + var centerY = height; + ArcTo(centerX, centerY, radius, radius, 360, 270, true, iterations, pathData); + } + } else { + LineTo(0, height, pathData); + } + + pathData.push(pathData[0], pathData[1]); // Repeat first point to close curve + Offset(this.x, this.y, pathData); + + super.updateData(); + return this; + } +} + +export default RoundRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/rectangle/Rectangle.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/rectangle/Rectangle.d.ts new file mode 100644 index 000000000..663e0c6dc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/rectangle/Rectangle.d.ts @@ -0,0 +1,21 @@ +import BaseGeom from "../base/BaseGeom"; + +export default class Rectangle extends BaseGeom { + constructor( + x?: number, y?: number, + width?: number, height?: number + ); + + setTopLeftPosition(x: number, y: number): this; + x: number; + y: number; + + setSize(width: number, height: number): this; + width: number; + height: number; + + setCenterPosition(x: number, y: number): this; + centerX: number; + centerY: number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/rectangle/Rectangle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/rectangle/Rectangle.js new file mode 100644 index 000000000..7a7c162a1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/rectangle/Rectangle.js @@ -0,0 +1,143 @@ +import BaseGeom from '../base/BaseGeom.js'; +import StrokePathWebGL from '../../../utils/render/StrokePathWebGL.js'; +import FillStyleCanvas from '../../../utils/render/FillStyleCanvas.js'; +import LineStyleCanvas from '../../../utils/render/LineStyleCanvas.js'; + +const GetTint = Phaser.Renderer.WebGL.Utils.getTintAppendFloatAlpha; + +class Rectangle extends BaseGeom { + constructor(x, y, width, height) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (width === undefined) { width = 0; } + if (height === undefined) { height = width; } + + super(); + + this.pathData = []; + this.closePath = true; + + this.setTopLeftPosition(x, y); + this.setSize(width, height); + } + + get x() { + return this._x; + } + + set x(value) { + this.dirty = this.dirty || (this._x !== value); + this._x = value; + } + + get y() { + return this._y; + } + + set y(value) { + this.dirty = this.dirty || (this._y !== value); + this._y = value; + } + + setTopLeftPosition(x, y) { + this.x = x; + this.y = y; + return this; + } + + get width() { + return this._width; + } + + set width(value) { + this.dirty = this.dirty || (this._width !== value); + this._width = value; + } + + get height() { + return this._height; + } + + set height(value) { + this.dirty = this.dirty || (this._height !== value); + this._height = value; + } + + setSize(width, height) { + this.width = width; + this.height = height; + return this; + } + + get centerX() { + return this.x + (this.width / 2); + } + + set centerX(value) { + this.x = value - (this.width / 2); + } + + get centerY() { + return this.y + (this.height / 2); + } + + set centerY(value) { + this.y = value - (this.height / 2); + } + + setCenterPosition(x, y) { + this.centerX = x; + this.centerY = y; + return this; + } + + updateData() { + this.pathData.length = 0; + var x0 = this.x, + x1 = x0 + this.width, + y0 = this.y, + y1 = y0 + this.height; + this.pathData.push(x0, y0); + this.pathData.push(x1, y0); + this.pathData.push(x1, y1); + this.pathData.push(x0, y1); + this.pathData.push(x0, y0); + + super.updateData(); + return this; + } + + webglRender(pipeline, calcMatrix, alpha, dx, dy) { + if (this.isFilled) { + var fillTint = pipeline.fillTint; + var fillTintColor = GetTint(this.fillColor, this.fillAlpha * alpha); + + fillTint.TL = fillTintColor; + fillTint.TR = fillTintColor; + fillTint.BL = fillTintColor; + fillTint.BR = fillTintColor; + + pipeline.batchFillRect(-dx + this.x, -dy + this.y, this.width, this.height); + } + + if (this.isStroked) { + StrokePathWebGL(pipeline, this, alpha, dx, dy); + } + } + + canvasRender(ctx, dx, dy) { + if (this.isFilled) { + FillStyleCanvas(ctx, this); + ctx.fillRect(-dx, -dy, this.width, this.height); + } + + if (this.isStroked) { + LineStyleCanvas(ctx, this); + ctx.beginPath(); + ctx.rect(-dx, -dy, this.width, this.height); + ctx.stroke(); + } + } +} + +export default Rectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/triangle/Triangle.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/triangle/Triangle.d.ts new file mode 100644 index 000000000..fc1bc8e22 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/triangle/Triangle.d.ts @@ -0,0 +1,21 @@ +import BaseGeom from "../base/BaseGeom"; + +export default class Triangle extends BaseGeom { + constructor( + x0?: number, y0?: number, + x1?: number, y1?: number, + x2?: number, y2?: number + ); + + setP0(x: number, y: number): this; + x0: number; + y0: number; + + setP1(x: number, y: number): this; + x1: number; + y1: number; + + setP2(x: number, y: number): this; + x2: number; + y2: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/triangle/Triangle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/triangle/Triangle.js new file mode 100644 index 000000000..5dab405a3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/geoms/triangle/Triangle.js @@ -0,0 +1,165 @@ +import BaseGeom from '../base/BaseGeom.js'; +import StrokePathWebGL from '../../../utils/render/StrokePathWebGL.js'; +import FillStyleCanvas from '../../../utils/render/FillStyleCanvas.js'; +import LineStyleCanvas from '../../../utils/render/LineStyleCanvas.js'; + +const GetTint = Phaser.Renderer.WebGL.Utils.getTintAppendFloatAlpha; + +class Triangle extends BaseGeom { + constructor(x0, y0, x1, y1, x2, y2) { + if (x0 === undefined) { x0 = 0; } + if (y0 === undefined) { y0 = 0; } + if (x1 === undefined) { x1 = 0; } + if (y1 === undefined) { y1 = 0; } + if (x2 === undefined) { x2 = 0; } + if (y2 === undefined) { y2 = 0; } + + super(); + + this.pathData = []; + this.closePath = true; + + this.setP0(x0, y0); + this.setP1(x1, y1); + this.setP2(x2, y2); + } + + get x0() { + return this._x0; + } + + set x0(value) { + this.dirty = this.dirty || (this._x0 !== value); + this._x0 = value; + } + + get y0() { + return this._y0; + } + + set y0(value) { + this.dirty = this.dirty || (this._y0 !== value); + this._y0 = value; + } + + setP0(x, y) { + this.x0 = x; + this.y0 = y; + return this; + } + + get x1() { + return this._x1; + } + + set x1(value) { + this.dirty = this.dirty || (this._x1 !== value); + this._x1 = value; + } + + get y1() { + return this._y1; + } + + set y1(value) { + this.dirty = this.dirty || (this._y1 !== value); + this._y1 = value; + } + + setP1(x, y) { + this.x1 = x; + this.y1 = y; + return this; + } + + get x2() { + return this._x2; + } + + set x2(value) { + this.dirty = this.dirty || (this._x2 !== value); + this._x2 = value; + } + + get y2() { + return this._y2; + } + + set y2(value) { + this.dirty = this.dirty || (this._y2 !== value); + this._y2 = value; + } + + setP2(x, y) { + this.dirty = this.dirty || (this.x2 !== x) || (this.y2 !== y); + this.x2 = x; + this.y2 = y; + return this; + } + + updateData() { + this.pathData.length = 0; + this.pathData.push(this.x0, this.y0); + this.pathData.push(this.x1, this.y1); + this.pathData.push(this.x2, this.y2); + this.pathData.push(this.x0, this.y0); + + super.updateData(); + return this; + } + + webglRender(pipeline, calcMatrix, alpha, dx, dy) { + if (this.isFilled) { + var fillTintColor = GetTint(this.fillColor, this.fillAlpha * alpha); + + var x0 = this.x0 - dx; + var y0 = this.y0 - dy; + var x1 = this.x1 - dx; + var y1 = this.y1 - dy; + var x2 = this.x2 - dx; + var y2 = this.y2 - dy; + + var tx0 = calcMatrix.getX(x0, y0); + var ty0 = calcMatrix.getY(x0, y0); + var tx1 = calcMatrix.getX(x1, y1); + var ty1 = calcMatrix.getY(x1, y1); + var tx2 = calcMatrix.getX(x2, y2); + var ty2 = calcMatrix.getY(x2, y2); + + pipeline.batchTri(tx0, ty0, tx1, ty1, tx2, ty2, fillTintColor, fillTintColor, fillTintColor); + } + + if (this.isStroked) { + StrokePathWebGL(pipeline, this, alpha, dx, dy); + } + } + + canvasRender(ctx, dx, dy) { + var x1 = this.x1 - dx; + var y1 = this.y1 - dy; + var x2 = this.x2 - dx; + var y2 = this.y2 - dy; + var x3 = this.x3 - dx; + var y3 = this.y3 - dy; + + ctx.beginPath(); + + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.lineTo(x3, y3); + + ctx.closePath(); + + if (this.isFilled) { + FillStyleCanvas(ctx, this); + ctx.fill(); + } + + if (this.isStroked) { + LineStyleCanvas(ctx, this); + ctx.stroke(); + } + } +} + +export default Triangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/render/CanvasRenderer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/render/CanvasRenderer.js new file mode 100644 index 000000000..090bca811 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/render/CanvasRenderer.js @@ -0,0 +1,23 @@ +const SetTransform = Phaser.Renderer.Canvas.SetTransform; + +var CanvasRenderer = function (renderer, src, camera, parentMatrix) { + src.updateData(); + camera.addToRenderList(src); + + var ctx = renderer.currentContext; + + if (SetTransform(renderer, ctx, src, camera, parentMatrix)) { + var dx = src._displayOriginX; + var dy = src._displayOriginY; + + var shapes = src.geom; + for (var i = 0, cnt = shapes.length; i < cnt; i++) { + shapes[i].canvasRender(ctx, dx, dy); + } + + // Restore the context saved in SetTransform + ctx.restore(); + } +}; + +export default CanvasRenderer; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/render/Render.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/render/Render.js new file mode 100644 index 000000000..404fc7750 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/render/Render.js @@ -0,0 +1,8 @@ +import WebGLRenderer from './WebGLRenderer.js'; +import CanvasRenderer from './CanvasRenderer.js'; + +export default { + renderWebGL: WebGLRenderer, + renderCanvas: CanvasRenderer + +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/render/WebGLRenderer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/render/WebGLRenderer.js new file mode 100644 index 000000000..41cdd1faa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/shapes/render/WebGLRenderer.js @@ -0,0 +1,28 @@ +const GetCalcMatrix = Phaser.GameObjects.GetCalcMatrix; + +var WebGLRenderer = function (renderer, src, camera, parentMatrix) { + src.updateData(); + camera.addToRenderList(src); + + var pipeline = renderer.pipelines.set(src.pipeline); + + var result = GetCalcMatrix(src, camera, parentMatrix); + + var calcMatrix = pipeline.calcMatrix.copyFrom(result.calc); + + var dx = src._displayOriginX; + var dy = src._displayOriginY; + + var alpha = camera.alpha * src.alpha; + + renderer.pipelines.preBatch(src); + + var shapes = src.geom; + for (var i = 0, cnt = shapes.length; i < cnt; i++) { + shapes[i].webglRender(pipeline, calcMatrix, alpha, dx, dy); + } + + renderer.pipelines.postBatch(src); +}; + +export default WebGLRenderer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/Creator.js new file mode 100644 index 000000000..a1924fc87 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/Creator.js @@ -0,0 +1,19 @@ +import ToggleSwitch from './ToggleSwitch.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', width); + var color = GetAdvancedValue(config, 'color', 0x005cb2); + var gameObject = new ToggleSwitch(this.scene, 0, 0, width, height, color, config); + + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/Factory.js new file mode 100644 index 000000000..83a93e851 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/Factory.js @@ -0,0 +1,7 @@ +import ToggleSwitch from './ToggleSwitch.js'; + +export default function (x, y, width, height, color, config) { + var gameObject = new ToggleSwitch(this.scene, x, y, width, height, color, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitch.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitch.d.ts new file mode 100644 index 000000000..9612af071 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitch.d.ts @@ -0,0 +1,36 @@ +import ToggleSwitchShape from './ToggleSwitchShape'; +import Click from '../../../input/button/Button'; + +export default ToggleSwitch; + +declare namespace ToggleSwitch { + interface IConfig extends ToggleSwitchShape.IConfig { + readOnly?: boolean; + input?: Click.IConfig, + } +} + +declare class ToggleSwitch extends ToggleSwitchShape { + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + color?: number, + config?: ToggleSwitch.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + config?: ToggleSwitch.IConfig + ); + + constructor( + scene: Phaser.Scene, + config?: ToggleSwitch.IConfig + ); + + setReadOnly(readOnly?: boolean): this; + readOnly: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitch.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitch.js new file mode 100644 index 000000000..98e661c1b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitch.js @@ -0,0 +1,35 @@ +import ToggleSwitchShape from './ToggleSwitchShape.js'; +import Click from '../../../input/button/Button.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class ToggleSwitch extends ToggleSwitchShape { + constructor(scene, x, y, width, height, color, config) { + super(scene, x, y, width, height, color, config); + + this._click = new Click(this, GetValue(config, 'click')); + this._click.on('click', function () { + this.toggleValue(); + }, this); + + this.setReadOnly(GetValue(config, 'readOnly', false)); + } + + get readOnly() { + return !this._click.enable; + } + + set readOnly(value) { + this._click.enable = !value; + } + + setReadOnly(enable) { + if (enable === undefined) { + enable = true; + } + this.readOnly = enable; + return this; + } +} + +export default ToggleSwitch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShape.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShape.d.ts new file mode 100644 index 000000000..e6e6670f7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShape.d.ts @@ -0,0 +1,87 @@ +import BaseShapes from '../shapes/BaseShapes'; + +export default ToggleSwitch; + +declare namespace ToggleSwitch { + interface IConfig { + x: number, y: number, + width: number, height: number, + + color?: number, trackFillAlpha?: number, + falseValueTrackColor?: number, falseValueTrackFillAlpha?: number, + + thumbColor?: number, thumbAlpha?: number, + + trackWidth?: number, trackHeight?: number, + trackRadius?: number, + + thumbWidth?: number, thumbHeight?: number, + thumbRadius?: number, + + thumbLeft?: number, thumbRight?: number, + rtl?: boolean, + + animationDuration?: number, + + value?: boolean, + } +} + +declare class ToggleSwitch extends BaseShapes { + constructor( + scene: Phaser.Scene, + x: number, y: number, + width: number, height: number, + color?: number, + config?: ToggleSwitch.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + config?: ToggleSwitch.IConfig + ); + + constructor( + scene: Phaser.Scene, + config?: ToggleSwitch.IConfig + ); + + setValue(value: boolean): this; + toggleValue(): this; + value: boolean; + + setTrackFillStyle(color: number, alpha?: number): this; + trackFillColor: number; + trackFillAlpha: number; + setFalseValueTrackFillStyle(color: number, alpha?: number): this; + falseValueTrackColor: number; + falseValueTrackFillAlpha: number; + + setThumbStyle(color: number, alpha?: number): this; + thumbColor: number; + thumbAlpha: number; + + setTrackSize(width: number, height: number): this; + trackWidth: number; + trackHeight: number; + setTrackRadius(radius: number): this; + trackRadius: number; + + setThumbSize(width: number, height: number): this; + thumbWidth: number; + thumbHeight: number; + setThumbRadius(radius: number): this; + thumbRadius: number; + + setThumbPosition(left: number, right?: number): this; + thumbLeftX: number; + thumbRightX: number; + + setRTL(rtl?: boolean): this; + rtl: boolean; + + setToggleAnimationDuration(duration: number): this; + toggleAnimProgress: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShape.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShape.js new file mode 100644 index 000000000..accb96899 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShape.js @@ -0,0 +1,140 @@ +import BaseShapes from '../shapes/BaseShapes.js'; +import Methods from './methods/Methods.js'; +import GrayScale from '../../../utils/color/GrayScale.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const DefaultTrackFillColor = 0x005cb2; +const DefaultThumbFillColor = 0xffffff; + +class ToggleSwitchShape extends BaseShapes { + constructor(scene, x, y, width, height, color, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 2); + height = GetValue(config, 'height', 2); + color = GetValue(config, 'color', DefaultTrackFillColor); + } else if (IsPlainObject(color)) { + config = color; + color = GetValue(config, 'color', DefaultTrackFillColor); + } + + super(scene, x, y, width, height); + this.type = 'rexToggleSwitch'; + + if (color === undefined) { + color = DefaultTrackFillColor; + } + + this.setTrackFillStyle( + color, + GetValue(config, 'trackFillAlpha', 1) + ); + + this.setFalseValueTrackFillStyle( + GetValue(config, 'falseValueTrackColor', GrayScale(color)), + GetValue(config, 'falseValueTrackFillAlpha', 1) + ) + + this.setThumbStyle( + GetValue(config, 'thumbColor', DefaultThumbFillColor), + GetValue(config, 'thumbAlpha', 1) + ); + + this.setTrackSize( + GetValue(config, 'trackWidth', 0.9), + GetValue(config, 'trackHeight', 0.5), + ); + + this.setTrackRadius( + GetValue(config, 'trackRadius', this.trackHeight * 0.5) + ); + + var thumbHeight = GetValue(config, 'thumbHeight', undefined); + var thumbWidth = GetValue(config, 'thumbWidth', thumbHeight); + if (thumbWidth === undefined) { + thumbWidth = this.trackHeight * 0.9; + } + this.setThumbSize(thumbWidth, thumbHeight); + + this.setThumbRadius( + GetValue(config, 'thumbRadius', this.thumbHeight * 0.5) + ); + + this.setThumbPosition( + GetValue(config, 'thumbLeft', 0.3), + GetValue(config, 'thumbRight', undefined), + ) + + this.setRTL(GetValue(config, 'rtl', false)); + + this.setToggleAnimationDuration( + GetValue(config, 'animationDuration', 150) + ); + + this.buildShapes(); + + this.setValue(GetValue(config, 'value', false), 0); + + } + + get value() { + return this._value; + } + + set value(value) { + value = !!value; + + if (this._value === value) { + return; + } + + this.dirty = true; + this._value = value; + + this.playToggleAnimation(); + + this.emit('valuechange', value); + } + + setValue(value, duration) { + if (duration === undefined) { + duration = this.toggleAnimDuration; + } + + var durationSave = this.toggleAnimDuration; + this.toggleAnimDuration = duration; + + this.value = value; + + this.toggleAnimDuration = durationSave; + return this; + } + + toggleValue(duration) { + this.setValue(!this.value, duration); + return this; + } + + get toggleAnimProgress() { + return this._toggleAnimProgress; + } + + set toggleAnimProgress(value) { + if (this._toggleAnimProgress === value) { + return; + } + + this._toggleAnimProgress = value; + this.dirty = true; + } +} + +Object.assign( + ToggleSwitchShape.prototype, + Methods, +) + +export default ToggleSwitchShape; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShapeCreator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShapeCreator.js new file mode 100644 index 000000000..0d7eba01a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShapeCreator.js @@ -0,0 +1,19 @@ +import ToggleSwitchShape from './ToggleSwitchShape.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', width); + var color = GetAdvancedValue(config, 'color', 0x005cb2); + var gameObject = new ToggleSwitchShape(this.scene, 0, 0, width, height, color, config); + + BuildGameObject(this.scene, gameObject, config); + + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShapeFactory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShapeFactory.js new file mode 100644 index 000000000..bd2fb9a5b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/ToggleSwitchShapeFactory.js @@ -0,0 +1,7 @@ +import ToggleSwitchShape from './ToggleSwitchShape.js'; + +export default function (x, y, width, height, color, config) { + var gameObject = new ToggleSwitchShape(this.scene, x, y, width, height, color, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/Methods.js new file mode 100644 index 000000000..1f7d27344 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/Methods.js @@ -0,0 +1,19 @@ +import StyleMethods from './StyleMethods.js'; +import SizeMethods from './SizeMethods.js'; +import PositionMethods from './PositionMethods.js'; +import ShapesUpdateMethods from './ShapesUpdateMethods.js'; +import ToggleAnimationMethods from './ToggleAnimationMethods.js'; + +var methods = { + +} +Object.assign( + methods, + StyleMethods, + SizeMethods, + PositionMethods, + ShapesUpdateMethods, + ToggleAnimationMethods, +) + +export default methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/PositionMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/PositionMethods.js new file mode 100644 index 000000000..7be6a29d5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/PositionMethods.js @@ -0,0 +1,19 @@ +export default { + setThumbPosition(left, right) { + if (right === undefined) { + right = 1 - left; + } + + this.thumbLeftX = left; + this.thumbRightX = right; + return this; + }, + + setRTL(rtl) { + if (rtl === undefined) { + rtl = true; + } + this.rtl = rtl; + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/ShapesUpdateMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/ShapesUpdateMethods.js new file mode 100644 index 000000000..cf331a521 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/ShapesUpdateMethods.js @@ -0,0 +1,65 @@ +import { RoundRectangle } from '../../shapes/geoms'; +import MixColor from '../../../../utils/color/MixColor'; + +const Linear = Phaser.Math.Linear; + +export default { + buildShapes() { + this + .addShape(new RoundRectangle().setName('track')) + .addShape(new RoundRectangle().setName('thumb')) + }, + + updateShapes() { + var width = this.width, + height = this.height; + + var toggleAnimProgress = (this.value) ? this.toggleAnimProgress : (1 - this.toggleAnimProgress); + + // Track + var trackShape = this.getShape('track'); + if (this.isSizeChanged) { + var trackWidth = width * this.trackWidth, + trackHeight = height * this.trackHeight, + trackX = (width - trackWidth) / 2, + trackY = (height - trackHeight) / 2, + trackRadius = height * this.trackRadius; + + trackShape + .setTopLeftPosition(trackX, trackY) + .setSize(trackWidth, trackHeight) + .setRadius(trackRadius); + + } + + var trackFillColor = MixColor(this.falseValueTrackColor, this.trackFillColor, toggleAnimProgress); + var trackFillAlpha = Linear(this.falseValueTrackFillAlpha, this.trackFillAlpha, toggleAnimProgress); + trackShape + .fillStyle(trackFillColor, trackFillAlpha) + + // Thumb + var thumbShape = this.getShape('thumb'); + if (this.isSizeChanged) { + var thumbWidth = width * this.thumbWidth, + thumbHeight = height * this.thumbHeight, + thumbRadius = height * this.thumbRadius; + + thumbShape + .setSize(thumbWidth, thumbHeight) + .setRadius(thumbRadius); + } + + var thumbX = Linear(this.thumbLeftX, this.thumbRightX, toggleAnimProgress) * width; + if (this.rtl) { + thumbX = width - thumbX; + } + var thumbY = height / 2; + thumbShape + .setCenterPosition(thumbX, thumbY) + + thumbShape + .fillStyle(this.thumbColor, this.thumbAlpha) + } + + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/SizeMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/SizeMethods.js new file mode 100644 index 000000000..76efe77f8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/SizeMethods.js @@ -0,0 +1,41 @@ +export default { + setTrackSize(width, height) { + this.dirty = this.dirty || + (this.trackWidth !== width) || + (this.trackHeight !== height); + + this.trackWidth = width; + this.trackHeight = height; + return this; + }, + + setTrackRadius(radius) { + this.dirty = this.dirty || + (this.trackRadius !== radius); + + this.trackRadius = radius; + return this; + }, + + setThumbSize(width, height) { + if (height === undefined) { + height = width; + } + this.dirty = this.dirty || + (this.thumbWidth !== width) || + (this.thumbHeight !== height); + + this.thumbWidth = width; + this.thumbHeight = height; + return this; + }, + + setThumbRadius(radius) { + this.dirty = this.dirty || + (this.thumbRadius !== radius); + + this.thumbRadius = radius; + return this; + }, + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/StyleMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/StyleMethods.js new file mode 100644 index 000000000..8c74097b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/StyleMethods.js @@ -0,0 +1,41 @@ +export default { + setTrackFillStyle(color, alpha) { + if (alpha === undefined) { + alpha = 1; + } + this.dirty = this.dirty || + (this.trackFillColor !== color) || + (this.trackFillAlpha !== alpha); + + this.trackFillColor = color; + this.trackFillAlpha = alpha; + return this; + }, + + setFalseValueTrackFillStyle(color, alpha) { + if (alpha === undefined) { + alpha = 1; + } + this.dirty = this.dirty || + (this.falseValueTrackColor !== color) || + (this.uncheckedTrackFillAlpha !== alpha); + + this.falseValueTrackColor = color; + this.falseValueTrackFillAlpha = alpha; + return this; + }, + + setThumbStyle(color, alpha) { + if (alpha === undefined) { + alpha = 1; + } + this.dirty = this.dirty || + (this.thumbColor !== color) || + (this.checkAlpha !== alpha); + + this.thumbColor = color; + this.thumbAlpha = alpha; + return this; + }, + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/ToggleAnimationMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/ToggleAnimationMethods.js new file mode 100644 index 000000000..c5f4274f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/toggleswitch/methods/ToggleAnimationMethods.js @@ -0,0 +1,36 @@ +import EaseValueTask from '../../../../utils/ease/EaseValueTask.js'; + +export default { + setToggleAnimationDuration(duration) { + if (duration === undefined) { + duration = 0; + } + this.toggleAnimDuration = duration; + return this; + }, + + playToggleAnimation() { + if (this.toggleAnimProgressTask === undefined) { + this.toggleAnimProgressTask = new EaseValueTask(this, { eventEmitter: null }); + } + + this.toggleAnimProgressTask.restart({ + key: 'toggleAnimProgress', + from: 0, + to: 1, + duration: this.toggleAnimDuration, + }); + + return this; + }, + + stopToggleAnimation() { + if (this.toggleAnimProgressTask === undefined) { + return this; + } + + this.toggleAnimProgressTask.stop(); + return this; + }, + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Creator.js new file mode 100644 index 000000000..2b300d552 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Creator.js @@ -0,0 +1,13 @@ +import Triangle from './Triangle.js'; + +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var gameObject = new Triangle(this.scene, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Factory.js new file mode 100644 index 000000000..014cceb94 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Factory.js @@ -0,0 +1,7 @@ +import Triangle from './Triangle.js'; + +export default function (x, y, width, height, fillColor, fillAlpha) { + var gameObject = new Triangle(this.scene, x, y, width, height, fillColor, fillAlpha); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Triangle.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Triangle.d.ts new file mode 100644 index 000000000..fb2b3faa8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Triangle.d.ts @@ -0,0 +1,76 @@ +import BaseShapes from '../shapes/BaseShapes'; + +// import * as Phaser from 'phaser'; +export default Triangle; + +declare namespace Triangle { + type DirectionType = 0 | 1 | 2 | 3 | 'right' | 'down' | 'left' | 'up'; + + interface IPaddingConfig { + x?: number, y?: number, + + left?: number, right?: number, top?: number, bottom?: number, + } + + interface IConfig { + x?: number, y?: number, + width?: number, height?: number, + + color?: number, + alpha?: number, + + strokeColor?: number, + strokeAlpha?: number, + strokeWidth?: number, + arrowOnly?: boolean, + + direction?: DirectionType, + easeDuration?: number, + padding?: number | IPaddingConfig, + + radius?: number, + } +} + +declare class Triangle extends BaseShapes { + constructor( + scene: Phaser.Scene, + config?: Triangle.IConfig + ); + + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + width?: number, height?: number, + fillColor?: number, fillAlpha?: number + ); + + setArrowOnly(enable?: boolean): this; + arrowOnly: boolean; + + setEaseDuration(duration?: number): this; + easeDuration: number; + + setDirection( + direction: Triangle.DirectionType, + easeDuration?: number + ): this; + toggleDirection(easeDuration?: number): this; + direction: number; + + setPadding(left?: number, top?: number, right?: number, bottom?: number): this; + setPadding(padding?: Triangle.IPaddingConfig): this; + padding: { + left: number, right: number, top: number, bottom: number, + }; + + setRadius(radius?: number): this; + radius: number; + + setVerticeRotation(rotation: number): this; + verticeRotation: number; + + setVerticeAngle(angle: number): this; + verticeAngle: number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Triangle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Triangle.js new file mode 100644 index 000000000..21639c3a4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/Triangle.js @@ -0,0 +1,261 @@ +import BaseShapes from '../shapes/BaseShapes.js'; +import ShapesUpdateMethods from './methods/ShapesUpdateMethods.js'; +import EaseDirectionMethods from './methods/EaseDirectionMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const DegToRad = Phaser.Math.DegToRad; +const RadToDeg = Phaser.Math.RadToDeg + +class Triangle extends BaseShapes { + constructor(scene, x, y, width, height, fillColor, fillAlpha) { + var strokeColor, strokeAlpha, strokeWidth, arrowOnly; + var direction, easeDuration, padding; + var radius; + if (IsPlainObject(x)) { + var config = x; + + x = config.x; + y = config.y; + width = config.width; + height = config.height; + + fillColor = config.color; + fillAlpha = config.alpha; + + strokeColor = config.strokeColor; + strokeAlpha = config.strokeAlpha; + strokeWidth = config.strokeWidth; + arrowOnly = config.arrowOnly; + + direction = config.direction; + easeDuration = config.easeDuration; + padding = config.padding; + + radius = config.radius; + } + + if (x === undefined) { x = 0; } + if (y === undefined) { y = 0; } + if (width === undefined) { width = 1; } + if (height === undefined) { height = width; } + + if (arrowOnly === undefined) { arrowOnly = false; } + if (direction === undefined) { direction = 0; } + if (easeDuration === undefined) { easeDuration = 0; } + if (padding === undefined) { padding = 0; } + if (radius === undefined) { radius = undefined; } + + super(scene, x, y, width, height); + this.type = 'rexTriangle'; + + this.setFillStyle(fillColor, fillAlpha); + + if ((strokeColor !== undefined) && (strokeWidth === undefined)) { + strokeWidth = 2; + } + this.setStrokeStyle(strokeWidth, strokeColor, strokeAlpha); + + this.setArrowOnly(arrowOnly); + + this.setDirection(direction, easeDuration); + + this.setPadding(padding); + + this.setRadius(radius); + + this.buildShapes(); + + } + + get arrowOnly() { + return this._arrowOnly; + } + + set arrowOnly(value) { + this.dirty = this.dirty || (this._arrowOnly != value); + this._arrowOnly = value; + } + + setArrowOnly(enable) { + if (enable === undefined) { + enable = true; + } + this.arrowOnly = enable; + return this; + } + + get direction() { + return this._direction; + } + + set direction(value) { + value = ParseDirection(value); + if (this._direction === value) { + return; + } + + if ((this.easeDuration > 0) && (this._direction !== undefined)) { + this.previousDirection = this._direction; + } else { + this.previousDirection = undefined; + } + + this._direction = value; + this.verticeAngle = value * 90; + this.dirty = true; + + if (this.previousDirection !== undefined) { + this.playEaseDirectionation(); + } else { + this.stopEaseDirection(); + } + } + + setDirection(direction, easeDuration) { + if (easeDuration !== undefined) { + this.setEaseDuration(easeDuration); + } + + this.direction = direction; + return this; + } + + toggleDirection(easeDuration) { + this.setDirection((this.direction + 2), easeDuration); + return this; + } + + get easeDirectionProgress() { + return this._easeDirectionProgress; + } + + set easeDirectionProgress(value) { + if (this._easeDirectionProgress === value) { + return; + } + + this._easeDirectionProgress = value; + this.dirty = true; + } + + setPadding(left, top, right, bottom) { + if (typeof left === 'object') { + var config = left; + + // If they specify x and/or y this applies to all + var x = GetValue(config, 'x', null); + + if (x !== null) { + left = x; + right = x; + } + else { + left = GetValue(config, 'left', 0); + right = GetValue(config, 'right', left); + } + + var y = GetValue(config, 'y', null); + + if (y !== null) { + top = y; + bottom = y; + } + else { + top = GetValue(config, 'top', 0); + bottom = GetValue(config, 'bottom', top); + } + } + else { + if (left === undefined) { left = 0; } + if (top === undefined) { top = left; } + if (right === undefined) { right = left; } + if (bottom === undefined) { bottom = top; } + } + + if (this.padding === undefined) { + this.padding = {}; + } + + this.dirty = this.dirty || + (this.padding.left != left) || + (this.padding.top != top) || + (this.padding.right != right) || + (this.padding.bottom != bottom); + + this.padding.left = left; + this.padding.top = top; + this.padding.right = right; + this.padding.bottom = bottom; + + // Switch to fit mode + this.setRadius(); + + return this; + } + + get radius() { + return this._radius; + } + + set radius(value) { + this.dirty = this.dirty || (this._radius != value); + this._radius = value; + } + + setRadius(radius) { + this.radius = radius; + + // 0: fit mode + // 1: circle mode + this.shapeMode = (radius == null) ? 0 : 1; + return this; + } + + get verticeRotation() { + return this._verticeRotation; + } + + set verticeRotation(value) { + this.dirty = this.dirty || (this._verticeRotation != value); + this._verticeRotation = value; + } + + setVerticeRotation(rotation) { + this.verticeRotation = rotation; + return this; + } + + get verticeAngle() { + return RadToDeg(this.verticeRotation); + } + + set verticeAngle(value) { + this.verticeRotation = DegToRad(value); + } + + setVerticeAngle(angle) { + this.verticeAngle = angle; + return this; + } + +} + +const DirectionNameMap = { + right: 0, down: 1, left: 2, up: 3 +} +var ParseDirection = function (direction) { + if (typeof (direction) === 'string') { + direction = DirectionNameMap[direction]; + } + direction = direction % 4; + return direction; +} + +Object.assign( + Triangle.prototype, + ShapesUpdateMethods, + EaseDirectionMethods, +) + +export default Triangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/DrawCircleVerticesTriangle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/DrawCircleVerticesTriangle.js new file mode 100644 index 000000000..5ab82efe7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/DrawCircleVerticesTriangle.js @@ -0,0 +1,35 @@ +const DegToRad = Phaser.Math.DegToRad; +const Rad120 = DegToRad(120); + +var DrawCircleVerticesTriangle = function (triangle) { + var triangle = this.getShape('triangle'); + + var centerX = this.width / 2, + centerY = this.height / 2; + + var radius = Math.min(centerX, centerY) * this.radius, + verticeRotation = this.verticeRotation; + + triangle + .startAt( + centerX + radius * Math.cos(verticeRotation + Rad120), + centerY + radius * Math.sin(verticeRotation + Rad120) + ) + .lineTo( + centerX + radius * Math.cos(verticeRotation), + centerY + radius * Math.sin(verticeRotation) + ) + .lineTo( + centerX + radius * Math.cos(verticeRotation - Rad120), + centerY + radius * Math.sin(verticeRotation - Rad120) + ) + + if (!this.arrowOnly) { + triangle.close(); + } else { + triangle.end(); + } + +} + +export default DrawCircleVerticesTriangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/DrawFitTriangle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/DrawFitTriangle.js new file mode 100644 index 000000000..d5688037c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/DrawFitTriangle.js @@ -0,0 +1,62 @@ +const Wrap = Phaser.Math.Wrap; +const Linear = Phaser.Math.Linear; + +var DrawFitTriangle = function () { + var triangle = this.getShape('triangle'); + + var padding = this.padding; + var right = this.width - padding.right; + var left = 0 + padding.left; + var bottom = this.height - padding.bottom; + var top = 0 + padding.top; + var centerX = (left + right) / 2; + var centerY = (top + bottom) / 2; + + var points = { + 0: { // right + a: { x: left, y: top }, b: { x: right, y: centerY }, c: { x: left, y: bottom }, + }, + 1: { // down + a: { x: left, y: top }, b: { x: centerX, y: bottom }, c: { x: right, y: top }, + }, + 2: { // left + a: { x: right, y: top }, b: { x: left, y: centerY }, c: { x: right, y: bottom }, + }, + 3: { // up + a: { x: left, y: bottom }, b: { x: centerX, y: top }, c: { x: right, y: bottom }, + } + } + + var pax, pay, pbx, pby, pcx, pcy; + if (this.previousDirection === undefined) { + var currentTrianglePoints = points[this.direction]; + var pa = currentTrianglePoints.a, + pb = currentTrianglePoints.b, + pc = currentTrianglePoints.c; + + pax = pa.x; pay = pa.y; + pbx = pb.x; pby = pb.y; + pcx = pc.x; pcy = pc.y; + + } else { + var p0 = points[this.previousDirection]; + var p1 = points[this.direction]; + var t = this.easeDirectionProgress; + pax = Linear(p0.a.x, p1.a.x, t); + pay = Linear(p0.a.y, p1.a.y, t); + pbx = Linear(p0.b.x, p1.b.x, t); + pby = Linear(p0.b.y, p1.b.y, t); + pcx = Linear(p0.c.x, p1.c.x, t); + pcy = Linear(p0.c.y, p1.c.y, t); + } + + triangle.startAt(pax, pay).lineTo(pbx, pby).lineTo(pcx, pcy); + + if (!this.arrowOnly) { + triangle.close(); + } else { + triangle.end(); + } + +} +export default DrawFitTriangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/EaseDirectionMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/EaseDirectionMethods.js new file mode 100644 index 000000000..d055e322e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/EaseDirectionMethods.js @@ -0,0 +1,36 @@ +import EaseValueTask from '../../../../utils/ease/EaseValueTask.js'; + +export default { + setEaseDuration(duration) { + if (duration === undefined) { + duration = 0; + } + this.easeDuration = duration; + return this; + }, + + playEaseDirectionation() { + if (this.easeDirectionProgressTask === undefined) { + this.easeDirectionProgressTask = new EaseValueTask(this, { eventEmitter: null }); + } + + this.easeDirectionProgressTask.restart({ + key: 'easeDirectionProgress', + from: 0, + to: 1, + duration: this.easeDuration, + }); + + return this; + }, + + stopEaseDirection() { + if (this.easeDirectionProgressTask === undefined) { + return this; + } + + this.easeDirectionProgressTask.stop(); + return this; + }, + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/ShapesUpdateMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/ShapesUpdateMethods.js new file mode 100644 index 000000000..cc3192a4b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/triangle/methods/ShapesUpdateMethods.js @@ -0,0 +1,33 @@ +import { Lines } from '../../shapes/geoms'; +import DrawFitTriangle from './DrawFitTriangle.js'; +import DrawCircleVerticesTriangle from './DrawCircleVerticesTriangle'; + +export default { + buildShapes() { + this + .addShape(new Lines().setName('triangle')) + }, + + updateShapes() { + // Set style + var triangle = this.getShape('triangle'); + + if (!this.arrowOnly) { + triangle + .fillStyle(this.fillColor, this.fillAlpha) + .lineStyle(this.lineWidth, this.strokeColor, this.strokeAlpha) + } else { + triangle + .fillStyle() + .lineStyle(this.lineWidth, this.strokeColor, this.strokeAlpha) + } + + // Set points + if (this.shapeMode === 0) { + DrawFitTriangle.call(this); + } else { + DrawCircleVerticesTriangle.call(this); + } + + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/FillPathWebGL.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/FillPathWebGL.js new file mode 100644 index 000000000..8fcb899fd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/FillPathWebGL.js @@ -0,0 +1,43 @@ +/* +src: { + fillColor, + fillAlpha, + pathData, + pathIndexes // Earcut(pathData) +} +*/ + +var Utils = Phaser.Renderer.WebGL.Utils; + +var FillPathWebGL = function (pipeline, calcMatrix, src, alpha, dx, dy) +{ + var fillTintColor = Utils.getTintAppendFloatAlpha(src.fillColor, src.fillAlpha * alpha); + + var path = src.pathData; + var pathIndexes = src.pathIndexes; + + for (var i = 0; i < pathIndexes.length; i += 3) + { + var p0 = pathIndexes[i] * 2; + var p1 = pathIndexes[i + 1] * 2; + var p2 = pathIndexes[i + 2] * 2; + + var x0 = path[p0 + 0] - dx; + var y0 = path[p0 + 1] - dy; + var x1 = path[p1 + 0] - dx; + var y1 = path[p1 + 1] - dy; + var x2 = path[p2 + 0] - dx; + var y2 = path[p2 + 1] - dy; + + var tx0 = calcMatrix.getX(x0, y0); + var ty0 = calcMatrix.getY(x0, y0); + var tx1 = calcMatrix.getX(x1, y1); + var ty1 = calcMatrix.getY(x1, y1); + var tx2 = calcMatrix.getX(x2, y2); + var ty2 = calcMatrix.getY(x2, y2); + + pipeline.batchTri(src, tx0, ty0, tx1, ty1, tx2, ty2, 0, 0, 1, 1, fillTintColor, fillTintColor, fillTintColor, 2); + } +}; + +export default FillPathWebGL; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/FillStyleCanvas.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/FillStyleCanvas.js new file mode 100644 index 000000000..733e3fb7c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/FillStyleCanvas.js @@ -0,0 +1,13 @@ +var FillStyleCanvas = function (ctx, src, altColor, altAlpha) +{ + var fillColor = (altColor) ? altColor : src.fillColor; + var fillAlpha = (altAlpha) ? altAlpha : src.fillAlpha; + + var red = ((fillColor & 0xFF0000) >>> 16); + var green = ((fillColor & 0xFF00) >>> 8); + var blue = (fillColor & 0xFF); + + ctx.fillStyle = 'rgba(' + red + ',' + green + ',' + blue + ',' + fillAlpha + ')'; +}; + +export default FillStyleCanvas; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/LineStyleCanvas.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/LineStyleCanvas.js new file mode 100644 index 000000000..31b9fcc9c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/LineStyleCanvas.js @@ -0,0 +1,14 @@ +var LineStyleCanvas = function (ctx, src, altColor, altAlpha) +{ + var strokeColor = (altColor) ? altColor : src.strokeColor; + var strokeAlpha = (altAlpha) ? altAlpha : src.strokeAlpha; + + var red = ((strokeColor & 0xFF0000) >>> 16); + var green = ((strokeColor & 0xFF00) >>> 8); + var blue = (strokeColor & 0xFF); + + ctx.strokeStyle = 'rgba(' + red + ',' + green + ',' + blue + ',' + strokeAlpha + ')'; + ctx.lineWidth = src.lineWidth; +}; + +export default LineStyleCanvas; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/StrokePathWebGL.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/StrokePathWebGL.js new file mode 100644 index 000000000..363cc2e5d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/shape/utils/render/StrokePathWebGL.js @@ -0,0 +1,57 @@ +/* +src: { + strokeColor, + strokeAlpha, + pathData, + lineWidth, + closePath +} +*/ +var Utils = Phaser.Renderer.WebGL.Utils; + +var StrokePathWebGL = function (pipeline, src, alpha, dx, dy) +{ + var strokeTint = pipeline.strokeTint; + var strokeTintColor = Utils.getTintAppendFloatAlpha(src.strokeColor, src.strokeAlpha * alpha); + + strokeTint.TL = strokeTintColor; + strokeTint.TR = strokeTintColor; + strokeTint.BL = strokeTintColor; + strokeTint.BR = strokeTintColor; + + var path = src.pathData; + var pathLength = path.length - 1; + var lineWidth = src.lineWidth; + var halfLineWidth = lineWidth / 2; + + var px1 = path[0] - dx; + var py1 = path[1] - dy; + + if (!src.closePath) + { + pathLength -= 2; + } + + for (var i = 2; i < pathLength; i += 2) + { + var px2 = path[i] - dx; + var py2 = path[i + 1] - dy; + + pipeline.batchLine( + px1, + py1, + px2, + py2, + halfLineWidth, + halfLineWidth, + lineWidth, + i - 2, + (src.closePath) ? (i === pathLength - 1) : false + ); + + px1 = px2; + py1 = py2; + } +}; + +export default StrokePathWebGL; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText.d.ts new file mode 100644 index 000000000..4e98501af --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText.d.ts @@ -0,0 +1,17 @@ +import Text from '../textbase/Text'; + +export default BBCodeText; + +declare namespace BBCodeText { + + interface TextStyle extends Text.TextStyle { + delimiters: string | string[]; + } +} + +declare class BBCodeText extends Text { + setDelimiters( + delimiterLeft: string | string[], + delimiterRight?: string + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText.js new file mode 100644 index 000000000..168728f49 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText.js @@ -0,0 +1,16 @@ +import Text from '../textbase/Text.js' +import ParserKlass from './parser/Parser.js'; + +class BBCodeText extends Text { + constructor(scene, x, y, text, style) { + var parser = new ParserKlass(style); + super(scene, x, y, text, style, 'rexBBCodeText', parser); + } + + setDelimiters(delimiterLeft, delimiterRight) { + this.parse.setDelimiters(delimiterLeft, delimiterRight); + return this; + } +} + +export default BBCodeText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/Creator.js new file mode 100644 index 000000000..858443e5e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/Creator.js @@ -0,0 +1,59 @@ +import BBCodeText from './BBCodeText.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + + // style Object = { + // font: [ 'font', '16px Courier' ], + // backgroundColor: [ 'backgroundColor', null ], + // backgroundColor2: [ 'backgroundColor2', null ], + // backgroundHorizontalGradient: ['backgroundHorizontalGradient', true], + // backgroundStrokeColor: ['backgroundStrokeColor', null], + // backgroundStrokeLineWidth: ['backgroundStrokeLineWidth', 2], + // backgroundCornerRadius: ['backgroundCornerRadius', 0], + // backgroundCornerIteration: ['backgroundCornerIteration', null], + // fill: [ 'fill', '#fff' ], + // stroke: [ 'stroke', '#fff' ], + // strokeThickness: [ 'strokeThickness', 0 ], + // shadowOffsetX: [ 'shadow.offsetX', 0 ], + // shadowOffsetY: [ 'shadow.offsetY', 0 ], + // shadowColor: [ 'shadow.color', '#000' ], + // shadowBlur: [ 'shadow.blur', 0 ], + // shadowStroke: [ 'shadow.stroke', false ], + // shadowFill: [ 'shadow.fill', false ], + // align: [ 'align', 'left' ], + // maxLines: [ 'maxLines', 0 ], + // fixedWidth: [ 'fixedWidth', false ], + // fixedHeight: [ 'fixedHeight', false ] + // } + + var content = GetAdvancedValue(config, 'text', ''); + var style = GetAdvancedValue(config, 'style', null); + + // Padding + // { padding: 2 } + // { padding: { x: , y: }} + // { padding: { left: , top: }} + // { padding: { left: , right: , top: , bottom: }} + + var padding = GetAdvancedValue(config, 'padding', null); + + if (padding !== null) { + style.padding = padding; + } + + var gameObject = new BBCodeText(this.scene, 0, 0, content, style); + BuildGameObject(this.scene, gameObject, config); + + // Text specific config options: + + gameObject.autoRound = GetAdvancedValue(config, 'autoRound', true); + + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/Factory.js new file mode 100644 index 000000000..eb00df8eb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/Factory.js @@ -0,0 +1,7 @@ +import BBCodeText from './BBCodeText.js'; + +export default function (x, y, text, style) { + var gameObject = new BBCodeText(this.scene, x, y, text, style); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/Parser.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/Parser.js new file mode 100644 index 000000000..37906924f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/Parser.js @@ -0,0 +1,46 @@ +import SplitText from './SplitText.js'; +import TagTextToProp from './TagTextToProp.js'; +import PropToContextStyle from './PropToContextStyle.js' +import PropToTagText from './PropToTagText.js'; +import { GetTagRegex, SetDelimiters } from './TagRegex.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Parser { + constructor(style) { + var delimiters = GetValue(style, 'delimiters', '[]'); + this.tagRegex = GetTagRegex(delimiters); + } + + getStrokeThinkness(defaultStyle, prop) { + var strokeThickness; + if (prop.hasOwnProperty('stroke')) { + strokeThickness = defaultStyle.strokeThickness; + } else { + strokeThickness = 0; + } + return strokeThickness; + } + + setDelimiters(delimiterLeft, delimiterRight) { + if (SetDelimiters(delimiterLeft, delimiterRight)) { + this.tagRegex = GetTagRegex(); + } + return this; + } + +} + +var methods = { + splitText: SplitText, + tagTextToProp: TagTextToProp, + propToContextStyle: PropToContextStyle, + propToTagText: PropToTagText, +} + +Object.assign( + Parser.prototype, + methods +); + +export default Parser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/PropToContextStyle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/PropToContextStyle.js new file mode 100644 index 000000000..bacb084ec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/PropToContextStyle.js @@ -0,0 +1,120 @@ +import TextStyle from '../../../textbase/textstyle/TextStyle.js'; + +var PropToContextStyle = function (defaultStyle, prop) { + var result = STYLE_RESULT; + if (!prop.hasOwnProperty('img')) { + result.image = null; + + if (prop.hasOwnProperty('family')) { + result.fontFamily = prop.family; + } else { + result.fontFamily = defaultStyle.fontFamily; + } + + if (prop.hasOwnProperty('size')) { + var size = prop.size; + if (typeof (size) === 'number') { + size = `${size}px`; + } + result.fontSize = size; + } else { + result.fontSize = defaultStyle.fontSize; + } + result.fontStyle = GetFontStyle(prop); + + if (prop.hasOwnProperty('color')) { + result.color = prop.color; + } else { + result.color = defaultStyle.color; + } + + if (prop.hasOwnProperty('stroke')) { + if (prop.stroke === true) { + result.stroke = defaultStyle.stroke; + result.strokeThickness = defaultStyle.strokeThickness; + } else { + result.stroke = prop.stroke; + result.strokeThickness = defaultStyle.strokeThickness; + } + } else { + result.stroke = defaultStyle.stroke; + result.strokeThickness = 0; + } + } else { + result.image = prop.img; + } + + if (prop.hasOwnProperty('shadow')) { + if (prop.shadow === true) { + result.shadowColor = defaultStyle.shadowColor; + result.shadowOffsetX = defaultStyle.shadowOffsetX; + result.shadowOffsetY = defaultStyle.shadowOffsetY; + result.shadowBlur = defaultStyle.shadowBlur; + result.shadowStroke = true; + result.shadowFill = true; + } else { + result.shadowColor = prop.shadow; + result.shadowOffsetX = defaultStyle.shadowOffsetX; + result.shadowOffsetY = defaultStyle.shadowOffsetY; + result.shadowBlur = defaultStyle.shadowBlur; + result.shadowStroke = true; + result.shadowFill = true; + } + } else { + result.shadowColor = '#000'; + result.shadowOffsetX = 0; + result.shadowOffsetY = 0; + result.shadowBlur = 0; + result.shadowStroke = false; + result.shadowFill = false; + } + + if (prop.hasOwnProperty('u')) { + if (prop.u === true) { + result.underlineColor = defaultStyle.underlineColor; + result.underlineThickness = defaultStyle.underlineThickness; + result.underlineOffset = defaultStyle.underlineOffset; + } else { + result.underlineColor = prop.u; + result.underlineThickness = defaultStyle.underlineThickness; + result.underlineOffset = defaultStyle.underlineOffset; + } + } else { + result.underlineColor = '#000'; + result.underlineThickness = 0; + result.underlineOffset = 0; + } + + return result; +} + +var GetFontStyle = function (prop) { + var isBold = prop.b; + var weight = prop.weight; + var isItalic = prop.i; + + if (isBold || weight || isItalic) { + if (isItalic) { + if (isBold) { + return 'bold italic'; + } else if (weight) { + return `${weight} italic`; + } else { + return 'italic'; + } + } else { // !isItalic + if (isBold) { + return 'bold'; + } else { + return weight.toString(); + } + } + } else { + return ''; + } +}; + + +var STYLE_RESULT = new TextStyle(); + +export default PropToContextStyle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/PropToTagText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/PropToTagText.js new file mode 100644 index 000000000..285c9beb8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/PropToTagText.js @@ -0,0 +1,59 @@ +var PropToTagText = function (text, prop, prevProp) { + if (prevProp == null) { + prevProp = EMPTYPROP; + } + + var headers = []; + + for (var k in prevProp) { + if (!prop.hasOwnProperty(k)) { + headers.push(`[/${k}]`); + } + } + + for (var k in prop) { + var value = prop[k]; + + if (prevProp[k] === value) { + continue; + } + + switch (k) { + case 'size': + headers.push(`[size=${value.replace('px', '')}]`); + break; + + case 'color': + case 'weight': + case 'stroke': + case 'y': + case 'img': + case 'area': + case 'url': + case 'align': + headers.push(`[${k}=${value}]`); + break; + + case 'u': + if (value === true) { + headers.push('[u]'); + } else { + headers.push(`[u=${value}]`) + } + break; + + default: + headers.push(`[${k}]`); + break; + } + } + + headers.push(text); + + return headers.join(''); +} + +var EMPTYPROP = {}; + + +export default PropToTagText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/SplitText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/SplitText.js new file mode 100644 index 000000000..12584d17a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/SplitText.js @@ -0,0 +1,60 @@ +var SplitText = function (text, mode) { + var TagRegex = this.tagRegex; + + var result = []; + var charIdx = 0; + var rawMode = false, + escMode = false; + while (true) { + var regexResult = TagRegex.RE_SPLITTEXT.exec(text); + if (!regexResult) { + break; + } + + var match = regexResult[0]; + if (escMode) { + if (TagRegex.RE_ESC_CLOSE.test(match)) { + escMode = false; + } else { + continue; // Skip other tags + } + + } else if (rawMode) { + if (TagRegex.RE_RAW_CLOSE.test(match)) { + rawMode = false; + } else { + continue; // Skip other tags + } + + } else { + if (TagRegex.RE_ESC_OPEN.test(match)) { + escMode = true; + } else if (TagRegex.RE_RAW_OPEN.test(match)) { + rawMode = true; + } + } + + var matchEnd = TagRegex.RE_SPLITTEXT.lastIndex; + var matchStart = matchEnd - match.length; + + if (charIdx < matchStart) { + var content = text.substring(charIdx, matchStart); + result.push(content); + } + + if (mode === undefined) { + result.push(match); + } + + charIdx = matchEnd; + } + + var totalLen = text.length; + if (charIdx < totalLen) { // Push remainder string + result.push(text.substring(charIdx, totalLen)); + } + + return result; // [text,...] +} + +export default SplitText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/TagRegex.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/TagRegex.js new file mode 100644 index 000000000..3259b4651 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/TagRegex.js @@ -0,0 +1,182 @@ +import EscapeRegex from '../../../../utils/string/EscapeRegex.js'; + +var DelimiterLeftSave; +var DelimiterRightSave; +var TagRegexSave = {}; + +var GetOpenTagRegString = function (delimiterLeft, delimiterRight, tagName, param) { + if (param === undefined) { + return `${delimiterLeft}${tagName}${delimiterRight}`; + } else { + return `${delimiterLeft}${tagName}=(${param})${delimiterRight}`; + } +} +var GetCloseTagRegString = function (delimiterLeft, delimiterRight, tagName) { + return `${delimiterLeft}\/${tagName}${delimiterRight}`; +} + +var NUMBER_PARAM = '[-.0-9]+'; +var COLOR_PARAM = '[a-z]+|#[0-9abcdef]+'; +var STR_PARAM = '[^\\]]+'; + +var SetDelimiters = function (delimiterLeft, delimiterRight) { + if (delimiterRight === undefined) { + var delimeters = delimiterLeft; + delimiterLeft = delimeters[0]; + delimiterRight = delimeters[1]; + } + + if ((DelimiterLeftSave === delimiterLeft) && (DelimiterRightSave === delimiterRight)) { + return false; + } + + DelimiterLeftSave = delimiterLeft; + DelimiterRightSave = delimiterRight; + + delimiterLeft = EscapeRegex(delimiterLeft); + delimiterRight = EscapeRegex(delimiterRight); + + var ESC = 'esc'; + var ESC_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, ESC); + var ESC_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, ESC); + + var RAW = 'raw'; + var RAW_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, RAW); + var RAW_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, RAW); + + var BLOD = 'b'; + var BLOD_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, BLOD); + var BLOD_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, BLOD); + + var ITALICS = 'i'; + var ITALICS_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, ITALICS); + var ITALICS_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, ITALICS); + + var WEIGHT = 'weight'; + var WEIGHT_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, WEIGHT, NUMBER_PARAM); + var WEIGHT_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, WEIGHT); + + var SIZE = 'size'; + var SIZE_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, SIZE, NUMBER_PARAM); + var SIZE_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, SIZE); + + var COLOR = 'color'; + var COLOR_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, COLOR, COLOR_PARAM); + var COLOR_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, COLOR); + + var UNDERLINE = 'u'; + var UNDERLINE_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, UNDERLINE); + var UNDERLINE_OPENC = GetOpenTagRegString(delimiterLeft, delimiterRight, UNDERLINE, COLOR_PARAM); + var UNDERLINE_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, UNDERLINE); + + var SHADOW = 'shadow'; + var SHADOW_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, SHADOW); + var SHADOW_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, SHADOW); + + var STROKE = 'stroke'; + var STROKE_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, STROKE); + var STROKE_OPENC = GetOpenTagRegString(delimiterLeft, delimiterRight, STROKE, COLOR_PARAM); + var STROKE_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, STROKE); + + var OFFSETY = 'y'; + var OFFSETY_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, OFFSETY, NUMBER_PARAM); + var OFFSETY_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, OFFSETY); + + var IMAGE = 'img'; + var IMAGE_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, IMAGE, STR_PARAM); + var IMAGE_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, IMAGE); + + var AREA = 'area'; + var AREA_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, AREA, STR_PARAM); + var AREA_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, AREA); + + var URL = 'url'; + var URL_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, URL, STR_PARAM); + var URL_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, URL); + + var ALIGN = 'align'; + var ALIGN_OPEN = GetOpenTagRegString(delimiterLeft, delimiterRight, ALIGN, STR_PARAM); + var ALIGN_CLOSE = GetCloseTagRegString(delimiterLeft, delimiterRight, ALIGN); + + TagRegexSave.RE_ESC_OPEN = new RegExp(ESC_OPEN, 'i'); + TagRegexSave.RE_ESC_CLOSE = new RegExp(ESC_CLOSE, 'i'); + + TagRegexSave.RE_RAW_OPEN = new RegExp(RAW_OPEN, 'i'); + TagRegexSave.RE_RAW_CLOSE = new RegExp(RAW_CLOSE, 'i'); + + TagRegexSave.RE_BLOD_OPEN = new RegExp(BLOD_OPEN, 'i'); + TagRegexSave.RE_BLOD_CLOSE = new RegExp(BLOD_CLOSE, 'i'); + + TagRegexSave.RE_ITALICS_OPEN = new RegExp(ITALICS_OPEN, 'i'); + TagRegexSave.RE_ITALICS_CLOSE = new RegExp(ITALICS_CLOSE, 'i'); + + TagRegexSave.RE_WEIGHT_OPEN = new RegExp(WEIGHT_OPEN, 'i'); + TagRegexSave.RE_WEIGHT_CLOSE = new RegExp(WEIGHT_CLOSE, 'i'); + + TagRegexSave.RE_SIZE_OPEN = new RegExp(SIZE_OPEN, 'i'); + TagRegexSave.RE_SIZE_CLOSE = new RegExp(SIZE_CLOSE, 'i'); + + TagRegexSave.RE_COLOR_OPEN = new RegExp(COLOR_OPEN, 'i'); + TagRegexSave.RE_COLOR_CLOSE = new RegExp(COLOR_CLOSE, 'i'); + + TagRegexSave.RE_UNDERLINE_OPEN = new RegExp(UNDERLINE_OPEN, 'i'); + TagRegexSave.RE_UNDERLINE_OPENC = new RegExp(UNDERLINE_OPENC, 'i'); + TagRegexSave.RE_UNDERLINE_CLOSE = new RegExp(UNDERLINE_CLOSE, 'i'); + + TagRegexSave.RE_SHADOW_OPEN = new RegExp(SHADOW_OPEN, 'i'); + TagRegexSave.RE_SHADOW_CLOSE = new RegExp(SHADOW_CLOSE, 'i'); + + TagRegexSave.RE_STROKE_OPEN = new RegExp(STROKE_OPEN, 'i'); + TagRegexSave.RE_STROKE_OPENC = new RegExp(STROKE_OPENC, 'i'); + TagRegexSave.RE_STROKE_CLOSE = new RegExp(STROKE_CLOSE, 'i'); + + TagRegexSave.RE_OFFSETY_OPEN = new RegExp(OFFSETY_OPEN, 'i'); + TagRegexSave.RE_OFFSETY_CLOSE = new RegExp(OFFSETY_CLOSE, 'i'); + + TagRegexSave.RE_IMAGE_OPEN = new RegExp(IMAGE_OPEN, 'i'); + TagRegexSave.RE_IMAGE_CLOSE = new RegExp(IMAGE_CLOSE, 'i'); + + TagRegexSave.RE_AREA_OPEN = new RegExp(AREA_OPEN, 'i') + TagRegexSave.RE_AREA_CLOSE = new RegExp(AREA_CLOSE, 'i'); + + TagRegexSave.RE_URL_OPEN = new RegExp(URL_OPEN, 'i') + TagRegexSave.RE_URL_CLOSE = new RegExp(URL_CLOSE, 'i'); + + TagRegexSave.RE_ALIGN_OPEN = new RegExp(ALIGN_OPEN, 'i') + TagRegexSave.RE_ALIGN_CLOSE = new RegExp(ALIGN_CLOSE, 'i'); + + TagRegexSave.RE_SPLITTEXT = new RegExp([ + RAW_OPEN, RAW_CLOSE, + ESC_OPEN, ESC_CLOSE, + + BLOD_OPEN, BLOD_CLOSE, + ITALICS_OPEN, ITALICS_CLOSE, + WEIGHT_OPEN, WEIGHT_CLOSE, + + SIZE_OPEN, SIZE_CLOSE, + COLOR_OPEN, COLOR_CLOSE, + UNDERLINE_OPEN, UNDERLINE_OPENC, UNDERLINE_CLOSE, + SHADOW_OPEN, SHADOW_CLOSE, + STROKE_OPEN, STROKE_OPENC, STROKE_CLOSE, + OFFSETY_OPEN, OFFSETY_CLOSE, + IMAGE_OPEN, IMAGE_CLOSE, + AREA_OPEN, AREA_CLOSE, + URL_OPEN, URL_CLOSE, + ALIGN_OPEN, ALIGN_CLOSE + ].join('|'), 'ig'); + + return true; +} + +var GetTagRegex = function (delimiterLeft, delimiterRight) { + if (delimiterLeft !== undefined) { + SetDelimiters(delimiterLeft, delimiterRight); + } + + return Object.assign({}, TagRegexSave); +} + +export { + SetDelimiters, + GetTagRegex, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/TagTextToProp.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/TagTextToProp.js new file mode 100644 index 000000000..d384fcccf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/parser/TagTextToProp.js @@ -0,0 +1,152 @@ +const PROP_REMOVE = false; +const PROP_ADD = true; + +var GETPROP_RESULT = { + plainText: null, + prevProp: null +}; + +var TagTextToProp = function (text, prevProp) { + var TagRegex = this.tagRegex; + + // text : result of splitText() + if (prevProp == null) { + prevProp = {}; + } + var plainText = ''; + + // close image tag + if (prevProp.img) { + UpdateProp(prevProp, PROP_REMOVE, 'img'); + } + + if (prevProp.esc) { + if (TagRegex.RE_ESC_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'esc'); + } else { + plainText = text; + } + + } else if (prevProp.raw) { + if (TagRegex.RE_RAW_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'raw'); + } else { + plainText = text; + } + + } else { + if (TagRegex.RE_ESC_OPEN.test(text)) { + UpdateProp(prevProp, PROP_ADD, 'esc', true); + } else if (TagRegex.RE_ESC_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'esc'); + + } else if (TagRegex.RE_RAW_OPEN.test(text)) { + UpdateProp(prevProp, PROP_ADD, 'raw', true); + } else if (TagRegex.RE_RAW_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'raw'); + + } else if (TagRegex.RE_BLOD_OPEN.test(text)) { + UpdateProp(prevProp, PROP_ADD, 'b', true); + } else if (TagRegex.RE_BLOD_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'b'); + + } else if (TagRegex.RE_ITALICS_OPEN.test(text)) { + UpdateProp(prevProp, PROP_ADD, 'i', true); + } else if (TagRegex.RE_ITALICS_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'i'); + + } else if (TagRegex.RE_WEIGHT_OPEN.test(text)) { + var innerMatch = text.match(TagRegex.RE_WEIGHT_OPEN); + UpdateProp(prevProp, PROP_ADD, 'weight', innerMatch[1]); + } else if (TagRegex.RE_WEIGHT_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'weight'); + + } else if (TagRegex.RE_SIZE_OPEN.test(text)) { + var innerMatch = text.match(TagRegex.RE_SIZE_OPEN); + UpdateProp(prevProp, PROP_ADD, 'size', `${innerMatch[1]}px`); + } else if (TagRegex.RE_SIZE_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'size'); + + } else if (TagRegex.RE_COLOR_OPEN.test(text)) { + var innerMatch = text.match(TagRegex.RE_COLOR_OPEN); + UpdateProp(prevProp, PROP_ADD, 'color', innerMatch[1]); + } else if (TagRegex.RE_COLOR_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'color'); + + } else if (TagRegex.RE_UNDERLINE_OPEN.test(text)) { + UpdateProp(prevProp, PROP_ADD, 'u', true); + } else if (TagRegex.RE_UNDERLINE_OPENC.test(text)) { + var innerMatch = text.match(TagRegex.RE_UNDERLINE_OPENC); + UpdateProp(prevProp, PROP_ADD, 'u', innerMatch[1]); + } else if (TagRegex.RE_UNDERLINE_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'u'); + + } else if (TagRegex.RE_SHADOW_OPEN.test(text)) { + UpdateProp(prevProp, PROP_ADD, 'shadow', true); + } else if (TagRegex.RE_SHADOW_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'shadow'); + + } else if (TagRegex.RE_STROKE_OPEN.test(text)) { + UpdateProp(prevProp, PROP_ADD, 'stroke', true); + } else if (TagRegex.RE_STROKE_OPENC.test(text)) { + var innerMatch = text.match(TagRegex.RE_STROKE_OPENC); + UpdateProp(prevProp, PROP_ADD, 'stroke', innerMatch[1]); + } else if (TagRegex.RE_STROKE_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'stroke'); + + } else if (TagRegex.RE_OFFSETY_OPEN.test(text)) { + var innerMatch = text.match(TagRegex.RE_OFFSETY_OPEN); + UpdateProp(prevProp, PROP_ADD, 'y', parseFloat(innerMatch[1])); + } else if (TagRegex.RE_OFFSETY_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'y'); + + } else if (TagRegex.RE_IMAGE_OPEN.test(text)) { + var innerMatch = text.match(TagRegex.RE_IMAGE_OPEN); + UpdateProp(prevProp, PROP_ADD, 'img', innerMatch[1]); + } else if (TagRegex.RE_IMAGE_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'img'); + + } else if (TagRegex.RE_AREA_OPEN.test(text)) { + var innerMatch = text.match(TagRegex.RE_AREA_OPEN); + UpdateProp(prevProp, PROP_ADD, 'area', innerMatch[1]); + } else if (TagRegex.RE_AREA_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'area'); + + } else if (TagRegex.RE_URL_OPEN.test(text)) { + var innerMatch = text.match(TagRegex.RE_URL_OPEN); + UpdateProp(prevProp, PROP_ADD, 'url', innerMatch[1]); + } else if (TagRegex.RE_URL_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'url'); + + } else if (TagRegex.RE_ALIGN_OPEN.test(text)) { + var innerMatch = text.match(TagRegex.RE_ALIGN_OPEN); + UpdateProp(prevProp, PROP_ADD, 'align', innerMatch[1]); + } else if (TagRegex.RE_ALIGN_CLOSE.test(text)) { + UpdateProp(prevProp, PROP_REMOVE, 'align'); + + } else { + plainText = text; + } + } + + var result = GETPROP_RESULT; + result.plainText = plainText; + result.prop = prevProp; + return result; +} + +var UpdateProp = function (prop, op, key, value) { + if (op === PROP_ADD) { + // PROP_ADD + prop[key] = value; + } else { + // PROP_REMOVE + if (prop.hasOwnProperty(key)) { + delete prop[key]; + } + } + + return prop; +}; + +export default TagTextToProp; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/Creator.js new file mode 100644 index 000000000..5571d4aa1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/Creator.js @@ -0,0 +1,59 @@ +import TagText from './TagText.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + + // style Object = { + // font: [ 'font', '16px Courier' ], + // backgroundColor: [ 'backgroundColor', null ], + // backgroundColor2: [ 'backgroundColor2', null ], + // backgroundHorizontalGradient: ['backgroundHorizontalGradient', true], + // backgroundStrokeColor: ['backgroundStrokeColor', null], + // backgroundStrokeLineWidth: ['backgroundStrokeLineWidth', 2], + // backgroundCornerRadius: ['backgroundCornerRadius', 0], + // backgroundCornerIteration: ['backgroundCornerIteration', null], + // fill: [ 'fill', '#fff' ], + // stroke: [ 'stroke', '#fff' ], + // strokeThickness: [ 'strokeThickness', 0 ], + // shadowOffsetX: [ 'shadow.offsetX', 0 ], + // shadowOffsetY: [ 'shadow.offsetY', 0 ], + // shadowColor: [ 'shadow.color', '#000' ], + // shadowBlur: [ 'shadow.blur', 0 ], + // shadowStroke: [ 'shadow.stroke', false ], + // shadowFill: [ 'shadow.fill', false ], + // align: [ 'align', 'left' ], + // maxLines: [ 'maxLines', 0 ], + // fixedWidth: [ 'fixedWidth', false ], + // fixedHeight: [ 'fixedHeight', false ] + // } + + var content = GetAdvancedValue(config, 'text', ''); + var style = GetAdvancedValue(config, 'style', null); + + // Padding + // { padding: 2 } + // { padding: { x: , y: }} + // { padding: { left: , top: }} + // { padding: { left: , right: , top: , bottom: }} + + var padding = GetAdvancedValue(config, 'padding', null); + + if (padding !== null) { + style.padding = padding; + } + + var gameObject = new TagText(this.scene, 0, 0, content, style); + BuildGameObject(this.scene, gameObject, config); + + // Text specific config options: + + gameObject.autoRound = GetAdvancedValue(config, 'autoRound', true); + + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/Factory.js new file mode 100644 index 000000000..3498e13da --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/Factory.js @@ -0,0 +1,7 @@ +import TagText from './TagText.js'; + +export default function (x, y, text, style) { + var gameObject = new TagText(this.scene, x, y, text, style); + this.scene.add.existing(gameObject); + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/Parser.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/Parser.js new file mode 100644 index 000000000..1d18d41bd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/Parser.js @@ -0,0 +1,309 @@ +import TextStyle from '../../textbase/textstyle/TextStyle.js'; + +var GETPROP_RESULT = { + plainText: null, + prevProp: null +}; + +var STYLE_RESULT = new TextStyle(); + +class Parser { + constructor(tags) { + if (tags === undefined) { + tags = {}; + } + this.tags = tags; + } + + addTag(name, prop) { + this.tags[name] = prop; + } + + getTag(name) { + return this.tags[name]; + } + + splitText(text, mode) { + var result = []; + var charIdx = 0; + while (true) { + var regexResult = RE_SPLITTEXT.exec(text); + if (!regexResult) { + break; + } + + var match = regexResult[0]; + var matchStart = RE_SPLITTEXT.lastIndex - match.length; + + if (charIdx < matchStart) { + result.push(text.substring(charIdx, matchStart)); + } + if (mode === undefined) { + result.push(match); + } else if (mode === 1) { // RAWTEXTONLY_MODE + if (RE_CLASS_HEADER.test(match)) { + var innerMatch = match.match(RE_CLASS); + result.push(innerMatch[2]); + } else if (RE_STYLE_HEADER.test(match)) { + var innerMatch = match.match(RE_STYLE); + result.push(innerMatch[2]); + } + } + + charIdx = RE_SPLITTEXT.lastIndex; + } + + var totalLen = text.length; + if (charIdx < totalLen) { // Push remainder string + result.push(text.substring(charIdx, totalLen)); + } + + return result; // [text,...] + } + + tagTextToProp(text, prevProp) { + var plainText, propOut; + if (RE_CLASS_HEADER.test(text)) { + var innerMatch = text.match(RE_CLASS); + if (innerMatch != null) { + var name = innerMatch[1]; + var tags = this.tags; + if (tags.hasOwnProperty(name)) { + propOut = tags[name]; + } else { + propOut = {}; + } + propOut._class = name; + plainText = innerMatch[2]; + } + } else if (RE_STYLE_HEADER.test(text)) { + var innerMatch = text.match(RE_STYLE); + if (innerMatch != null) { + var style = innerMatch[1]; + propOut = StyleToProp(style); + propOut._style = style; + plainText = innerMatch[2]; + } + } + + if (plainText == null) { + plainText = text; + } + + if (propOut == null) { + propOut = {}; + } + + var result = GETPROP_RESULT; + result.plainText = plainText; + result.prop = propOut; + return result; + } + + propToContextStyle(defaultStyle, prop) { + var result = STYLE_RESULT; + if (!prop.hasOwnProperty('img')) { + result.image = null; + + if (prop.hasOwnProperty('family') || prop.hasOwnProperty('fontFamily') || prop.hasOwnProperty('font-family')) { + var family = (prop.hasOwnProperty('family')) ? prop.family : + (prop.hasOwnProperty('fontFamily')) ? prop.fontFamily : + prop['font-family']; + result.fontFamily = family; + } else { + result.fontFamily = defaultStyle.fontFamily; + } + + if (prop.hasOwnProperty('size') || prop.hasOwnProperty('fontSize') || prop.hasOwnProperty('font-size')) { + var size = (prop.hasOwnProperty('size')) ? prop.size : + (prop.hasOwnProperty('fontSize')) ? prop.fontSize : + prop['font-size']; + if (typeof (size) === 'number') { + size = `${size}px`; + } + result.fontSize = size; + } else { + result.fontSize = defaultStyle.fontSize; + } + + if (prop.hasOwnProperty('style') || prop.hasOwnProperty('fontStyle') || prop.hasOwnProperty('font-style')) { + var fontStyle = (prop.hasOwnProperty('style')) ? prop.style : + (prop.hasOwnProperty('fontStyle')) ? prop.fontStyle : + prop['font-style']; + result.fontStyle = fontStyle; + } else { + result.fontStyle = defaultStyle.fontStyle; + } + + if (prop.hasOwnProperty('color') || prop.hasOwnProperty('font-color')) { + var color = (prop.hasOwnProperty('color')) ? prop.color : prop['font-color']; + result.color = color; + } else { + result.color = defaultStyle.color; + } + + if (prop.hasOwnProperty('stroke')) { + var stroke = prop.stroke; // {color, thickness} + result.stroke = (stroke.hasOwnProperty('color')) ? stroke.color : defaultStyle.stroke; + result.strokeThickness = (stroke.hasOwnProperty('thickness')) ? stroke.thickness : defaultStyle.strokeThickness; + } else { + result.stroke = defaultStyle.stroke; + result.strokeThickness = defaultStyle.strokeThickness; + } + } else { + result.image = prop.img; + } + + if (prop.hasOwnProperty('shadow')) { + var shadow = prop.shadow; // {color, offsetX, offsetY, blur} + result.shadowColor = (shadow.hasOwnProperty('color')) ? shadow.color : defaultStyle.shadowColor; + result.shadowOffsetX = (shadow.hasOwnProperty('offsetX')) ? shadow.offsetX : defaultStyle.shadowOffsetX; + result.shadowOffsetY = (shadow.hasOwnProperty('offsetY')) ? shadow.offsetY : defaultStyle.shadowOffsetY; + result.shadowBlur = (shadow.hasOwnProperty('blur')) ? shadow.blur : defaultStyle.shadowBlur; + result.shadowStroke = true; + result.shadowFill = true; + } else { + result.shadowColor = defaultStyle.shadowColor; + result.shadowOffsetX = defaultStyle.shadowOffsetX; + result.shadowOffsetY = defaultStyle.shadowOffsetY; + result.shadowBlur = defaultStyle.shadowBlur; + result.shadowStroke = defaultStyle.shadowStroke; + result.shadowFill = defaultStyle.shadowFill; + } + + if (prop.hasOwnProperty('u') || prop.hasOwnProperty('underline')) { + var u = (prop.hasOwnProperty('u')) ? prop.u : prop.underline; // {color, thickness, offset} + result.underlineColor = (u.hasOwnProperty('color')) ? u.color : defaultStyle.underlineColor; + result.underlineThickness = (u.hasOwnProperty('thickness')) ? u.thickness : defaultStyle.underlineThickness; + result.underlineOffset = (u.hasOwnProperty('offset')) ? u.offset : defaultStyle.underlineOffset; + } else { + result.underlineColor = defaultStyle.underlineColor; + result.underlineThickness = defaultStyle.underlineThickness; + result.underlineOffset = defaultStyle.underlineOffset; + } + + return result; + } + + getStrokeThinkness(defaultStyle, prop) { + var strokeThinkness; + if (prop.hasOwnProperty('stroke')) { + var stroke = prop.stroke; // {color, thickness} + strokeThinkness = (stroke.hasOwnProperty('thickness')) ? stroke.thickness : defaultStyle.strokeThickness; + } else { + strokeThinkness = defaultStyle.strokeThickness; + } + return strokeThinkness; + } + + propToTagText(text, prop, prevProp) { + if (prop.hasOwnProperty('_class')) { // class mode + if (text === '') { + if (this.isTextTag(prop._class)) { + return ''; + } + } + return `${text}`; + } else if (prop.hasOwnProperty('_style')) { // class mode + return `${text}`; + } else { + return text; + } + } + + destroy() { + this.tags = undefined; + } + + isTextTag(tagName) { + var tag = this.tags[tagName]; + if (tag) { + return (tag.img == null); + } else { // tag not found + return false; + } + } +}; + +var StyleToProp = function (s) { + s = s.split(";"); + + var result = {}, + prop, k, v; + for (var i = 0, slen = s.length; i < slen; i++) { + prop = s[i].split(":"); + k = prop[0], v = prop[1]; + if (isEmpty(k) || isEmpty(v)) { + continue; + } + + switch (k) { + case 'stroke': + var stroke = v.split(' '); // stroke:blue 1px + var len = stroke.length; + v = {}; + if (len >= 1) { + v.color = stroke[0]; + } + if (len >= 2) { + v.thickness = parseInt(stroke[1].replace('px', '')); + } + break; + + case 'shadow': + var shadow = v.split(' '); // shadow:blue 2px 2px 2px + var len = shadow.length; + v = {}; + if (len >= 1) { + v.color = shadow[0]; + } + if (len >= 2) { + v.offsetX = parseInt(shadow[1].replace('px', '')); + } + if (len >= 3) { + v.offsetY = parseInt(shadow[2].replace('px', '')); + } + if (len >= 4) { + v.blur = parseInt(shadow[3].replace('px', '')); + } + break; + + case 'u': + case 'underline': // underline:blue 3px -1px + var u = v.split(' '); + var len = u.length; + v = {}; + if (len >= 1) { + v.color = u[0]; + } + if (len >= 2) { + v.thickness = parseInt(u[1].replace('px', '')); + } + if (len >= 3) { + v.offset = parseInt(u[2].replace('px', '')); + } + break; + + case 'y': + v = parseFloat(v); + break; + } + result[k] = v; + } + return result; +}; + +var isEmpty = function (s) { + // Remove white spaces. + s = s.replace(RE_SPACE, ''); + return (s.length === 0); +}; + +var RE_SPLITTEXT = /<\s*class=["|']([^"|']+)["|']\s*\>([\s\S]*?)<\s*\/class\s*\>|<\s*style=["|']([^"|']+)["|']\s*\>([\s\S]*?)<\s*\/style\s*\>/g; +var RE_CLASS_HEADER = /<\s*class=/i; +var RE_CLASS = /<\s*class=["|']([^"|']+)["|']\s*\>([\s\S]*?)<\s*\/class\s*\>/; +var RE_STYLE_HEADER = /<\s*style=/i; +var RE_STYLE = /<\s*style=["|']([^"|']+)["|']\s*\>([\s\S]*?)<\s*\/style\s*\>/; +var RE_SPACE = /^\s+|\s+$/; + +export default Parser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/TagText.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/TagText.d.ts new file mode 100644 index 000000000..b0a1e7bc9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/TagText.d.ts @@ -0,0 +1,65 @@ +import Text from '../textbase/Text'; + +export default TagText; + +declare namespace TagText { + + interface TagProg { + color?: string, + + stroke?: { + color?: string, + thickness?: number, + }, + + fontSize?: string, + fontFamily?: string, + fontStyle?: string, + + shadow?: { + color?: string, + offsetX?: number, + offsetY?: number, + blur?: number, + }, + + underline?: { + color?: string, + thickness?: number, + offset?: number, + }, + + y?: number, + + img?: string, + + area?: string, + + url?: string, + } + + interface TextStyle extends Text.TextStyle { + tags?: { + [name: string]: TagProg + } + } + +} + +declare class TagText extends Text { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + content?: string, + style?: TagText.TextStyle + ); + + addTag( + name: string, + prop: TagText.TagProg + ): this; + + addTags( + tags: { [name: string]: TagText.TagProg } + ): this; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/TagText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/TagText.js new file mode 100644 index 000000000..30d643b80 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/tagtext/TagText.js @@ -0,0 +1,36 @@ +import Text from '../textbase/Text.js'; +import ParserKlass from './Parser.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class TagText extends Text { + constructor(scene, x, y, text, style) { + var tags = GetValue(style, 'tags', undefined); + var parser = new ParserKlass(tags); + super(scene, x, y, text, style, 'rexTagText', parser); + } + + addTag(name, prop) { + this.parser.addTag(name, prop); + return this.updateText(true); + } + + addTags(tags) { + for (var name in tags) { + this.parser.addTag(name, tags[name]); + } + return this.updateText(true); + } + + getTag(name) { + return this.parser.getTag(name); + } + + preDestroy() { + super.preDestroy(); + this.parser.destroy(); + this.parser = undefined; + } +} + +export default TagText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/FullFill.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/FullFill.js new file mode 100644 index 000000000..86b07e7d1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/FullFill.js @@ -0,0 +1,22 @@ +var FullFill = function (textObject, width, height) { + textObject.setFixedSize(width, height); + // Remove padding + var padding = textObject.padding; + width -= (padding.left + padding.right); + height -= (padding.top + padding.bottom); + + var style = textObject.style; + // Set wrap width + style.wrapWidth = Math.max(width, 0); + + // Set max lines + // height = (maxLines * (lineHeight + lineSpacing)) - lineSpacing + var lineHeight = style.metrics.fontSize + style.strokeThickness; + var lineSpacing = style.lineSpacing; + var maxLines = Math.floor((height - lineSpacing) / (lineHeight + lineSpacing)); + style.maxLines = Math.max(maxLines, 0); + + // Redraw text + textObject.updateText(); +} +export default FullFill; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/IsCanvasTextGameObject.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/IsCanvasTextGameObject.js new file mode 100644 index 000000000..70bf6c349 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/IsCanvasTextGameObject.js @@ -0,0 +1,7 @@ +import Text from './Text.js'; + +var IsCanvasTextGameObject = function (gameObject) { + return (gameObject instanceof Text); +} + +export default IsCanvasTextGameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/Text.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/Text.d.ts new file mode 100644 index 000000000..2e528d77c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/Text.d.ts @@ -0,0 +1,266 @@ +// import * as Phaser from 'phaser'; +import CanvasGameObjectBase from '../../../utils/types/CanvasGameObjectBase'; +import TextStyleBase from '../../textbase/textstyle/TextStyleInterface'; + +export default Text; + +declare namespace Text { + + type MetricsType = { + ascent: number, + descent: number, + fontSize: number + }; + + type FontConfigType = string | + { + fontFamily?: string, + fontSize?: string, + fontStyle?: string + }; + + type TextMarginsType = { + left?: number + }; + + interface ImageData { + key: string, frame?: string, + width?: number, height?: number, + y?: number, + left?: number, right?: number, + } + + interface TextStyle extends TextStyleBase { + images?: ImageData[], + interactive?: boolean, + urlCursor?: string, + } + + namespace Events { + type AnyAreaCallbackType = ( + key: string, + pointer: Phaser.Input.Pointer, + localX: number, + localY: number + ) => void; + + type AreaCallbackType = ( + pointer: Phaser.Input.Pointer, + localX: number, + localY: number + ) => void; + } + +} + +declare class Text extends CanvasGameObjectBase { + constructor( + scene: Phaser.Scene, + x?: number, y?: number, + content?: string, + + style?: Text.TextStyle + ); + + text: string; + setText(text: string | number | string[]): this; + appendText( + text: string | number | string[], + addCR?: boolean + ): this; + getPlainText( + text?: string | undefined, + start?: number, end?: number + ): string; + getWrappedText( + text?: string | undefined, + start?: number, end?: number + ): string; + getText( + text?: string | undefined, + start?: number, end?: number + ): string; + getSubString( + text?: string | undefined, + start?: number, end?: number + ): string; + + updateText(runWrap?: boolean): this; + + setWrapMode( + mode: 0 | 1 | 2 | 'none' | 'word' | 'char' | 'character' + ): this; + setWrapWidth(width: number): this; + setWordWrapWidth(width: number): this; + + setFont(font: Text.FontConfigType): this; + setFontFamily(family: string): this; + setFontSize(size: number | string): this; + setFontStyle(style: string): this; + setStyle(style: Text.TextStyle): this; + setTestString(string: string): this; + + setColor( + color?: null | string | number + ): this; + setFill( + color?: null | string | number + ): this; + + setStroke( + color?: null | string | number, + thickness?: number + ): this; + + setUnderline( + color?: null | string | number, + thickness?: number, + ofset?: number + ): this; + setUnderlineColor( + color?: null | string | number + ): this; + setUnderlineThinkness(thickness: number): this; + setUnderlineOffset(ofset: number): this; + + setBackgroundColor( + color?: null | string | number, + color2?: null | string | number, + isHorizontalGradient?: boolean + ): this; + setBackgroundStrokeColor( + color?: null | string | number, + lineWidth?: number + ): this; + setBackgroundCornerRadius( + radius?: number, + iteration?: number + ): this; + + setShadow( + x?: number, y?: number, + color?: null | string | number, + blur?: number, + shadowStroke?: boolean, + shadowFill?: boolean + ): this; + setShadowOffset(x: number, y: number): this; + setShadowColor(color?: null | string | number): this; + setShadowBlur(blur: number): this; + setShadowStroke(enabled?: boolean): this; + setShadowFill(enabled?: boolean): this; + + setAlign(align?: 'left' | 'center' | 'right'): this; + setHAlign(align?: 'left' | 'center' | 'right'): this; + setVAlign(align?: 'top' | 'center' | 'bottom'): this; + + addImage( + imgKey: string, + config?: { + key: string, + frame?: string, + width?: number, + height?: number, + y?: number, + left?: number, + right?: number, + } + ): this; + + drawAreaBounds( + graphics: Phaser.GameObjects.Graphics, + color?: number + ): this; + + setLineSpacing(value: number): this; + + setXOffset(value: number): this; + + setPadding( + left?: number | { + left?: number, right?: number, top?: number, bottom?: number + }, + top?: number, + right?: number, + bottom?: number, + ): this; + + setMaxLines(max?: number): this; + + measureTextMargins( + testString: string, + out?: Text.TextMarginsType + ): Text.TextMarginsType; + + setResolution(value: number): this; + + setFixedSize(width?: number, height?: number): this; + setSize(width?: number, height?: number): this; + resize(width?: number, height?: number): this; + + getTextMetrics(): Text.MetricsType; + + setTextMetrics( + metrics: Text.MetricsType, + font: Text.FontConfigType + ): this; + + generateTexture( + key: string, + x?: number, y?: number, + width?: number, height?: number + ): this; + + setUrlTagCursor(cursor?: string): this; + urlTagCursor: string; + + style: { + color: string | null, + stroke: string | null, + strokeThickness: number, + + underlineColor: string | null, + underlineThickness: number, + underlineOffset: number, + + backgroundColor: string | null, + backgroundColor2: string | null, + backgroundHorizontalGradient: boolean, + + backgroundStrokeColor: string | null, + backgroundStrokeLineWidth: number, + + backgroundCornerRadius: number, + backgroundCornerIteration: number | undefined, + + shadowColor: string | null, + shadowOffsetX: number, + shadowOffsetY: number, + shadowBlur: number, + shadowStroke: boolean, + shadowFill: boolean, + + lineSpacing: number, + maxLines: number, + + resolution: number, + + fixedWidth: number, + fixedHeight: number, + + halign: string, + valign: string, + + wrapWidth: number | null, + wrapMode: number + }; + + padding: { + left: number, + right: number, + top: number, + bottom: number + }; + + lineSpacing: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/Text.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/Text.js new file mode 100644 index 000000000..fec039bc6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/Text.js @@ -0,0 +1,508 @@ +import TextBase from '../../textbase/TextBase.js'; +import TextStyle from '../../textbase/textstyle/TextStyle.js'; +import CanvasText from './canvastext/CanvasText.js'; +import Pool from '../../../pool.js'; +import WrapTextLinesPoolClass from './wraptext/WrapTextLinesPool.js'; +import CONST from '../../textbase/const.js'; +import ImageManager from '../../../utils/texture/imagemanager/ImageManager.js'; +import CopyCanvasToTexture from '../../../utils/texture/CopyCanvasToTexture.js'; +import AppendText from '../../../utils/text/AppendText.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const AddToDOM = Phaser.DOM.AddToDOM; +const CanvasPool = Phaser.Display.Canvas.CanvasPool; +const GameObject = Phaser.GameObjects.GameObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const RemoveFromDOM = Phaser.DOM.RemoveFromDOM; +const SPLITREGEXP = CONST.SPLITREGEXP; + +// Reuse objects can increase performance +var SharedPensPools = null; +var SharedLinesPool = null; +var SharedWrapTextLinesPool = null; + +class Text extends TextBase { + constructor(scene, x, y, text, style, type, parser) { + if (IsPlainObject(x)) { + var config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + text = GetValue(config, 'text', ''); + style = GetValue(config, 'style'); + } + if (x === undefined) { + x = 0; + } + if (y === undefined) { + y = 0; + } + + super(scene, type); + + this.renderer = scene.sys.game.renderer; + + this.setPosition(x, y); + this.setOrigin(0, 0); + this.initPipeline(); + + this.canvas = CanvasPool.create(this); + + this.context = this.canvas.getContext('2d', { willReadFrequently: true }); + + this._imageManager = undefined; + + if (style) { + // Override align + if (style.hasOwnProperty('align')) { + var halign = style.align; + delete style.align; + style.halign = halign; + } + // Has Stroke color but stroke thinkness, set stroke thinkness to 1 + if (style.hasOwnProperty('stroke') && !style.hasOwnProperty('strokeThickness')) { + style.strokeThickness = 1; + } + } + this.style = new TextStyle(this, style); + + var imageData = GetValue(style, 'images', undefined); + if (imageData) { + this.addImage(imageData); + } + + this.autoRound = true; + + this._text = undefined; + + this.padding = { + left: 0, + right: 0, + top: 0, + bottom: 0 + }; + + this.width = 1; + + this.height = 1; + + this.dirty = false; + + // If resolution wasn't set, force it to 1 + if (this.style.resolution === 0) { + this.style.resolution = 1; + } + + this._crop = this.resetCropObject(); + + // Create a Texture for this Text object + this.texture = scene.sys.textures.addCanvas(null, this.canvas, true); + + // Get the frame + this.frame = this.texture.get(); + + // Set the resolution + this.frame.source.resolution = this.style.resolution; + + if (this.renderer && this.renderer.gl) { + // Clear the default 1x1 glTexture, as we override it later + this.renderer.deleteTexture(this.frame.source.glTexture); + this.frame.source.glTexture = null; + } + + var sharedPoolMode = GetValue(style, 'sharedPool', true); + + var pensPool, linesPool, wrapTextLinesPool; + if (sharedPoolMode) { + // Use pools first time + if (!SharedPensPools) { + SharedPensPools = {}; + SharedLinesPool = new Pool(); + SharedWrapTextLinesPool = new WrapTextLinesPoolClass(); + + // Remove cached data + this.scene.game.events.once('destroy', function () { + SharedPensPools = null; + SharedLinesPool = null; + SharedWrapTextLinesPool = null; + }); + } + if (!SharedPensPools.hasOwnProperty(type)) { + SharedPensPools[type] = new Pool(); + } + + pensPool = SharedPensPools[type]; + linesPool = SharedLinesPool; + wrapTextLinesPool = SharedWrapTextLinesPool; + } else { + pensPool = new Pool(); + linesPool = new Pool(); + wrapTextLinesPool = new WrapTextLinesPoolClass(); + } + + this.canvasText = new CanvasText({ + parent: this, + context: this.context, + parser: parser, + style: this.style, + pensPool: pensPool, + linesPool: linesPool, + wrapTextLinesPool: wrapTextLinesPool, + }); + this.parser = parser; + + this.initRTL(); + + if (style && style.padding) { + this.setPadding(style.padding); + } + + this.setText(text); + + this.setUrlTagCursorStyle(GetValue(style, 'urlTagCursorStyle', 'pointer')); + + if (GetValue(style, 'interactive', false)) { + this.setInteractive(); + } + } + + preDestroy() { + if (this.style.rtl) { + RemoveFromDOM(this.canvas); + } + + this.canvasText.destroy(); + this.canvasText = undefined; + + if (this._imageManager) { + this._imageManager.destroy(); + this._imageManager = undefined; + } + + CanvasPool.remove(this.canvas); + + this.texture.destroy(); + } + + set text(value) { + this.setText(value); + } + get text() { + return this._text; + } + + initRTL() { + if (!this.style.rtl) { + return; + } + + // Here is where the crazy starts. + // + // Due to browser implementation issues, you cannot fillText BiDi text to a canvas + // that is not part of the DOM. It just completely ignores the direction property. + + this.canvas.dir = 'rtl'; + + // Experimental atm, but one day ... + this.context.direction = 'rtl'; + + // Add it to the DOM, but hidden within the parent canvas. + this.canvas.style.display = 'none'; + + AddToDOM(this.canvas, this.scene.sys.canvas); + + // And finally we set the x origin + this.originX = 1; + } + + setText(value) { + if (value == null) { + value = ''; + } else if (Array.isArray(value)) { + value = value.join('\n'); + } else { + value = value.toString(); + } + + if (value === this._text) { + return this; + } + + this._text = value; + this.updateText(); + + return this; + } + + setPadding(left, top, right, bottom) { + if (typeof left === 'object') { + var config = left; + + // If they specify x and/or y this applies to all + var x = GetValue(config, 'x', null); + + if (x !== null) { + left = x; + right = x; + } else { + left = GetValue(config, 'left', 0); + right = GetValue(config, 'right', left); + } + + var y = GetValue(config, 'y', null); + + if (y !== null) { + top = y; + bottom = y; + } else { + top = GetValue(config, 'top', 0); + bottom = GetValue(config, 'bottom', top); + } + } else { + if (left === undefined) { + left = 0; + } + if (top === undefined) { + top = left; + } + if (right === undefined) { + right = left; + } + if (bottom === undefined) { + bottom = top; + } + } + + this.padding.left = left; + this.padding.top = top; + this.padding.right = right; + this.padding.bottom = bottom; + + return this.updateText(false); + } + + updateText(runWrap) { + if (runWrap === undefined) { + runWrap = true; + } + var canvasText = this.canvasText; + + // wrap text to pens + var style = this.style; + if (runWrap) { + canvasText.updatePenManager( + this._text, + style.wrapMode, + style.wrapWidth, + style.lineHeight + ); + } + + // resize + var padding = this.padding; + var textWidth, textHeight; + var linesWidth = Math.ceil(canvasText.linesWidth); + if (style.fixedWidth === 0) { + this.width = linesWidth + padding.left + padding.right; + textWidth = linesWidth; + } + else { + this.width = style.fixedWidth; + textWidth = this.width - padding.left - padding.right; + if (textWidth < linesWidth) { + textWidth = linesWidth; + } + } + if (style.fixedHeight === 0) { + this.height = canvasText.linesHeight + padding.top + padding.bottom; + textHeight = canvasText.linesHeight; + } + else { + this.height = style.fixedHeight; + textHeight = this.height - padding.top - padding.bottom; + if (textHeight < canvasText.linesHeight) { + textHeight = canvasText.linesHeight; + } + } + + var w = this.width; + var h = this.height; + + this.updateDisplayOrigin(); + + var resolution = style.resolution; + w *= resolution; + h *= resolution; + + w = Math.max(Math.ceil(w), 1); + h = Math.max(Math.ceil(h), 1); + + var canvas = this.canvas; + var context = this.context; + if (canvas.width !== w || canvas.height !== h) { + canvas.width = w; + canvas.height = h; + this.frame.setSize(w, h); + } else { + context.clearRect(0, 0, w, h); + } + + context.save(); + context.scale(resolution, resolution); + + // draw + var startX = (!this.style.rtl) ? padding.left : padding.right; + var startY = padding.top; + canvasText.draw( + startX, + startY, + textWidth, + textHeight, + ); + + context.restore(); + + if (this.renderer && this.renderer.gl) { + this.frame.source.glTexture = this.renderer.canvasToTexture(canvas, this.frame.source.glTexture, true); + this.frame.glTexture = this.frame.source.glTexture; + } + + this.dirty = true; + + var input = this.input; + + if (input && !input.customHitArea) { + input.hitArea.width = this.width; + input.hitArea.height = this.height; + } + + return this; + } + + toJSON() { + var out = Components.ToJSON(this); + + // Extra Text data is added here + + var data = { + autoRound: this.autoRound, + text: this._text, + style: this.style.toJSON(), + resolution: this.resolution, + padding: { + left: this.padding.left, + right: this.padding.right, + top: this.padding.top, + bottom: this.padding.bottom + } + }; + + out.data = data; + + return out; + } + + setInteractive(hitArea, hitAreaCallback, dropZone) { + var isInteractived = !!this.input; + + GameObject.prototype.setInteractive.call(this, hitArea, hitAreaCallback, dropZone); + + if (!isInteractived) { + this.canvasText.setInteractive(); + } + + return this; + } + + setUrlTagCursorStyle(cursor) { + this.urlTagCursorStyle = cursor; + return this; + } + + get urlTagCursorStyle() { + return this.canvasText.urlTagCursorStyle; + } + + set urlTagCursorStyle(value) { + this.canvasText.urlTagCursorStyle = value; + } + + getWrappedText(text, start, end) { + text = this.canvasText.getText(text, start, end, true); + return text.split(SPLITREGEXP); + } + + getPlainText(text, start, end) { + return this.canvasText.getPlainText(text, start, end); + } + + getText(text, start, end, wrap) { + if (wrap === undefined) { + wrap = false; + } + return this.canvasText.getText(text, start, end, wrap); + } + + getSubString(text, start, end) { + return this.getText(text, start, end); + } + + copyPenManager(penManager) { + return this.canvasText.copyPenManager(penManager); + } + + getPenManager(text, penManager) { + return this.canvasText.getPenManager(text, penManager); + } + + setSize(width, height) { + return this.setFixedSize(width, height); + } + + resize(width, height) { + return this.setFixedSize(width, height); + } + + get imageManager() { + if (!this._imageManager) { + this._imageManager = new ImageManager(this.scene); + } + return this._imageManager; + } + + addImage(key, config) { + this.imageManager.add(key, config); + return this; + } + + drawAreaBounds(graphics, color) { + this.canvasText.hitAreaManager.drawBounds(graphics, color, this); + return this; + } + + generateTexture(key, x, y, width, height) { + var srcCanvas = this.canvas; + if (width === undefined) { + width = srcCanvas.width; + } else { + width *= this.resolution; + } + if (height === undefined) { + height = srcCanvas.height; + } else { + height *= this.resolution; + } + + CopyCanvasToTexture(this.scene, srcCanvas, key, x, y, width, height); + return this; + } +} + +var methods = { + appendText: AppendText, +} + +Object.assign( + Text.prototype, + methods +) +export default Text; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/canvastext/CanvasText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/canvastext/CanvasText.js new file mode 100644 index 000000000..6b3b170f3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/canvastext/CanvasText.js @@ -0,0 +1,353 @@ +import DrawMethods from './DrawMethods.js'; +import PenManager from '../penmanger/PenManager.js'; +import HitAreaManager from '../hitareamanager/HitAreaManager.js'; +import SetInteractive from './SetInteractive.js'; +import CONST from '../../../textbase/const.js'; +import WrapText from '../wraptext/WrapText.js'; +import Clone from '../../../../utils/object/Clone.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const NO_WRAP = CONST.NO_WRAP; +const NO_NEWLINE = CONST.NO_NEWLINE; + +class CanvasText { + constructor(config) { + this.parent = config.parent; + this.scene = this.parent.scene; + this.context = GetValue(config, 'context', null); + this.canvas = this.context.canvas; + this.parser = GetValue(config, 'parser', null); + this.defaultStyle = GetValue(config, 'style', null); + this.autoRound = true; + + this.pensPool = config.pensPool; // Required + this.linesPool = config.linesPool; // Required + this.wrapTextLinesPool = config.wrapTextLinesPool; // Required + + this.penManager = this.newPenManager(); + this._tmpPenManager = null; + + this.hitAreaManager = new HitAreaManager(); + this.lastHitAreaKey = null; + this.urlTagCursorStyle = null; + + var context = this.context; + this.getTextWidth = function (text) { + return context.measureText(text).width; + } + } + + destroy() { + this.parent = undefined; + this.scene = undefined; + this.context = undefined; + this.canvas = undefined; + this.parser = undefined; + this.defaultStyle = undefined; + + if (this.penManager) { + this.penManager.destroy(); + this.penManager = undefined; + } + if (this._tmpPenManager) { + this._tmpPenManager.destroy(); + this._tmpPenManager = undefined; + } + if (this.hitAreaManager) { + this.hitAreaManager.destroy(); + this.hitAreaManager = undefined; + } + + this.pensPool = undefined; + this.linesPool = undefined; + this.wrapTextLinesPool = undefined; + } + + updatePenManager(text, wrapMode, wrapWidth, lineHeight, penManager) { + if (penManager === undefined) { + penManager = this.penManager; + } + penManager.clear(); + if (text === "") { + return penManager; + } + + var textStyle = this.parent.style; + if (textStyle.isWrapFitMode) { + var padding = this.parent.padding; + wrapWidth = textStyle.fixedWidth - padding.left - padding.right; + } + + var canvas = this.canvas; + var context = this.context; + + var MeasureText = function (text) { + return context.measureText(text).width; + } + + var cursorX = 0, + cursorY = 0; + + var customTextWrapCallback = textStyle.wrapCallback, + customTextWrapCallbackScope = textStyle.wrapCallbackScope; + var reuseLines = true; + + var plainText, curProp, curStyle; + var match = this.parser.splitText(text), + result, wrapLines, + wrapTextLinesPool = this.wrapTextLinesPool; + for (var i = 0, len = match.length; i < len; i++) { + result = this.parser.tagTextToProp(match[i], curProp); + plainText = result.plainText; + curProp = result.prop; + + if (curProp.img) { // Image tag + var imgWidth = this.imageManager.getOuterWidth(curProp.img); + if ((wrapWidth > 0) && (wrapMode !== NO_WRAP)) { // Wrap mode + if (wrapWidth < (cursorX + imgWidth)) { + penManager.addNewLinePen(); + cursorY += lineHeight; + cursorX = 0; + } + } + penManager.addImagePen(cursorX, cursorY, imgWidth, Clone(curProp)); + cursorX += imgWidth; + + } else if (plainText !== '') { + // wrap text to lines + // Save the current context. + context.save(); + curStyle = this.parser.propToContextStyle(this.defaultStyle, curProp); + curStyle.buildFont(); + curStyle.syncFont(canvas, context); + curStyle.syncStyle(canvas, context); + + if (!customTextWrapCallback) { + wrapLines = WrapText( + plainText, + MeasureText, + wrapMode, wrapWidth, + cursorX, + wrapTextLinesPool + ); + } else { // customTextWrapCallback + wrapLines = customTextWrapCallback.call(customTextWrapCallbackScope, + plainText, + MeasureText, + wrapWidth, + cursorX + ); + if (typeof (wrapLines) === 'string') { + wrapLines = wrapLines.split('\n'); + } + var n; + for (var j = 0, jLen = wrapLines.length; j < jLen; j++) { + n = wrapLines[j]; + if (typeof (n) === 'string') { + wrapLines[j] = wrapTextLinesPool.getLine( + n, + MeasureText(n), + (j < (jLen - 1)) ? 2 : 0 + ); + } else { + reuseLines = false; + } + } + } // customTextWrapCallback + + // add pens + var n; + for (var j = 0, jLen = wrapLines.length; j < jLen; j++) { + n = wrapLines[j]; + penManager.addTextPen(n.text, cursorX, cursorY, n.width, Clone(curProp), n.newLineMode); + + if (n.newLineMode !== NO_NEWLINE) { + cursorX = 0; + cursorY += lineHeight; + } else { + cursorX += n.width; + } + + } + + if (reuseLines) { + wrapTextLinesPool.freeLines(wrapLines); + } + wrapLines = null; + + context.restore(); + + } + + } + + // Add strokeThinkness to last pen of each line + for (var i = 0, len = this.lines.length; i < len; i++) { + var line = this.lines[i]; + var lastPen = line[line.length - 1]; + if (lastPen) { + lastPen.width += this.parser.getStrokeThinkness(this.defaultStyle, lastPen.prop); + } + } + + return penManager; + } + + get startXOffset() { + var defaultStyle = this.defaultStyle; + return (defaultStyle.strokeThickness / 2) + defaultStyle.xOffset; + } + + get startYOffset() { + var defaultStyle = this.defaultStyle; + return (defaultStyle.strokeThickness / 2) + defaultStyle.metrics.ascent; + } + + get lines() { + return this.penManager.lines; + } + + get desplayLinesCount() { + var linesCount = this.penManager.linesCount, + maxLines = this.defaultStyle.maxLines; + if ((maxLines > 0) && (linesCount > maxLines)) { + linesCount = maxLines; + } + return linesCount; + } + + get linesWidth() { + return Math.ceil(this.penManager.getMaxLineWidth()); + } + + get linesHeight() { + var linesCount = this.desplayLinesCount; + var linesHeight = (this.defaultStyle.lineHeight * linesCount); + if (linesCount > 0) { + linesHeight -= this.defaultStyle.lineSpacing; + } + return linesHeight; + } + + get imageManager() { + return this.parent.imageManager; + } + + get rtl() { + return this.parent.style.rtl; + } + + newPenManager() { + return new PenManager({ + pensPool: this.pensPool, + linesPool: this.linesPool, + tagToText: this.parser.propToTagText, + tagToTextScope: this.parser + }); + } + + get tmpPenManager() { + if (this._tmpPenManager === null) { + this._tmpPenManager = this.newPenManager(); + } + return this._tmpPenManager; + } + + getPlainText(text, start, end) { + var plainText; + if (text == null) { + plainText = this.penManager.plainText; + } else { + var m, match = this.parser.splitText(text, 1); // PLAINTEXTONLY_MODE + plainText = ""; + for (var i = 0, len = match.length; i < len; i++) { + plainText += match[i]; + } + } + + if ((start != null) || (end != null)) { + if (start == null) { + start = 0; + } + if (end == null) { + end = plainText.length; + } + plainText = plainText.substring(start, end); + } + + return plainText; + } + + getPenManager(text, retPenManager) { + if (text === undefined) { + return this.copyPenManager(retPenManager, this.penManager); + } + + if (retPenManager === undefined) { + retPenManager = this.newPenManager(); + } + + var defaultStyle = this.defaultStyle; + this.updatePenManager( + text, + defaultStyle.wrapMode, + defaultStyle.wrapWidth, + defaultStyle.lineHeight, + retPenManager + ); + return retPenManager; + } + + getText(text, start, end, wrap) { + if (text == null) { + return this.penManager.getSliceTagText(start, end, wrap); + } + + var penManager = this.tmpPenManager; + var defaultStyle = this.defaultStyle; + this.updatePenManager( + text, + defaultStyle.wrapMode, + defaultStyle.wrapWidth, + defaultStyle.lineHeight, + penManager + ); + + return penManager.getSliceTagText(start, end, wrap); + } + + copyPenManager(ret, src) { + if (src === undefined) { + src = this.penManager; + } + return src.copy(ret); + } + + getTextWidth(penManager) { + if (penManager === undefined) { + penManager = this.penManager; + } + + return penManager.getMaxLineWidth(); + } + + getLastPen(penManager) { + if (penManager === undefined) { + penManager = this.penManager; + } + + return penManager.lastPen; + } +}; + +var methods = { + setInteractive: SetInteractive, +} + +Object.assign( + CanvasText.prototype, + DrawMethods, + methods +); + +export default CanvasText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/canvastext/DrawMethods.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/canvastext/DrawMethods.js new file mode 100644 index 000000000..eeb86023e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/canvastext/DrawMethods.js @@ -0,0 +1,203 @@ +import DrawRoundRectangleBackground from '../../../canvas/utils/DrawRoundRectangleBackground.js'; + +export default { + draw(startX, startY, textWidth, textHeight) { + var penManager = this.penManager; + this.hitAreaManager.clear(); + + var context = this.context; + context.save(); + + var defaultStyle = this.defaultStyle; + + this.clear(); + DrawRoundRectangleBackground( + this, + defaultStyle.backgroundColor, + defaultStyle.backgroundStrokeColor, + defaultStyle.backgroundStrokeLineWidth, + defaultStyle.backgroundCornerRadius, + defaultStyle.backgroundColor2, + defaultStyle.backgroundHorizontalGradient, + defaultStyle.backgroundCornerIteration + ); + + // draw lines + startX += this.startXOffset; + startY += this.startYOffset; + var defaultHalign = defaultStyle.halign, + valign = defaultStyle.valign; + + var lineWidth, lineHeight = defaultStyle.lineHeight; + var lines = penManager.lines; + var totalLinesNum = lines.length, + maxLines = defaultStyle.maxLines; + var drawLinesNum, drawLineStartIdx, drawLineEndIdx; + if ((maxLines > 0) && (totalLinesNum > maxLines)) { + drawLinesNum = maxLines; + if (valign === 'center') { // center + drawLineStartIdx = Math.floor((totalLinesNum - drawLinesNum) / 2); + } else if (valign === 'bottom') { // bottom + drawLineStartIdx = totalLinesNum - drawLinesNum; + } else { + drawLineStartIdx = 0; + } + } else { + drawLinesNum = totalLinesNum; + drawLineStartIdx = 0; + } + drawLineEndIdx = drawLineStartIdx + drawLinesNum; + + var offsetX, offsetY; + var rtl = this.rtl, + rtlOffset = (rtl) ? this.parent.width : undefined; + if (valign === 'center') { // center + offsetY = Math.max((textHeight - (drawLinesNum * lineHeight)) / 2, 0); + } else if (valign === 'bottom') { // bottom + offsetY = Math.max(textHeight - (drawLinesNum * lineHeight) - 2, 0); + } else { + offsetY = 0; + } + offsetY += startY; + for (var lineIdx = drawLineStartIdx; lineIdx < drawLineEndIdx; lineIdx++) { + lineWidth = penManager.getLineWidth(lineIdx); + if (lineWidth === 0) { + continue; + } + + var pens = lines[lineIdx], + penCount = pens.length; + var halign = defaultHalign; + // Seek if there has algin tag + for (var penIdx = 0; penIdx < penCount; penIdx++) { + var penAlign = pens[penIdx].prop.align + if (penAlign !== undefined) { + halign = penAlign; + break; + } + } + + if (halign === 'center') { // center + offsetX = (textWidth - lineWidth) / 2; + } else if (halign === 'right') { // right + offsetX = (!rtl) ? (textWidth - lineWidth) : 0; + } else { + offsetX = (!rtl) ? 0 : (textWidth - lineWidth); + } + offsetX += startX; + + for (var penIdx = 0; penIdx < penCount; penIdx++) { + this.drawPen(pens[penIdx], offsetX, offsetY, rtlOffset); + } + } + + context.restore(); + }, + + drawPen(pen, offsetX, offsetY, rtlOffset) { + offsetX += pen.x; + offsetY += pen.y + (pen.prop.y || 0); + + if (rtlOffset !== undefined) { + offsetX = rtlOffset - offsetX; + } + + var canvas = this.canvas; + var context = this.context; + context.save(); + + var curStyle = this.parser.propToContextStyle(this.defaultStyle, pen.prop); + curStyle.buildFont(); + curStyle.syncFont(canvas, context); + curStyle.syncStyle(canvas, context); + + // Underline + if ((curStyle.underlineThickness > 0) && (pen.width > 0)) { + this.drawUnderline(offsetX, offsetY, pen.width, curStyle); + } + + // Text + if (pen.isTextPen) { + this.drawText(offsetX, offsetY, pen.text, curStyle); + } + + // Image + if (pen.isImagePen) { + this.drawImage(offsetX, offsetY, pen.prop.img, curStyle); + } + + context.restore(); + + if (pen.hasAreaMarker && (pen.width > 0)) { + var data; + var areaKey = pen.prop.area; + if (areaKey) { + data = { + key: areaKey + }; + } else { + var url = pen.prop.url; + data = { + key: `url:${url}`, + url: url + }; + } + + this.hitAreaManager.add( + offsetX, // x + (offsetY - this.startYOffset), // y + pen.width, // width + this.defaultStyle.lineHeight, // height + data + ); + } + }, + + clear() { + var canvas = this.canvas; + this.context.clearRect(0, 0, canvas.width, canvas.height); + }, + + drawUnderline(x, y, width, style) { + y += style.underlineOffset - (style.underlineThickness / 2); + if (this.autoRound) { + x = Math.round(x); + y = Math.round(y); + } + + var context = this.context; + var savedLineCap = context.lineCap; + context.lineCap = 'butt'; + context.strokeStyle = style.underlineColor; + context.lineWidth = style.underlineThickness; + context.beginPath(); + context.moveTo(x, y); + context.lineTo((x + width), y); + context.stroke(); + context.lineCap = savedLineCap; + }, + + drawText(x, y, text, style) { + if (this.autoRound) { + x = Math.round(x); + y = Math.round(y); + } + + var context = this.context; + if (style.stroke && (style.stroke !== 'none') && (style.strokeThickness > 0)) { + style.syncShadow(context, style.shadowStroke); + context.strokeText(text, x, y); + } + + if (style.color && (style.color !== 'none')) { + style.syncShadow(context, style.shadowFill); + context.fillText(text, x, y); + } + }, + + drawImage(x, y, imgKey, style) { + y -= this.startYOffset; + this.parent.imageManager.draw(imgKey, this.context, x, y, this.autoRound); + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/canvastext/SetInteractive.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/canvastext/SetInteractive.js new file mode 100644 index 000000000..e6251492f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/canvastext/SetInteractive.js @@ -0,0 +1,91 @@ +var SetInteractive = function () { + this.parent + + .on('pointerdown', OnAreaDown, this) + + .on('pointerup', OnAreaUp, this) + + .on('pointermove', OnAreaOverOut, this) + .on('pointerover', OnAreaOverOut, this) + .on('pointerout', function (pointer, event) { + OnAreaOverOut.call(this, pointer, null, null, event); + }, this) +} + +var OnAreaDown = function (pointer, localX, localY, event) { + var area = this.hitAreaManager.getFirst(localX, localY); + if (area === null) { + return; + } + + var key = area.data.key; + FireEvent.call(this, 'areadown', key, pointer, localX, localY, event); + + area.data.isDown = true; +} + +var OnAreaUp = function (pointer, localX, localY, event) { + var area = this.hitAreaManager.getFirst(localX, localY); + if (area === null) { + return; + } + + var areaData = area.data; + + var key = areaData.key; + FireEvent.call(this, 'areaup', key, pointer, localX, localY, event); + + if (areaData.isDown) { + FireEvent.call(this, 'areaclick', key, pointer, localX, localY, event); + + var url = areaData.url; + if (url) { + window.open(url, '_blank'); + } + } + + areaData.isDown = false; +} + +var OnAreaOverOut = function (pointer, localX, localY, event) { + if (localX === null) { // Case of pointerout + if (this.lastHitAreaKey !== null) { + FireEvent.call(this, 'areaout', this.lastHitAreaKey, pointer, localX, localY, event); + this.hitAreaManager.getByKey(this.lastHitAreaKey).isDown = false; + this.lastHitAreaKey = null; + } + return; + } + + var area = this.hitAreaManager.getFirst(localX, localY); + var key = (area) ? area.data.key : null; + if (this.lastHitAreaKey === key) { + return; + } + + if (this.lastHitAreaKey !== null) { + FireEvent.call(this, 'areaout', this.lastHitAreaKey, pointer, localX, localY, event); + + var prevHitArea = this.hitAreaManager.getByKey(this.lastHitAreaKey); + if (this.urlTagCursorStyle && !!prevHitArea.data.url) { + this.scene.input.manager.canvas.style.cursor = ''; + } + + prevHitArea.isDown = false; + } + if (key !== null) { + FireEvent.call(this, 'areaover', key, pointer, localX, localY, event); + + if (this.urlTagCursorStyle && !!area.data.url) { + this.scene.input.manager.canvas.style.cursor = this.urlTagCursorStyle; + } + } + + this.lastHitAreaKey = key; +} + +var FireEvent = function (eventName, key, pointer, localX, localY, event) { + this.parent.emit(`${eventName}-${key}`, pointer, localX, localY, event); + this.parent.emit(eventName, key, pointer, localX, localY, event); +} +export default SetInteractive; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/hitareamanager/HitAreaManager.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/hitareamanager/HitAreaManager.js new file mode 100644 index 000000000..27fd0ae56 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/hitareamanager/HitAreaManager.js @@ -0,0 +1,86 @@ +import Pool from '../../../../pool.js'; +import Clear from '../../../../utils/object/Clear.js'; + +const Rectangle = Phaser.Geom.Rectangle; + +var RectanglePool = new Pool(); +class HitAreaManager { + constructor() { + this.hitAreas = []; + } + + destroy() { + this.clear(); + } + + clear() { + // Reuse hitArea(rectangle) later + for (var i = 0, cnt = this.hitAreas.length; i < cnt; i++) { + Clear(this.hitAreas[i].data); + } + RectanglePool.pushMultiple(this.hitAreas); + return this; + } + + add(x, y, width, height, data) { + var rectangle = RectanglePool.pop(); + if (rectangle === null) { + rectangle = new Rectangle(x, y, width, height); + } else { + rectangle.setTo(x, y, width, height); + } + + rectangle.data = data; + + this.hitAreas.push(rectangle); + return this; + } + + getFirst(x, y) { + for (var i = 0, cnt = this.hitAreas.length; i < cnt; i++) { + var hitArea = this.hitAreas[i]; + if (hitArea.contains(x, y)) { + return hitArea; + } + } + return null; + } + + getByKey(key) { + for (var i = 0, cnt = this.hitAreas.length; i < cnt; i++) { + var hitArea = this.hitAreas[i]; + if (hitArea.data.key === key) { + return hitArea; + } + } + return null; + } + + drawBounds(graphics, color, parent) { + if (color === undefined) { + color = 0xffffff; + } + + if (parent) { + graphics + .save() + .scaleCanvas(parent.scaleX, parent.scaleY) + .rotateCanvas(parent.rotation) + .translateCanvas(parent.x, parent.y) + } + + for (var i = 0, cnt = this.hitAreas.length; i < cnt; i++) { + var hitArea = this.hitAreas[i]; + graphics + .lineStyle(1, color) + .strokeRect(hitArea.x, hitArea.y, hitArea.width, hitArea.height); + } + + if (parent) { + graphics + .restore() + } + return this; + } +} +export default HitAreaManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/penmanger/Pen.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/penmanger/Pen.js new file mode 100644 index 000000000..38db705df --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/penmanger/Pen.js @@ -0,0 +1,75 @@ +import CONST from '../../../textbase/const.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const NO_NEWLINE = CONST.NO_NEWLINE; +const RAW_NEWLINE = CONST.RAW_NEWLINE; + +class Pen { + constructor(config) { + this.prop = {}; + this.resetFromJSON(config); + } + + resetFromJSON(o) { // (txt, x, y, width, prop, newLineMode, startIndex) + this.text = GetValue(o, 'text', ''); + this.x = GetValue(o, 'x', 0); + this.y = GetValue(o, 'y', 0); + this.width = GetValue(o, 'width', 0); + + var prop = GetValue(o, 'prop', null); + if (prop === null) { + prop = {}; + } + this.prop = prop; + this.newLineMode = GetValue(o, 'newLineMode', 0); + this.startIndex = GetValue(o, 'startIndex', 0); + } + + get plainText() { + var txt = this.text + if (this.newLineMode === RAW_NEWLINE) { + txt += "\n"; + } + + return txt; + } + + get wrapText() { + var txt = this.text; + if (this.newLineMode !== NO_NEWLINE) { + txt += "\n"; + } + + return txt; + } + + get rawTextLength() { + var len = this.text.length; + if (this.newLineMode === RAW_NEWLINE) { + len += 1; + } + return len; + } + + get endIndex() { + return this.startIndex + this.rawTextLength; + } + + get lastX() { + return this.x + this.width; + } + + get isTextPen() { + return (this.text !== ''); + } + + get isImagePen() { + return !!this.prop.img; + } + + get hasAreaMarker() { + return !!this.prop.area || !!this.prop.url; + } +}; + +export default Pen; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/penmanger/PenManager.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/penmanger/PenManager.js new file mode 100644 index 000000000..23a445fdc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/penmanger/PenManager.js @@ -0,0 +1,280 @@ +import Pen from './Pen.js'; +import CONST from '../../../textbase/const.js'; +import Clone from '../../../../utils/object/Clone.js'; +import NOOP from '../../../../utils/object/NOOP.js'; + +const GetFastValue = Phaser.Utils.Objects.GetFastValue; +const NO_NEWLINE = CONST.NO_NEWLINE; +const WRAPPED_NEWLINE = CONST.WRAPPED_NEWLINE; + +class PenManager { + constructor(config) { + this.pens = []; // all pens + this.lines = []; // pens in lines [ [],[],[],.. ] + this.maxLinesWidth = undefined; + + this.pensPool = config.pensPool; // Required + this.linesPool = config.linesPool; // Required + this.tagToText = GetFastValue(config, 'tagToText', NOOP); + this.tagToTextScope = GetFastValue(config, 'tagToTextScope', undefined); + } + + destroy() { + this.clear(); + this.tagToText = undefined; + this.tagToTextScope = undefined; + } + + clear() { + for (var i = 0, len = this.lines.length; i < len; i++) { + this.lines[i].length = 0; + } + + this.pensPool.pushMultiple(this.pens); + this.linesPool.pushMultiple(this.lines); + this.maxLinesWidth = undefined; + } + + addTextPen(text, x, y, width, prop, newLineMode) { + var pen = this.pensPool.pop(); + if (pen == null) { + pen = new Pen(); + } + PEN_CONFIG.text = text; + PEN_CONFIG.x = x; + PEN_CONFIG.y = y; + PEN_CONFIG.width = width; + PEN_CONFIG.prop = prop; + PEN_CONFIG.newLineMode = newLineMode; + pen.resetFromJSON(PEN_CONFIG); + this.addPen(pen); + return this; + } + + addImagePen(x, y, width, prop) { + this.addTextPen('', x, y, width, prop, NO_NEWLINE); + return this; + } + + addNewLinePen() { + var previousPen = this.lastPen; + var x = (previousPen) ? previousPen.lastX : 0; + var y = (previousPen) ? previousPen.y : 0; + var prop = (previousPen) ? Clone(previousPen.prop) : null; + this.addTextPen('', x, y, 0, prop, WRAPPED_NEWLINE); + return this; + } + + addPen(pen) { + var previousPen = this.lastPen; + if (previousPen == null) { + pen.startIndex = 0; + } else { + pen.startIndex = previousPen.endIndex; + } + this.pens.push(pen); + + // maintan lines + var line = this.lastLine; + if (line == null) { + line = this.linesPool.pop() || []; + this.lines.push(line); + } + line.push(pen); + + // new line, add an empty line + if (pen.newLineMode !== NO_NEWLINE) { + line = this.linesPool.pop() || []; + this.lines.push(line); + } + this.maxLinesWidth = undefined; + } + + clone(targetPenManager) { + if (targetPenManager == null) + targetPenManager = new PenManager(); + + targetPenManager.clear(); + + for (var li = 0, llen = this.lines.length; li < llen; li++) { + var pens = this.lines[li]; + for (var pi = 0, plen = pens.length; pi < plen; pi++) { + var pen = pens[pi]; + targetPenManager.addPen( + pen.text, + pen.x, + pen.y, + pen.width, + Clone(pen.prop), + pen.newLineMode + ); + } + } + + return targetPenManager; + } + + get lastPen() { + return this.pens[this.pens.length - 1]; + } + + get lastLine() { + return this.lines[this.lines.length - 1]; + } + + getLineStartIndex(i) { + if (i >= this.lines.length) { + return this.getLineEndIndex(i); + } else { + var line = this.lines[i]; + return (line && line[0]) ? line[0].startIndex : 0; + } + } + + getLineEndIndex(i) { + if (i >= this.lines.length) { + i = this.lines.length - 1; + } + var li, hasLastPen = false, + line; + for (li = i; li >= 0; li--) { + line = this.lines[li]; + hasLastPen = (line != null) && (line.length > 0); + if (hasLastPen) { + break; + } + } + if (!hasLastPen) { + return 0; + } + + var lastPen = line[line.length - 1]; + return lastPen.endIndex; + } + + getLineWidth(i) { + var line = this.lines[i]; + if (!line) { + return 0; + } + + var lastPen = line[line.length - 1]; + if (lastPen == null) { + return 0; + } + + var lineWidth = lastPen.lastX; // start from 0 + return lineWidth; + } + + getMaxLineWidth() { + if (this.maxLinesWidth !== undefined) { + return this.maxLinesWidth; + } + var w, maxW = 0; + for (var i = 0, len = this.lines.length; i < len; i++) { + w = this.getLineWidth(i); + if (w > maxW) { + maxW = w; + } + } + this.maxLinesWidth = maxW; + return maxW; + } + + getLineWidths() { + var result = []; + for (var i = 0, len = this.lines.length; i < len; i++) { + result.push(this.getLineWidth(i)); + } + return result; + } + + get linesCount() { + return this.lines.length; + } + + get plainText() { + var txt = "", + pens = this.pens; + for (var i = 0, len = pens.length; i < len; i++) { + txt += pens[i].plainText; + } + + return txt; + } + + get rawTextLength() { + var l = 0, + pens = this.pens; + for (var i = 0, len = this.pens.length; i < len; i++) { + l += pens[i].rawTextLength; + } + + return l; + } + + getSliceTagText(start, end, wrap) { + if (start === undefined) { + start = 0; + } + if (end === undefined) { + var lastPen = this.lastPen; + if (lastPen == null) { + return ""; + } + + end = lastPen.endIndex; + } + if (wrap === undefined) { + wrap = false; + } + + var txt = "", + formatTxt, + pen, penTxt, penStartIdx, penEndIdx, isInRange; + var currentProp, previousProp; + for (var i = 0, len = this.pens.length; i < len; i++) { + pen = this.pens[i]; + penEndIdx = pen.endIndex; + if (penEndIdx <= start) { + continue; + } + pen = this.pens[i]; + penTxt = (!wrap) ? pen.plainText : pen.wrapText; + currentProp = pen.prop; + penStartIdx = pen.startIndex; + + isInRange = (penStartIdx >= start) && (penEndIdx <= end); + if (!isInRange) { + penTxt = penTxt.substring(start - penStartIdx, end - penStartIdx); + } + + if (this.tagToTextScope) { + txt += this.tagToText.call(this.tagToTextScope, penTxt, currentProp, previousProp); + } else { + txt += this.tagToText(penTxt, currentProp, previousProp); + } + + previousProp = currentProp; + if (penEndIdx >= end) { + break; + } + } + + return txt; + } + + get length() { + return this.lines.length; + } + + set length(value) { + // Only for set length to 0 (clear) + this.clear(); + } +}; + +var PEN_CONFIG = {}; + +export default PenManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/wraptext/WrapText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/wraptext/WrapText.js new file mode 100644 index 000000000..45939c9a2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/wraptext/WrapText.js @@ -0,0 +1,126 @@ +import CONST from '../../../textbase/const.js'; + +const NO_NEWLINE = CONST.NO_NEWLINE; +const RAW_NEWLINE = CONST.RAW_NEWLINE; +const WRAPPED_NEWLINE = CONST.WRAPPED_NEWLINE; +const NO_WRAP = CONST.NO_WRAP; +const WORD_WRAP = CONST.WORD_WRAP; +const CHAR_WRAP = CONST.CHAR_WRAP; +const splitRegExp = CONST.SPLITREGEXP; + +var WrapText = function (text, getTextWidth, wrapMode, wrapWidth, offset, wrapTextLinesPool) { + if (wrapWidth <= 0) { + wrapMode = NO_WRAP; + } + + var retLines = []; + if (!text || !text.length) { + return retLines; + } + + var isNoWrap = (wrapMode === NO_WRAP); + var isWordWrap = (wrapMode === WORD_WRAP); + + var lines = text.split(splitRegExp), + line, remainWidth, newLineMode; + for (var i = 0, linesLen = lines.length; i < linesLen; i++) { + line = lines[i]; + newLineMode = (i === (linesLen - 1)) ? NO_NEWLINE : RAW_NEWLINE; + + if (isNoWrap) { + var textWidth = getTextWidth(line); + retLines.push(wrapTextLinesPool.getLine(line, textWidth, newLineMode)); + continue; + } + + remainWidth = (i === 0) ? (wrapWidth - offset) : wrapWidth; + + // short string testing + if (line.length <= 100) { + var textWidth = getTextWidth(line); + if (textWidth <= remainWidth) { + retLines.push(wrapTextLinesPool.getLine(line, textWidth, newLineMode)); + continue; + } + } + + var tokenArray, isSpaceCharacterEnd; + if (isWordWrap) { + // word mode + tokenArray = line.split(' '); + isSpaceCharacterEnd = (tokenArray[tokenArray.length - 1] === ''); + if (isSpaceCharacterEnd) { + tokenArray.length -= 1; + } + } else { + tokenArray = line; + } + var token, tokenWidth, isLastToken; + var lineText = '', lineWidth = 0; + var currLineWidth; + var whiteSpaceWidth = (isWordWrap) ? getTextWidth(' ') : undefined; + for (var j = 0, tokenLen = tokenArray.length; j < tokenLen; j++) { + token = tokenArray[j]; + tokenWidth = getTextWidth(token); + + isLastToken = (j === (tokenLen - 1)); + if (isWordWrap && (!isLastToken || isSpaceCharacterEnd)) { + token += ' '; + tokenWidth += whiteSpaceWidth; + } + + // Text width of single token is larger than a line width + if (isWordWrap && (tokenWidth > wrapWidth)) { + if (lineText !== '') { + // Has pending lineText, flush it out + retLines.push(wrapTextLinesPool.getLine(lineText, lineWidth, WRAPPED_NEWLINE)); + + } else if ((j === 0) && (offset > 0)) { + // No pending lineText, but has previous text. Append a newline + retLines.push(wrapTextLinesPool.getLine('', 0, WRAPPED_NEWLINE)); + + } + + // Word break + retLines.push(...WrapText(token, getTextWidth, CHAR_WRAP, wrapWidth, 0, wrapTextLinesPool)); + // Continue at last-wordBreak-line + var lastwordBreakLine = retLines.pop(); + lineText = lastwordBreakLine.text; + lineWidth = lastwordBreakLine.width; + // Free this line + wrapTextLinesPool.freeLine(lastwordBreakLine); + + // Special case : Start at a space character, discard it + if (lineText === ' ') { + lineText = ''; + lineWidth = 0; + } + continue; + } + + currLineWidth = lineWidth + tokenWidth; + if (currLineWidth > remainWidth) { + // New line + retLines.push(wrapTextLinesPool.getLine(lineText, lineWidth, WRAPPED_NEWLINE)); + lineText = token; + lineWidth = tokenWidth; + remainWidth = wrapWidth; + + } else { + // Append token, continue + lineText += token; + lineWidth = currLineWidth; + } + + if (isLastToken) { + // Flush remain text + retLines.push(wrapTextLinesPool.getLine(lineText, lineWidth, newLineMode)); + } + } // for token in tokenArray + + } // for each line in lines + + return retLines; +}; + +export default WrapText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/wraptext/WrapTextLinesPool.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/wraptext/WrapTextLinesPool.js new file mode 100644 index 000000000..e4015f52d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/tagtext/textbase/wraptext/WrapTextLinesPool.js @@ -0,0 +1,34 @@ +import Pool from '../../../../pool.js'; + +class WrapTextLinesPool extends Pool { + freeLine(line) { + if (!line) { + return; + } + this.push(line); + return this; + } + + freeLines(lines) { + if (!lines) { + return; + } + this.pushMultiple(lines); + return this; + } + + getLine(text, width, newLineMode) { + var l = this.pop(); + if (l === null) { + l = {}; + } + l.text = text; + l.width = width; + l.newLineMode = newLineMode; + return l; + } + +} + + +export default WrapTextLinesPool; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/TextBase.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/TextBase.js new file mode 100644 index 000000000..ef23d049e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/TextBase.js @@ -0,0 +1,166 @@ +import Render from './render/Render.js'; +import MeasureTextMargins from './textstyle/MeasureTextMargins.js'; + +const GameObject = Phaser.GameObjects.GameObject; + +class TextBase extends GameObject { + + setStyle(style) { + return this.style.setStyle(style); + } + + setFont(font) { + return this.style.setFont(font); + } + + setFontFamily(family) { + return this.style.setFontFamily(family); + } + + setFontSize(size) { + return this.style.setFontSize(size); + } + + setFontStyle(style) { + return this.style.setFontStyle(style); + } + + setTestString(string) { + return this.style.setTestString(string); + } + + setFixedSize(width, height) { + return this.style.setFixedSize(width, height); + } + + setBackgroundColor(color, color2, isHorizontalGradient) { + return this.style.setBackgroundColor(color, color2, isHorizontalGradient); + } + + setBackgroundStrokeColor(color, lineWidth) { + return this.style.setBackgroundStrokeColor(color, lineWidth); + } + + setBackgroundCornerRadius(radius, iteration) { + return this.style.setBackgroundCornerRadius(radius, iteration); + } + + setFill(color) { + return this.style.setFill(color); + } + + setColor(color) { + return this.style.setColor(color); + } + + setStroke(color, thickness) { + return this.style.setStroke(color, thickness); + } + + setShadow(x, y, color, blur, shadowStroke, shadowFill) { + return this.style.setShadow(x, y, color, blur, shadowStroke, shadowFill); + } + + setShadowOffset(x, y) { + return this.style.setShadowOffset(x, y); + } + + setShadowColor(color) { + return this.style.setShadowColor(color); + } + + setShadowBlur(blur) { + return this.style.setShadowBlur(blur); + } + + setShadowStroke(enabled) { + return this.style.setShadowStroke(enabled); + } + + setShadowFill(enabled) { + return this.style.setShadowFill(enabled); + } + + setWrapMode(mode) { + return this.style.setWrapMode(mode); + } + + setWrapWidth(width) { + return this.style.setWrapWidth(width); + } + + // Align with built-in text game object + setWordWrapWidth(width) { + return this.style.setWrapWidth(width); + } + + setAlign(align) { + return this.style.setHAlign(align); + } + setHAlign(align) { + return this.style.setHAlign(align); + } + setVAlign(align) { + return this.style.setVAlign(align); + } + + setLineSpacing(value) { + return this.style.setLineSpacing(value); + } + + set lineSpacing(value) { + this.setLineSpacing(value); + } + + get lineSpacing() { + return this.style.lineSpacing; + } + + setXOffset(value) { + return this.style.setXOffset(value); + } + + setMaxLines(max) { + return this.style.setMaxLines(max); + } + + setResolution(value) { + return this.style.setResolution(value); + } + + getTextMetrics() { + return this.style.getTextMetrics(); + } + + setTextMetrics(metrics, font) { + return this.style.setTextMetrics(metrics, font); + } + + measureTextMargins(testString, out) { + return MeasureTextMargins(this.style, testString, out); + } + +} + +const Components = Phaser.GameObjects.Components; +Phaser.Class.mixin(TextBase, + [ + Components.Alpha, + Components.BlendMode, + Components.ComputedSize, + Components.Crop, + Components.Depth, + Components.Flip, + Components.GetBounds, + Components.Mask, + Components.Origin, + Components.Pipeline, + Components.PostPipeline, + Components.ScrollFactor, + Components.Tint, + Components.Transform, + Components.Visible, + Render + ] +); +export default TextBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/const.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/const.js new file mode 100644 index 000000000..81d1bda45 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/const.js @@ -0,0 +1,16 @@ +var CONST = { + // new line mode + NO_NEWLINE: 0, + RAW_NEWLINE: 1, + WRAPPED_NEWLINE: 2, + + // wrap mode + NO_WRAP: 0, + WORD_WRAP: 1, + CHAR_WRAP: 2, + + // split lines + SPLITREGEXP: /(?:\r\n|\r|\n)/ +}; + +export default CONST; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/render/CanvasRenderer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/render/CanvasRenderer.js new file mode 100644 index 000000000..6a26261a9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/render/CanvasRenderer.js @@ -0,0 +1,32 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Renders this Game Object with the Canvas Renderer to the given Camera. + * The object will not render if any of its renderFlags are set or it is being actively filtered out by the Camera. + * This method should not be called directly. It is a utility function of the Render module. + * + * @method Phaser.GameObjects.Text#renderCanvas + * @since 3.0.0 + * @private + * + * @param {Phaser.Renderer.Canvas.CanvasRenderer} renderer - A reference to the current active Canvas renderer. + * @param {Phaser.GameObjects.Text} src - The Game Object being rendered in this call. + * @param {number} interpolationPercentage - Reserved for future use and custom pipelines. + * @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera that is rendering the Game Object. + * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - This transform matrix is defined if the game object is nested + */ +var CanvasRenderer = function (renderer, src, camera, parentMatrix) { + if ((src.width === 0) || (src.height === 0)) { + return; + } + + camera.addToRenderList(src); + + renderer.batchSprite(src, src.frame, camera, parentMatrix); +}; + +export default CanvasRenderer; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/render/Render.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/render/Render.js new file mode 100644 index 000000000..404fc7750 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/render/Render.js @@ -0,0 +1,8 @@ +import WebGLRenderer from './WebGLRenderer.js'; +import CanvasRenderer from './CanvasRenderer.js'; + +export default { + renderWebGL: WebGLRenderer, + renderCanvas: CanvasRenderer + +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/render/WebGLRenderer.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/render/WebGLRenderer.js new file mode 100644 index 000000000..0acc9ce46 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/render/WebGLRenderer.js @@ -0,0 +1,67 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +var Utils = Phaser.Renderer.WebGL.Utils; + +/** + * Renders this Game Object with the WebGL Renderer to the given Camera. + * The object will not render if any of its renderFlags are set or it is being actively filtered out by the Camera. + * This method should not be called directly. It is a utility function of the Render module. + * + * @method Phaser.GameObjects.Text#renderWebGL + * @since 3.0.0 + * @private + * + * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - A reference to the current active WebGL renderer. + * @param {Phaser.GameObjects.Text} src - The Game Object being rendered in this call. + * @param {number} interpolationPercentage - Reserved for future use and custom pipelines. + * @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera that is rendering the Game Object. + * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - This transform matrix is defined if the game object is nested + */ +var WebGLRenderer = function (renderer, src, camera, parentMatrix) { + if ((src.width === 0) || (src.height === 0)) { + return; + } + + camera.addToRenderList(src); + + var frame = src.frame; + var width = frame.width; + var height = frame.height; + var getTint = Utils.getTintAppendFloatAlpha; + var pipeline = renderer.pipelines.set(src.pipeline, src); + var textureUnit = pipeline.setTexture2D(frame.glTexture, src); + + renderer.pipelines.preBatch(src); + + pipeline.batchTexture( + src, + frame.glTexture, + width, height, + src.x, src.y, + width / src.style.resolution, height / src.style.resolution, + src.scaleX, src.scaleY, + src.rotation, + src.flipX, src.flipY, + src.scrollFactorX, src.scrollFactorY, + src.displayOriginX, src.displayOriginY, + 0, 0, width, height, + getTint(src.tintTopLeft, camera.alpha * src._alphaTL), + getTint(src.tintTopRight, camera.alpha * src._alphaTR), + getTint(src.tintBottomLeft, camera.alpha * src._alphaBL), + getTint(src.tintBottomRight, camera.alpha * src._alphaBR), + src.tintFill, + 0, 0, + camera, + parentMatrix, + false, + textureUnit + ); + + renderer.pipelines.postBatch(src); +}; + +export default WebGLRenderer; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/MeasureText.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/MeasureText.js new file mode 100644 index 000000000..d29d6b4c6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/MeasureText.js @@ -0,0 +1,134 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +const CanvasPool = Phaser.Display.Canvas.CanvasPool; + +/** + * Calculates the ascent, descent and fontSize of a given font style. + * + * @function Phaser.GameObjects.MeasureText + * @since 3.0.0 + * + * @param {Phaser.GameObjects.Text.TextStyle} textStyle - The TextStyle object to measure. + * + * @return {object} An object containing the ascent, descent and fontSize of the TextStyle. + */ +var MeasureText = function (textStyle) { + // @property {HTMLCanvasElement} canvas - The canvas element that the text is rendered. + var canvas = CanvasPool.create(this); + + // @property {HTMLCanvasElement} context - The context of the canvas element that the text is rendered to. + var context = canvas.getContext('2d', { willReadFrequently: true }); + + textStyle.syncFont(canvas, context); + + var metrics = context.measureText(textStyle.testString); + + if ('actualBoundingBoxAscent' in metrics) { + var ascent = metrics.actualBoundingBoxAscent; + var descent = metrics.actualBoundingBoxDescent; + + var output = { + ascent: ascent, + descent: descent, + fontSize: (ascent + descent) + }; + + CanvasPool.remove(canvas); + + return output; + } + + var width = Math.ceil(metrics.width * textStyle.baselineX); + var baseline = width; + var height = 2 * baseline; + + baseline = baseline * textStyle.baselineY | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = textStyle._font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText(textStyle.testString, 0, baseline); + + var output = { + ascent: 0, + descent: 0, + fontSize: 0 + }; + + if (!context.getImageData(0, 0, width, height)) { + output.ascent = baseline; + output.descent = baseline + 6; + output.fontSize = output.ascent + output.descent; + + CanvasPool.remove(canvas); + + return output; + } + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + var i; + var j; + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) { + for (j = 0; j < line; j += 4) { + if (imagedata[idx + j] !== 255) { + stop = true; + break; + } + } + + if (!stop) { + idx += line; + } + else { + break; + } + } + + output.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) { + for (j = 0; j < line; j += 4) { + if (imagedata[idx + j] !== 255) { + stop = true; + break; + } + } + + if (!stop) { + idx -= line; + } + else { + break; + } + } + + output.descent = (i - baseline); + output.fontSize = output.ascent + output.descent; + + CanvasPool.remove(canvas); + + return output; +}; + +export default MeasureText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/MeasureTextMargins.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/MeasureTextMargins.js new file mode 100644 index 000000000..df04eedbf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/MeasureTextMargins.js @@ -0,0 +1,60 @@ +const CanvasPool = Phaser.Display.Canvas.CanvasPool; + +var MeasureTextMargins = function (textStyle, testString, out) { + if (out === undefined) { + out = {}; + } + + var canvas = CanvasPool.create(this); + var context = canvas.getContext('2d', { willReadFrequently: true }); + + textStyle.syncFont(canvas, context); + + var metrics = context.measureText(testString); + + var width = Math.ceil(metrics.width * textStyle.baselineX); + var baseline = width; + var height = 2 * baseline; + + baseline = baseline * textStyle.baselineY | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = textStyle._font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText(textStyle.testString, 0, baseline); + + out.left = 0; + + if ((width === 0) || (height === 0) || !context.getImageData(0, 0, width, height)) { + CanvasPool.remove(canvas); + return out; + } + + var imagedata = context.getImageData(0, 0, width, height).data; + var stop = false; + for (var x = 0; x < width; x++) { + for (var y = 0; y < height; y++) { + var idx = (y * width + x) * 4; + if (imagedata[idx] !== 255) { + out.left = x; + stop = true; + break; + } + } + if (stop) { + break; + } + } + + CanvasPool.remove(canvas); + return out; +} + +export default MeasureTextMargins; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/PropertyMap.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/PropertyMap.js new file mode 100644 index 000000000..2128e905b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/PropertyMap.js @@ -0,0 +1,58 @@ +import GetStyle from '../../../utils/canvas/GetStyle.js'; + +// Key: [ Object Key, Default Value, postCallback ] + +var PropertyMap = { + // background + backgroundColor: ['backgroundColor', null, GetStyle], + backgroundColor2: ['backgroundColor2', null, GetStyle], + backgroundHorizontalGradient: ['backgroundHorizontalGradient', true, null], + backgroundStrokeColor: ['backgroundStrokeColor', null, GetStyle], + backgroundStrokeLineWidth: ['backgroundStrokeLineWidth', 2, null], + backgroundCornerRadius: ['backgroundCornerRadius', 0, null], + backgroundCornerIteration: ['backgroundCornerIteration', null, null], + + // font + fontFamily: ['fontFamily', 'Courier', null], + fontSize: ['fontSize', '16px', null], + fontStyle: ['fontStyle', '', null], + color: ['color', '#fff', GetStyle], + stroke: ['stroke', '#fff', GetStyle], + strokeThickness: ['strokeThickness', 0, null], + shadowOffsetX: ['shadow.offsetX', 0, null], + shadowOffsetY: ['shadow.offsetY', 0, null], + shadowColor: ['shadow.color', '#000', GetStyle], + shadowBlur: ['shadow.blur', 0, null], + shadowStroke: ['shadow.stroke', false, null], + shadowFill: ['shadow.fill', false, null], + + // underline + underlineColor: ['underline.color', '#000', GetStyle], + underlineThickness: ['underline.thickness', 0, null], + underlineOffset: ['underline.offset', 0, null], + + // align + halign: ['halign', 'left', null], + valign: ['valign', 'top', null], + + // size + maxLines: ['maxLines', 0, null], + fixedWidth: ['fixedWidth', 0, null], + fixedHeight: ['fixedHeight', 0, null], + resolution: ['resolution', 0, null], + lineSpacing: ['lineSpacing', 0, null], + xOffset: ['xOffset', 0, null], + + rtl: ['rtl', false, null], + testString: ['testString', '|MÉqgy', null], + baselineX: ['baselineX', 1.2, null], + baselineY: ['baselineY', 1.4, null], + + // wrap + wrapMode: ['wrap.mode', 0, null], + wrapWidth: ['wrap.width', 0, null], + wrapCallback: ['wrap.callback', null], + wrapCallbackScope: ['wrap.callbackScope', null], +}; + +export default PropertyMap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/TextStyle.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/TextStyle.js new file mode 100644 index 000000000..880ecf44b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/TextStyle.js @@ -0,0 +1,578 @@ +import DefaultPropertyMap from './PropertyMap.js'; +import MeasureText from './MeasureText.js'; +import CONST from '../const.js'; +import GetStyle from '../../../utils/canvas/GetStyle.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const GetValue = Phaser.Utils.Objects.GetValue; + +class TextStyle { + constructor(text, style, propertyMap) { + this.parent = text; + // parent.updateText() + // parent.width, parent.height + + if (propertyMap === undefined) { + propertyMap = DefaultPropertyMap; + } + this.propertyMap = propertyMap; + + this.backgroundColor; + this.backgroundColor2; + this.backgroundHorizontalGradient; + this.backgroundStrokeColor; + this.backgroundStrokeLineWidth; + this.backgroundCornerRadius; + this.backgroundCornerIteration; + + this.fontFamily; + this.fontSize; + this.fontStyle; + this.color; + this.stroke; + this.strokeThickness; + this.shadowOffsetX; + this.shadowOffsetY; + this.shadowColor; + this.shadowBlur; + this.shadowStroke; + this.shadowFill; + + this.underlineColor; + this.underlineThickness; + this.underlineOffset; + + this.halign; + this.valign; + + this.maxLines; + this.fixedWidth; + this.fixedHeight; + this.resolution; + this.lineSpacing; + this.xOffset; + + this.rtl; + this.testString; + + this.baselineX; + this.baselineY; + + this.wrapMode; + this.wrapWidth; + this.wrapCallback; + this.wrapCallbackScope; + + this._font; + + // Set to defaults + user style + this.setStyle(style, false, true); + } + + get isWrapFitMode() { + return (this.fixedWidth > 0) && (this.wrapMode !== CONST.NO_WRAP) && (this.wrapWidth === 0); + } + + setStyle(style, updateText, setDefaults) { + if (updateText === undefined) { + updateText = true; + } + if (setDefaults === undefined) { + setDefaults = false; + } + + if (style && style.hasOwnProperty('wrap')) { + var wrap = style.wrap; + if (wrap.hasOwnProperty('mode')) { + var mode = wrap.mode; + if (typeof mode === 'string') { + wrap.mode = WRAPMODE[mode]; + } + } else { + if (wrap.hasOwnProperty('width')) { + wrap.mode = 1; + } + } + } + + // default halign of RTL is 'right' + if (style && style.rtl && setDefaults && (!style.hasOwnProperty('halign'))) { + style.halign = 'right'; + } + + // Avoid type mutation + if (style && style.hasOwnProperty('fontSize') && typeof style.fontSize === 'number') { + style.fontSize = style.fontSize.toString() + 'px'; + } + + var propertyMap = this.propertyMap; + for (var key in propertyMap) { + var prop = propertyMap[key]; // [ Object Key, Default Value, preCallback ] + var objKey = prop[0]; + var defaultValue = (setDefaults) ? prop[1] : this[key]; + var postCallback = prop[2]; + + + if (key === 'wrapCallback' || key === 'wrapCallbackScope') { + // Callback & scope should be set without processing the values + this[key] = GetValue(style, objKey, defaultValue); + } else { + var value = GetAdvancedValue(style, objKey, defaultValue); + if (postCallback) { + value = postCallback(value); + } + this[key] = value; + } + + } + + // Allow for 'font' override + var font = GetValue(style, 'font', null); + + if (font === null) { + this._font = this.fontStyle + ' ' + this.fontSize + ' ' + this.fontFamily; + } else { + this._font = font; + } + + // Allow for 'fill' to be used in place of 'color' + var fill = GetValue(style, 'fill', null); + + if (fill !== null) { + this.color = GetStyle(fill); + } + + var metrics = GetValue(style, 'metrics', false); + + // Provide optional TextMetrics in the style object to avoid the canvas look-up / scanning + // Doing this is reset if you then change the font of this TextStyle after creation + if (metrics) { + this.metrics = { + ascent: GetValue(metrics, 'ascent', 0), + descent: GetValue(metrics, 'descent', 0), + fontSize: GetValue(metrics, 'fontSize', 0) + }; + } else if (updateText || (!this.metrics)) { + this.metrics = MeasureText(this); + } + + if (updateText) { + return this.parent.updateText(); + } else { + return this.parent; + } + } + + syncFont(canvas, context) { + context.font = this._font; + } + + syncStyle(canvas, context) { + context.textBaseline = 'alphabetic'; + + context.fillStyle = this.color; + context.strokeStyle = this.stroke; + + context.lineWidth = this.strokeThickness; + context.lineCap = 'round'; + context.lineJoin = 'round'; + } + + syncShadow(context, enabled) { + if (enabled) { + context.shadowOffsetX = this.shadowOffsetX; + context.shadowOffsetY = this.shadowOffsetY; + context.shadowColor = this.shadowColor; + context.shadowBlur = this.shadowBlur; + } else { + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowColor = 0; + context.shadowBlur = 0; + } + } + + update(recalculateMetrics) { + if (recalculateMetrics) { + this._font = `${this.fontStyle} ${this.fontSize} ${this.fontFamily}`.trim(); + + this.metrics = MeasureText(this); + } + + return this.parent.updateText(recalculateMetrics); + } + + buildFont() { + var newFont = `${this.fontStyle} ${this.fontSize} ${this.fontFamily}`.trim(); + if (newFont !== this._font) { + this._font = newFont; + //this.metrics = MeasureText(this); + } + return this; + } + + setFont(font) { + if (typeof font === 'string') { + this.fontFamily = font; + this.fontSize = ''; + this.fontStyle = ''; + } else { + this.fontFamily = GetValue(font, 'fontFamily', 'Courier'); + this.fontSize = GetValue(font, 'fontSize', '16px'); + this.fontStyle = GetValue(font, 'fontStyle', ''); + } + + return this.update(true); + } + + setFontFamily(family) { + this.fontFamily = family; + + return this.update(true); + } + + setFontStyle(style) { + this.fontStyle = style; + + return this.update(true); + } + + setFontSize(size) { + if (typeof size === 'number') { + size = size.toString() + 'px'; + } + + this.fontSize = size; + + return this.update(true); + } + + setTestString(string) { + this.testString = string; + + return this.update(true); + } + + setFixedSize(width, height) { + this.fixedWidth = width; + this.fixedHeight = height; + + if (width) { + this.parent.width = width; + } + + if (height) { + this.parent.height = height; + } + + return this.update(this.isWrapFitMode); + } + + setResolution(value) { + this.resolution = value; + + return this.update(false); + } + + setLineSpacing(value) { + this.lineSpacing = value; + + return this.update(false); + } + + setXOffset(value) { + this.xOffset = value; + + return this.update(false); + } + + setBackgroundColor(color, color2, isHorizontalGradient) { + if (isHorizontalGradient === undefined) { + isHorizontalGradient = true; + } + + this.backgroundColor = GetStyle(color, this.parent.canvas, this.parent.context); + this.backgroundColor2 = GetStyle(color2, this.parent.canvas, this.parent.context); + this.backgroundHorizontalGradient = isHorizontalGradient; + + return this.update(false); + } + + setBackgroundStrokeColor(color, lineWidth) { + this.backgroundStrokeColor = GetStyle(color, this.parent.canvas, this.parent.context); + this.backgroundStrokeLineWidth = lineWidth; + + return this.update(false); + } + + setBackgroundCornerRadius(radius, iteration) { + this.backgroundCornerRadius = radius; + this.backgroundCornerIteration = iteration; + + return this.update(false); + } + + setFill(color) { + this.color = GetStyle(color, this.parent.canvas, this.parent.context); + + return this.update(false); + } + + setColor(color) { + this.color = GetStyle(color, this.parent.canvas, this.parent.context); + + return this.update(false); + } + + setStroke(color, thickness) { + if (color === undefined) { + // Reset the stroke to zero (disabling it) + this.strokeThickness = 0; + } else { + if (thickness === undefined) { + thickness = this.strokeThickness; + } + + this.stroke = GetStyle(color, this.parent.canvas, this.parent.context); + this.strokeThickness = thickness; + } + + return this.update(true); + } + + setShadow(x, y, color, blur, shadowStroke, shadowFill) { + if (x === undefined) { + x = 0; + } + if (y === undefined) { + y = 0; + } + if (color === undefined) { + color = '#000'; + } + if (blur === undefined) { + blur = 0; + } + if (shadowStroke === undefined) { + shadowStroke = false; + } + if (shadowFill === undefined) { + shadowFill = true; + } + + this.shadowOffsetX = x; + this.shadowOffsetY = y; + this.shadowColor = GetStyle(color, this.parent.canvas, this.parent.context); + this.shadowBlur = blur; + this.shadowStroke = shadowStroke; + this.shadowFill = shadowFill; + + return this.update(false); + } + + setShadowOffset(x, y) { + if (x === undefined) { + x = 0; + } + if (y === undefined) { + y = x; + } + + this.shadowOffsetX = x; + this.shadowOffsetY = y; + + return this.update(false); + } + + setShadowColor(color) { + if (color === undefined) { + color = '#000'; + } + + this.shadowColor = GetStyle(color, this.parent.canvas, this.parent.context); + + return this.update(false); + } + + setShadowBlur(blur) { + if (blur === undefined) { + blur = 0; + } + + this.shadowBlur = blur; + + return this.update(false); + } + + setShadowStroke(enabled) { + this.shadowStroke = enabled; + + return this.update(false); + } + + setShadowFill(enabled) { + this.shadowFill = enabled; + + return this.update(false); + } + + setUnderline(color, thickness, offset) { + if (color === undefined) { + color = '#000'; + } + if (thickness === undefined) { + thickness = 0; + } + if (offset === undefined) { + offset = 0; + } + + this.underlineColor = GetStyle(color, this.parent.canvas, this.parent.context); + this.underlineThickness = thickness; + this.underlineOffset = offset; + + return this.update(false); + } + + setUnderlineColor(color) { + if (color === undefined) { + color = '#000'; + } + + this.underlineColor = GetStyle(color, this.parent.canvas, this.parent.context); + return this.update(false); + } + + setUnderlineThickness(thickness) { + if (thickness === undefined) { + thickness = 0; + } + + this.underlineThickness = thickness; + return this.update(false); + } + + setUnderlineOffset(offset) { + if (offset === undefined) { + offset = 0; + } + + this.underlineOffset = offset; + return this.update(false); + } + + setWrapMode(mode) { + if (typeof mode === 'string') { + mode = WRAPMODE[mode.toLowerCase()] || 0; + } + this.wrapMode = mode; + return this.update(true); + } + + setWrapWidth(width) { + this.wrapWidth = width; + return this.update(false); + } + + setAlign(halign, valign) { + if (halign === undefined) { + halign = 'left'; + } + if (valign === undefined) { + valign = 'top'; + } + this.halign = halign; + this.valign = valign; + + return this.update(false); + } + + setHAlign(halign) { + if (halign === undefined) { + halign = 'left'; + } + this.halign = halign; + + return this.update(false); + } + + setVAlign(valign) { + if (valign === undefined) { + valign = 'top'; + } + this.valign = valign; + + return this.update(false); + } + + setMaxLines(max) { + if (max === undefined) { + max = 0; + } + + this.maxLines = max; + + return this.update(false); + } + + getTextMetrics() { + var metrics = this.metrics; + + return { + ascent: metrics.ascent, + descent: metrics.descent, + fontSize: metrics.fontSize + }; + } + + setTextMetrics(metrics, font) { + this.metrics.ascent = metrics.ascent; + this.metrics.descent = metrics.descent; + this.metrics.fontSize = metrics.fontSize; + + if (font) { + if (typeof font === 'string') { + this.fontFamily = font; + this.fontSize = ''; + this.fontStyle = ''; + } else { + this.fontFamily = GetValue(font, 'fontFamily', this.fontFamily); + this.fontSize = GetValue(font, 'fontSize', this.fontSize); + this.fontStyle = GetValue(font, 'fontStyle', this.fontStyle); + } + } + + return this.parent.updateText(true); + } + + get lineHeight() { + return this.metrics.fontSize + this.strokeThickness + this.lineSpacing; + } + + toJSON() { + var output = {}; + + var propertyMap = this.propertyMap; + for (var key in propertyMap) { + output[key] = this[key]; + } + + output.metrics = this.getTextMetrics(); + + return output; + } + + destroy() { + this.parent = undefined; + } + +} + +const WRAPMODE = { + none: CONST.NO_WRAP, + word: CONST.WORD_WRAP, + char: CONST.CHAR_WRAP, + character: CONST.CHAR_WRAP +}; + +export default TextStyle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/TextStyleInterface.d.ts b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/TextStyleInterface.d.ts new file mode 100644 index 000000000..d9e8153f5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/textbase/textstyle/TextStyleInterface.d.ts @@ -0,0 +1,69 @@ +interface TextStyle { + fontFamily?: string, + fontSize?: string, + fontStyle?: string, + + backgroundColor?: null | string | number, + backgroundColor2?: null | string | number, + backgroundHorizontalGradient?: boolean, + backgroundStrokeColor?: null | string | number, + backgroundStrokeLineWidth?: number, + backgroundCornerRadius?: number, + backgroundCornerIteration?: null | number, + + color?: null | string | number, + fill?: null | string | number, + + stroke?: null | string | number, + strokeThickness?: number, + + shadow?: { + offsetX?: number, + offsetY?: number, + color?: number | string, + blur?: number, + stroke?: false, + fill?: false + }, + + underline?: { + color?: number | string, + thickness?: number, + offset?: number, + }, + + align?: 'left' | 'center' | 'right', + halign?: 'left' | 'center' | 'right', + valign?: 'top' | 'center' | 'bottom', + + padding?: { + left?: number, + right?: number, + top?: number, + bottom?: number, + }, + + maxLines?: number, + lineSpacing?: number, + + fixedWidth?: number, + fixedHeight?: number, + + resolution?: number, + + testString?: string, + + wrap?: { + mode?: 0 | 1 | 2 | 'none' | 'word' | 'char' | 'character' + width?: null | number, + }, + + metrics?: boolean | + { + ascent: number, + descent: number, + fontSize: number + }, +} + +export default TextStyle; diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videobase/CreateVideoElement.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videobase/CreateVideoElement.js new file mode 100644 index 000000000..77a62e9c5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videobase/CreateVideoElement.js @@ -0,0 +1,54 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +var CreateVideoElement = function (config) { + var element = document.createElement('video'); + + // Apply registed properties + var elemProp, elemPropValue; + for (var key in ElementProperties) { + elemProp = ElementProperties[key]; + elemPropValue = GetValue(config, key, elemProp[1]); + if (elemPropValue !== undefined) { + element[elemProp[0]] = elemPropValue; + } + } + + // Apply events + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + if (eventEmitter) { + for (let eventName in ElementEvents) { // Note: Don't use `var` here + element.addEventListener(ElementEvents[eventName], (function () { + eventEmitter.emit(eventName, this); + }).bind(this)); + } + } + + return element; +} + +const ElementProperties = { + id: ['id', undefined], + width: ['width', undefined], + height: ['height', undefined], + autoPlay: ['autoplay', true], + controls: ['controls', false], + loop: ['loop', false], + poster: ['poster', undefined], + preload: ['preload', undefined], + muted: ['muted', false], + playsInline: ['playsInline', true], + crossOrigin: ['crossOrigin', 'anonymous'], +}; + +const ElementEvents = { + canplay: 'canplay', + canplaythrough: 'canplaythrough', + ended: 'ended', + error: 'error', + loadstart: 'loadstart', + playing: 'playing', + pause: 'pause', + stalled: 'stalled', +}; + +export default CreateVideoElement; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videobase/Load.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videobase/Load.js new file mode 100644 index 000000000..792b67ef0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videobase/Load.js @@ -0,0 +1,21 @@ +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +var Load = function (video, src, availableVideoTypes) { + if (IsPlainObject(src)) { + var videoType; + for (var i = 0, cnt = VideoTypes.length; i < cnt; i++) { + videoType = VideoTypes[i]; + if (availableVideoTypes[videoType] && src.hasOwnProperty(videoType)) { + src = src[videoType]; + break; + } + } + } + + video.src = src; + video.load(); +} + +const VideoTypes = ['webm', 'ogg', 'mp4', 'h264', 'vp9', 'hls']; + +export default Load; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videobase/VideoBase.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videobase/VideoBase.js new file mode 100644 index 000000000..4a3a0610e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videobase/VideoBase.js @@ -0,0 +1,219 @@ +import CreateVideoElement from './CreateVideoElement.js'; +import Load from './Load.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Clamp = Phaser.Math.Clamp; + +var VideoBase = function (GOClass) { + return class Base extends GOClass { + createVideoElement(config) { + if (!this.video) { + if (config === undefined) { + config = {}; + } + config.eventEmitter = this; + this.video = CreateVideoElement(config); + this.playbackTimeChangeEventEnable = GetValue(config, 'playbackTimeChangeEventEnable', true); + } + return this.video; + } + + preDestroy() { + if (this.video) { + this.video.pause(); + this.video.removeAttribute('src'); // empty source + this.video.load(); + this.video = undefined; + } + if (super.preDestroy) { + super.preDestroy(); + } + } + + preUpdate(time, delta) { + if (this.playbackTimeChangeEventEnable) { + var curT = this.playbackTime; + if (curT !== this.prevT) { + this.emit('playbacktimechange', this); + } + this.prevT = curT; + } + if (super.preUpdate) { + super.preUpdate(time, delta); + } + } + + get availableVideoTypes() { + return this.scene.sys.game.device.video; + } + + load(src) { + if (this.video) { + Load(this.video, src, this.availableVideoTypes); + } + return this; + } + + play() { + if (this.video) { + this.video.play(); + } + return this; + } + + get isPlaying() { + if (this.video) { + var video = this.video; + return (!video.paused) && (!video.ended) && (video.currentTime > 0); + } else { + return false; + } + } + + pause() { + if (this.video) { + this.video.pause(); + } + return this; + } + + get isPaused() { + if (this.video) { + return this.video.paused; + } else { + return false; + } + } + + get playbackTime() { + if (this.video) { + return this.video.currentTime || 0; + } else { + return 0; + } + } + + set playbackTime(value) { + if (this.video) { + try { + this.video.currentTime = value; + } catch (e) { + } + } + } + + setPlaybackTime(time) { + this.playbackTime = time; + return this; + } + + get duration() { + if (this.video) { + return this.video.duration || 0; + } else { + return 0; + } + } + + get t() { + if (this.video) { + var duration = this.duration; + return (duration === 0) ? 0 : this.playbackTime / duration; + } else { + return 0; + } + } + + set t(value) { + if (this.video) { + this.playbackTime = this.duration * Clamp(value, 0, 1); + } + } + + setT(value) { + this.t = value; + return this; + } + + get hasEnded() { + if (this.video) { + return this.video.ended; + } else { + return false; + } + } + + get volume() { + if (this.video) { + return this.video.volume || 0; + } else { + return 0; + } + } + + set volume(value) { + if (this.video) { + this.video.volume = value; + } + } + + setVolume(value) { + this.volume = value; + return this; + } + + get muted() { + if (this.video) { + return this.video.muted || false; + } else { + return false; + } + } + + set muted(value) { + if (this.video) { + this.video.muted = value; + } + } + + setMute(value) { + if (value === undefined) { + value = true; + } + this.muted = value; + return this; + } + + get loop() { + if (this.video) { + return this.video.loop; + } else { + return false; + } + } + + set loop(value) { + if (this.video) { + this.video.loop = value; + } + } + + setLoop(value) { + if (value === undefined) { + value = true; + } + this.loop = value; + return this; + } + + get readyState() { + if (this.video) { + return this.video.readyState; + } else { + return undefined; + } + } + } +}; + +export default VideoBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videocanvas/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videocanvas/Creator.js new file mode 100644 index 000000000..48cc1525d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videocanvas/Creator.js @@ -0,0 +1,17 @@ +import VideoCanvas from './VideoCanvas.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', undefined); + var gameObject = new VideoCanvas(this.scene, 0, 0, width, height, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videocanvas/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videocanvas/Factory.js new file mode 100644 index 000000000..f0c9dccdb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videocanvas/Factory.js @@ -0,0 +1,7 @@ +import VideoCanvas from './VideoCanvas.js'; + +export default function (x, y, width, height, config) { + var gameObject = new VideoCanvas(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videocanvas/VideoCanvas.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videocanvas/VideoCanvas.js new file mode 100644 index 000000000..102cb5ce5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videocanvas/VideoCanvas.js @@ -0,0 +1,79 @@ +import VideoBase from '../videobase/VideoBase.js'; +import Canvas from '../../canvas/canvasbase/Canvas.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class VideoCanvas extends VideoBase(Canvas) { + constructor(scene, x, y, width, height, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', undefined); + height = GetValue(config, 'height', undefined); + } else if (IsPlainObject(width)) { + config = width; + width = GetValue(config, 'width', undefined); + height = GetValue(config, 'height', undefined); + } + + if (config === undefined) { + config = {}; + } + var autoRound = scene.sys.scale.autoRound; + if (width !== undefined) { + if (autoRound) { + width = Math.floor(width); + } + config.width = width; + } + if (height !== undefined) { + if (autoRound) { + height = Math.floor(height); + } + config.height = height; + } + + super(scene, x, y, width, height); + this.type = 'rexVideoCanvas'; + + this.createVideoElement(config); + this.load(GetValue(config, 'src', '')); + } + + renderWebGL(renderer, src, camera, parentMatrix) { + if (this.readyState > 0) { + this.renderer.canvasToTexture(this.video, this.frame.source.glTexture, true); + this.frame.glTexture = this.frame.source.glTexture; + } else { + var renderer = this.renderer; + var gl = renderer.gl; + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + renderer.setFramebuffer(null, true); + } + super.renderWebGL(renderer, src, camera, parentMatrix); + } + + renderCanvas(renderer, src, camera, parentMatrix) { + if (this.readyState > 0) { + this.context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height); + } else { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + } + super.renderCanvas(renderer, src, camera, parentMatrix); + } + + resize(width, height) { + if ((this.width === width) && (this.height === height)) { + return this; + } + + this.video.width = width; + this.video.height = height; + super.resize(width, height); + return this; + } +} +export default VideoCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videodom/Creator.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videodom/Creator.js new file mode 100644 index 000000000..75de98a18 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videodom/Creator.js @@ -0,0 +1,16 @@ +import VideoDOM from './VideoDOM.js'; + +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const BuildGameObject = Phaser.GameObjects.BuildGameObject; + +export default function (config, addToScene) { + if (config === undefined) { config = {}; } + if (addToScene !== undefined) { + config.add = addToScene; + } + var width = GetAdvancedValue(config, 'width', undefined); + var height = GetAdvancedValue(config, 'height', undefined); + var gameObject = new VideoDOM(this.scene, 0, 0, width, height, config); + BuildGameObject(this.scene, gameObject, config); + return gameObject; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videodom/Factory.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videodom/Factory.js new file mode 100644 index 000000000..624a59d1c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videodom/Factory.js @@ -0,0 +1,7 @@ +import VideoDOM from './VideoDOM.js'; + +export default function (x, y, width, height, config) { + var gameObject = new VideoDOM(this.scene, x, y, width, height, config); + this.scene.add.existing(gameObject); + return gameObject; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videodom/VideoDOM.js b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videodom/VideoDOM.js new file mode 100644 index 000000000..259dc3bfb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gameobjects/video/videodom/VideoDOM.js @@ -0,0 +1,58 @@ +import VideoBase from '../videobase/VideoBase.js'; + +const DOMElement = Phaser.GameObjects.DOMElement; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class VideoDOM extends VideoBase(DOMElement) { + constructor(scene, x, y, width, height, config) { + if (IsPlainObject(x)) { + config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', undefined); + height = GetValue(config, 'height', undefined); + } else if (IsPlainObject(width)) { + config = width; + width = GetValue(config, 'width', undefined); + height = GetValue(config, 'height', undefined); + } + + if (config === undefined) { + config = {}; + } + var autoRound = scene.sys.scale.autoRound; + if (width !== undefined) { + if (autoRound) { + width = Math.floor(width); + } + config.width = width; + } + if (height !== undefined) { + if (autoRound) { + height = Math.floor(height); + } + config.height = height; + } + + super(scene, x, y); + this.type = 'rexVideo'; + + this + .setElement(this.createVideoElement(config)) + .load(GetValue(config, 'src', '')); + } + + resize(width, height) { + if ((this.width === width) && (this.height === height)) { + return this; + } + + this.node.width = width; + this.node.height = height; + this.updateSize(); + return this; + } +} + +export default VideoDOM; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gashapon-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/gashapon-plugin.d.ts new file mode 100644 index 000000000..eb33554a0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gashapon-plugin.d.ts @@ -0,0 +1,8 @@ +import Gashapon from './gashapon'; + +export default class GashaponPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: Gashapon.IConfig + ): Gashapon; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gashapon-plugin.js b/ui/src/phaser3-rex-plugins/plugins/gashapon-plugin.js new file mode 100644 index 000000000..9315bb169 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gashapon-plugin.js @@ -0,0 +1,19 @@ +import Gashapon from './gashapon.js'; + +class GashaponPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new Gashapon(config); + } +} + +export default GashaponPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gashapon.d.ts b/ui/src/phaser3-rex-plugins/plugins/gashapon.d.ts new file mode 100644 index 000000000..6a2ee196f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gashapon.d.ts @@ -0,0 +1,2 @@ +import Gashapon from './math/gashapon/Gashapon'; +export default Gashapon; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gashapon.js b/ui/src/phaser3-rex-plugins/plugins/gashapon.js new file mode 100644 index 000000000..f414f940e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gashapon.js @@ -0,0 +1,2 @@ +import Gashapon from './math/gashapon/Gashapon.js'; +export default Gashapon; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/hexagon/Height.js b/ui/src/phaser3-rex-plugins/plugins/geom/hexagon/Height.js new file mode 100644 index 000000000..4300ea0ad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/hexagon/Height.js @@ -0,0 +1,7 @@ +const SQRT3 = Math.sqrt(3); + +var Height = function (hexagon) { + return (hexagon.type === 0) ? (SQRT3 * hexagon.size) : (2 * hexagon.size); +}; + +export default Height; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/hexagon/Hexagon.js b/ui/src/phaser3-rex-plugins/plugins/geom/hexagon/Hexagon.js new file mode 100644 index 000000000..f2e730a0d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/hexagon/Hexagon.js @@ -0,0 +1,208 @@ +// https://www.redblobgames.com/grids/hexagons/ + +import Offset from '../utils/Offset.js'; +import Width from './Width.js'; +import Height from './Height.js'; +import SetPoints from './SetPoints.js'; + +const Polygon = Phaser.Geom.Polygon; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const Line = Phaser.Geom.Line; + +class Hexagon extends Polygon { + constructor(x, y, size, orientationType) { + super(); + if (IsPlainObject(x)) { + var config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + size = GetValue(config, 'size', 0); + orientationType = GetValue(config, 'type', 1); + } + var points = this.points; + for (var i = 0; i < 6; i++) { + points.push({}); + } + this.setTo(x, y, size, orientationType); + } + + // override + setTo(x, y, size, orientationType) { + if (typeof (orientationType) === 'string') { + orientationType = ORIENTATIONTYPE[orientationType] + } + + this._x = x; + this._y = y; + this._size = size; + this._orientationType = orientationType; + + SetPoints(x, y, size, orientationType, this.points); + this.calculateArea(); + this.width = Width(this); + this.height = Height(this); + return this; + } + + get x() { + return this._x; + } + + set x(value) { + var offsetX = value - this.x; + if (offsetX === 0) { + return; + } + Offset(this, offsetX, 0); + this._x = value; + } + + get y() { + return this._y; + } + + set y(value) { + var offsetY = value - this.y; + if (offsetY === 0) { + return; + } + Offset(this, 0, offsetY); + this._y = value; + } + + get centerX() { + return this.x; + } + + set centerX(value) { + this.x = value; + } + + get centerY() { + return this.y; + } + + set centerY(value) { + this.y = value; + } + + setPosition(x, y) { + var offsetX = x - this.x; + var offsetY = y - this.y; + if ((offsetX === 0) && (offsetY === 0)) { + return this; + } + Offset(this, offsetX, offsetY); + this._x = x; + this._y = y; + return this; + } + + get left() { + return this.x - (this.width / 2); + } + + set left(value) { + this.x += (value - this.left); + } + + get right() { + return this.x + (this.width / 2); + } + + set right(value) { + this.x += (value - this.right); + } + + get top() { + return this.y - (this.height / 2); + } + + set top(value) { + this.y += (value - this.top); + } + + get bottom() { + return this.y + (this.height / 2); + } + + set bottom(value) { + this.y += (value - this.bottom); + } + + get size() { + return this._size; + } + + set size(value) { + this.setTo(this._x, this._y, value, this._orientationType); + } + + setSize(value) { + this.size = value; + return this; + } + + get orientationType() { + return this._orientationType; + } + + set orientationType(value) { + this.setTo(this._x, this._y, this._size, value); + } + + setType(orientationType) { + this.orientationType = orientationType; + } + + isEmpty() { + return (this.size <= 0); + } + + getEdge(idx, line) { + if (line === undefined) { + line = new Line(); + } + var p0 = this.points[idx]; + var p1 = this.points[(idx + 1) % 6]; + line.setTo(p0.x, p0.y, p1.x, p1.y); + return line; + } + + getLineA(line) { + return this.getEdge(0, line); + } + + getLineB(line) { + return this.getEdge(1, line); + } + + getLineC(line) { + return this.getEdge(2, line); + } + + getLineD(line) { + return this.getEdge(3, line); + } + + getLineE(line) { + return this.getEdge(4, line); + } + + getLineF(line) { + return this.getEdge(5, line); + } +} + +const ORIENTATIONTYPE = { + 'flat': 0, + 'y': 0, + 'pointy': 1, + 'x': 1 +}; + +// use `rexHexagon` to prevent name conflict +Phaser.Geom.rexHexagon = Hexagon; + +export default Hexagon; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/hexagon/SetPoints.js b/ui/src/phaser3-rex-plugins/plugins/geom/hexagon/SetPoints.js new file mode 100644 index 000000000..c98a2de83 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/hexagon/SetPoints.js @@ -0,0 +1,67 @@ +import InitPoints from '../utils/InitPoints.js'; +import DegToRad from '../../utils/math/DegToRad.js'; + +var SetPoints = function (x, y, size, type, points) { + if (points === undefined) { + points = InitPoints(6); + } + + if (size === undefined) {} else if (typeof (size) === 'number') { + var angleOffset = (type === 0) ? 0 : -30; + var angleDeg, angleRad; + for (var i = 0; i < 6; i++) { + angleDeg = (60 * i) + angleOffset; + angleRad = DegToRad(angleDeg); + points[i].x = x + size * Math.cos(angleRad); + points[i].y = y + size * Math.sin(angleRad); + } + } else { + var config = size; + var w = config.width; + var h = config.height; + var halfW = w / 2; + var quarterW = w / 4; + var halfH = h / 2; + var quarterH = h / 4; + if (type === 0) { + points[0].x = x + halfW; + points[0].y = y; + + points[1].x = x + quarterW; + points[1].y = y + halfH; + + points[2].x = x - quarterW; + points[2].y = y + halfH; + + points[3].x = x - halfW; + points[3].y = y; + + points[4].x = x - quarterW; + points[4].y = y - halfH; + + points[5].x = x + quarterW; + points[5].y = y - halfH; + } else { + points[0].x = x + halfW; + points[0].y = y - quarterH; + + points[1].x = x + halfW; + points[1].y = y + quarterH; + + points[2].x = x; + points[2].y = y + halfH; + + points[3].x = x - halfW; + points[3].y = y + quarterH; + + points[4].x = x - halfW; + points[4].y = y - quarterH; + + points[5].x = x; + points[5].y = y - halfH; + } + } + return points; +} + +export default SetPoints; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/hexagon/Width.js b/ui/src/phaser3-rex-plugins/plugins/geom/hexagon/Width.js new file mode 100644 index 000000000..afca1c759 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/hexagon/Width.js @@ -0,0 +1,7 @@ +const SQRT3 = Math.sqrt(3); + +var Width = function (hexagon) { + return (hexagon.type === 0) ? (2 * hexagon.size) : (SQRT3 * hexagon.size); +}; + +export default Width; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/ArcTo.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/ArcTo.js new file mode 100644 index 000000000..5e44c4b65 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/ArcTo.js @@ -0,0 +1,24 @@ +import LineTo from './LineTo.js'; + +const DegToRad = Phaser.Math.DegToRad; + +var ArcTo = function (centerX, centerY, radiusX, radiusY, startAngle, endAngle, antiClockWise, iteration, pathData) { + // startAngle, endAngle: 0 ~ 360 + if (antiClockWise && (endAngle > startAngle)) { + endAngle -= 360; + } else if (!antiClockWise && (endAngle < startAngle)) { + endAngle += 360; + } + + var deltaAngle = endAngle - startAngle; + var step = DegToRad(deltaAngle) / iteration; + startAngle = DegToRad(startAngle); + for (var i = 0; i <= iteration; i++) { + var angle = startAngle + (step * i); + var x = centerX + (radiusX * Math.cos(angle)); + var y = centerY + (radiusY * Math.sin(angle)); + LineTo(x, y, pathData); + } + return pathData; +} +export default ArcTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/CubicBezierCurveTo.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/CubicBezierCurveTo.js new file mode 100644 index 000000000..52760eae2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/CubicBezierCurveTo.js @@ -0,0 +1,19 @@ +// import CubicBezierInterpolation from '../../utils/math/interpolation/CubicBezierInterpolation.js'; + +const CubicBezierInterpolation = Phaser.Math.Interpolation.CubicBezier; + +var CubicBezierCurveTo = function (cx0, cy0, cx1, cy1, x, y, iterations, pathData) { + var pathDataCnt = pathData.length; + var p0x = pathData[pathDataCnt - 2]; + var p0y = pathData[pathDataCnt - 1]; + for (var i = 1, last = iterations - 1; i <= last; i++) { + var t = i / last; + pathData.push( + CubicBezierInterpolation(t, p0x, cx0, cx1, x), + CubicBezierInterpolation(t, p0y, cy0, cy1, y) + ); + } + return pathData; +} + +export default CubicBezierCurveTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/DuplicateLast.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/DuplicateLast.js new file mode 100644 index 000000000..53af83d00 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/DuplicateLast.js @@ -0,0 +1,15 @@ +var DuplicateLast = function (pathData) { + var len = pathData.length; + if (len < 2) { + return pathData; + } + + var lastX = pathData[len - 2]; + var lastY = pathData[len - 1]; + pathData.push(lastX); + pathData.push(lastY); + + return pathData; +} + +export default DuplicateLast; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/LineTo.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/LineTo.js new file mode 100644 index 000000000..1ccc238b4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/LineTo.js @@ -0,0 +1,15 @@ +var LineTo = function (x, y, pathData) { + var cnt = pathData.length; + if (cnt >= 2) { + var lastX = pathData[cnt - 2]; + var lastY = pathData[cnt - 1]; + if ((x === lastX) && (y === lastY)) { + return pathData; + } + } + + pathData.push(x, y); + return pathData; +} + +export default LineTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/Offset.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/Offset.js new file mode 100644 index 000000000..3569dceaa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/Offset.js @@ -0,0 +1,9 @@ +var Offset = function (x, y, pathData) { + for (var i = 0, cnt = pathData.length - 1; i < cnt; i += 2) { + pathData[i] += x; + pathData[i + 1] += y; + } + return pathData; +} + +export default Offset; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/AddPathMethods.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/AddPathMethods.js new file mode 100644 index 000000000..880dea735 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/AddPathMethods.js @@ -0,0 +1,133 @@ +import StartAt from '../StartAt.js'; +import LineTo from '../LineTo.js'; +import ArcTo from '../ArcTo.js'; +import QuadraticBezierTo from '../QuadraticBezierTo.js'; +import CubicBezierCurveTo from '../CubicBezierCurveTo.js'; +import DuplicateLast from '../DuplicateLast.js'; + +export default { + clear() { + this.start(); + return this; + }, + + start() { + this.startAt(); + return this; + }, + + startAt(x, y) { + this.restorePathData(); + this.accumulationLengths = undefined; + + StartAt(x, y, this.pathData); + this.firstPointX = x; + this.firstPointY = y; + this.lastPointX = x; + this.lastPointY = y; + + return this; + }, + + lineTo(x, y, relative) { + if (relative === undefined) { + relative = false; + } + if (relative) { + x += this.lastPointX; + y += this.lastPointY; + } + + LineTo(x, y, this.pathData); + + this.lastPointX = x; + this.lastPointY = y; + return this; + }, + + verticalLineTo(x, relative) { + this.lineTo(x, this.lastPointY, relative); + return this; + }, + + horizontalLineTo(y, relative) { + this.lineTo(this.lastPointX, y, relative); + return this; + }, + + ellipticalArc(centerX, centerY, radiusX, radiusY, startAngle, endAngle, anticlockwise) { + if (anticlockwise === undefined) { + anticlockwise = false; + } + + ArcTo( + centerX, centerY, + radiusX, radiusY, + startAngle, endAngle, anticlockwise, + this.iterations, + this.pathData + ); + + this.lastPointX = this.pathData[this.pathData.length - 2]; + this.lastPointY = this.pathData[this.pathData.length - 1]; + return this; + }, + + arc(centerX, centerY, radius, startAngle, endAngle, anticlockwise) { + this.ellipticalArc(centerX, centerY, radius, radius, startAngle, endAngle, anticlockwise) + return this; + }, + + quadraticBezierTo(cx, cy, x, y) { + QuadraticBezierTo( + cx, cy, x, y, + this.iterations, + this.pathData + ); + + this.lastPointX = x; + this.lastPointY = y; + this.lastCX = cx; + this.lastCY = cy; + return this; + }, + + smoothQuadraticBezierTo(x, y) { + var cx = this.lastPointX * 2 - this.lastCX; + var cy = this.lastPointY * 2 - this.lastCY; + this.quadraticBezierTo(cx, cy, x, y); + return this; + }, + + cubicBezierCurveTo(cx0, cy0, cx1, cy1, x, y) { + CubicBezierCurveTo( + cx0, cy0, cx1, cy1, x, y, + this.iterations, + this.pathData + ); + + this.lastPointX = x; + this.lastPointY = y; + this.lastCX = cx1; + this.lastCY = cy1; + return this; + }, + + smoothCubicBezierCurveTo(cx1, cy1, x, y) { + var cx0 = this.lastPointX * 2 - this.lastCX; + var cy0 = this.lastPointY * 2 - this.lastCY; + this.cubicBezierCurveTo(cx0, cy0, cx1, cy1, x, y); + return this; + }, + + close() { + this.closePath = true; + return this; + }, + + end() { + DuplicateLast(this.pathData); + return this; + }, + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/GraphicsMethods.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/GraphicsMethods.js new file mode 100644 index 000000000..6c0ffcb44 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/GraphicsMethods.js @@ -0,0 +1,13 @@ +export default { + draw(graphics, isFill, isStroke) { + var points = this.toPoints(); + if (isFill) { + graphics.fillPoints(points, this.closePath, this.closePath); + } + if (isStroke) { + graphics.strokePoints(points, this.closePath, this.closePath); + } + + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/PathDataBuilder.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/PathDataBuilder.js new file mode 100644 index 000000000..41b2b63db --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/PathDataBuilder.js @@ -0,0 +1,51 @@ +import AddPathMethods from './AddPathMethods.js'; +import TransformPointsMethods from './TransformPointsMethods.js'; +import SavePathDataMethods from './SavePathDataMethods.js'; +import PathSegmentMethods from './PathSegmentMethods.js'; +import GraphicsMethods from './GraphicsMethods.js'; +import ToPoints from '../ToPoints.js'; +import ToPolygon from '../ToPolygon.js'; + + +class PathDataBuilder { + constructor(pathData) { + if (pathData === undefined) { + pathData = []; + } + + this.pathData = pathData; + this.closePath = false; + this.setIterations(32); + + this.firstPointX = undefined; + this.firstPointY = undefined; + this.lastPointX = undefined; + this.lastPointY = undefined; + this.accumulationLengths = undefined; + } + + setIterations(iterations) { + this.iterations = iterations; + return this; + } + + toPoints() { + return ToPoints(this.pathData); + } + + toPolygon(polygon) { + return ToPolygon(this.pathData, polygon); + } + +} + +Object.assign( + PathDataBuilder.prototype, + AddPathMethods, + TransformPointsMethods, + SavePathDataMethods, + PathSegmentMethods, + GraphicsMethods, +) + +export default PathDataBuilder; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/PathSegmentMethods.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/PathSegmentMethods.js new file mode 100644 index 000000000..dad32a9b4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/PathSegmentMethods.js @@ -0,0 +1,138 @@ +import DuplicateLast from '../DuplicateLast'; + +const DistanceBetween = Phaser.Math.Distance.Between; +const Wrap = Phaser.Math.Wrap; +const Linear = Phaser.Math.Linear; + +var AppendFromPathSegment = function (srcPathData, accumulationLengths, startT, endT, destPathData) { + if (endT === undefined) { + endT = startT; + startT = 0; + } + + startT = WrapT(startT); + endT = WrapT(endT); + + if (startT === endT) { + return; + } + + var totalPathLength = accumulationLengths[accumulationLengths.length - 1]; + var startL = totalPathLength * startT; + var endL = totalPathLength * endT; + if (startT < endT) { + AddPathSegment(srcPathData, accumulationLengths, startL, endL, destPathData); + } else { + AddPathSegment(srcPathData, accumulationLengths, startL, totalPathLength, destPathData); + AddPathSegment(srcPathData, accumulationLengths, 0, endL, destPathData); + } + + DuplicateLast(destPathData); +} + +var AddPathSegment = function (srcPathData, accumulationLengths, startL, endL, destPathData) { + var skipState = (startL > 0); + for (var i = 0, cnt = accumulationLengths.length; i < cnt; i++) { + var pIdx = i * 2; + var d = accumulationLengths[i]; + + if (skipState) { + if (d < startL) { + continue; + } else if (d == startL) { + skipState = false; + } else { // d > startL + var deltaD = d - accumulationLengths[i - 1]; + var t = 1 - ((d - startL) / deltaD); + destPathData.push(GetInterpolation(srcPathData, pIdx - 2, pIdx, t)); + destPathData.push(GetInterpolation(srcPathData, pIdx - 1, pIdx + 1, t)); + skipState = false; + } + } + + if (d <= endL) { + destPathData.push(srcPathData[pIdx]); + destPathData.push(srcPathData[pIdx + 1]); + if (d === endL) { + break; + } + } else { // d > endL + var deltaD = d - accumulationLengths[i - 1]; + var t = 1 - ((d - endL) / deltaD); + destPathData.push(GetInterpolation(srcPathData, pIdx - 2, pIdx, t)); + destPathData.push(GetInterpolation(srcPathData, pIdx - 1, pIdx + 1, t)); + break; + } + } +} + +var GetInterpolation = function (pathData, i0, i1, t) { + var p0 = pathData[i0], p1 = pathData[i1]; + return Linear(p0, p1, t); +} + +var WrapT = function (t) { + if (t === 0) { + return 0; + } else if ((t % 1) === 0) { + return 1; + } + return Wrap(t, 0, 1); +} + +export default { + updateAccumulationLengths() { + if (this.accumulationLengths == null) { + this.accumulationLengths = []; + } else if (this.accumulationLengths.length === (this.pathData.length / 2)) { + return this; + } + + var accumulationLengths = this.accumulationLengths; + var pathData = this.pathData; + var prevX, prevY, x, y; + var d, accumulationLength = 0; + for (var i = 0, cnt = pathData.length; i < cnt; i += 2) { + x = pathData[i]; + y = pathData[i + 1]; + + d = (prevX === undefined) ? 0 : DistanceBetween(prevX, prevY, x, y); + accumulationLength += d; + accumulationLengths.push(accumulationLength); + + prevX = x; + prevY = y; + } + + this.totalPathLength = accumulationLength; + + return this; + }, + + setDisplayPathSegment(startT, endT) { + if (!this.pathDataSaved) { + this.updateAccumulationLengths(); + this.savePathData(); + } + + this.pathData.length = 0; + AppendFromPathSegment(this.pathDataSave, this.accumulationLengths, startT, endT, this.pathData); + + return this; + }, + + appendFromPathSegment(src, startT, endT) { + if (startT === undefined) { + this.pathData.push(...src.pathData); + } else { + src.updateAccumulationLengths(); + AppendFromPathSegment(src.pathData, src.accumulationLengths, startT, endT, this.pathData); + } + + this.firstPointX = this.pathData[0]; + this.firstPointY = this.pathData[1]; + this.lastPointX = this.pathData[this.pathData.length - 2]; + this.lastPointY = this.pathData[this.pathData.length - 1]; + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/SavePathDataMethods.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/SavePathDataMethods.js new file mode 100644 index 000000000..c1c6ea1d7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/SavePathDataMethods.js @@ -0,0 +1,25 @@ +import Copy from '../../../utils/array/Copy.js'; + +export default { + savePathData() { + if (this.pathDataSaved) { + return this; + } + + this.pathDataSave = [...this.pathData]; + this.pathData.length = 0; + this.pathDataSaved = true; + return this; + }, + + restorePathData() { + if (!this.pathDataSaved) { + return this; + } + + Copy(this.pathData, this.pathDataSave); + this.pathDataSave = undefined; + this.pathDataSaved = false; + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/TransformPointsMethods.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/TransformPointsMethods.js new file mode 100644 index 000000000..ace7f0f0c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/PathDataBuilder/TransformPointsMethods.js @@ -0,0 +1,57 @@ +import RotateAround from '../RotateAround.js'; +import Scale from '../Scale.js'; +import Offset from '../Offset.js'; + +const DegToRad = Phaser.Math.DegToRad; +const PointRotateAround = Phaser.Math.RotateAround; + +export default { + rotateAround(centerX, centerY, angle) { + if (this.pathData.length === 0) { + return this; + } + + angle = DegToRad(angle); + + RotateAround(centerX, centerY, angle, this.pathData); + + var pathDataCnt = this.pathData.length; + this.lastPointX = this.pathData[pathDataCnt - 2]; + this.lastPointY = this.pathData[pathDataCnt - 1]; + if (this.lastCX !== undefined) { + var point = { + x: this.lastCX, + y: this.lastCY + } + PointRotateAround(point, centerX, centerY, angle); + this.lastCX = point.x; + this.lastCY = point.y; + } + return this; + }, + + scale(centerX, centerY, scaleX, scaleY) { + if (this.pathData.length === 0) { + return this; + } + + Scale(centerX, centerY, scaleX, scaleY, this.pathData); + this.lastPointX = this.pathData[pathDataCnt - 2]; + this.lastPointY = this.pathData[pathDataCnt - 1]; + if (this.lastCX !== undefined) { + var x = this.lastCX - centerX; + var y = this.lastCY - centerY; + x *= scaleX; + y *= scaleY; + this.lastCX = x + centerX; + this.lastCY = y + centerY; + } + return this; + }, + + offset(x, y) { + Offset(x, y, this.pathData); + return this; + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/QuadraticBezierTo.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/QuadraticBezierTo.js new file mode 100644 index 000000000..e5a162597 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/QuadraticBezierTo.js @@ -0,0 +1,19 @@ +//import QuadraticBezierInterpolation from '../../utils/math/interpolation/QuadraticBezierInterpolation.js'; + +const QuadraticBezierInterpolation = Phaser.Math.Interpolation.QuadraticBezier; + +var QuadraticBezierTo = function (cx, cy, x, y, iterations, pathData) { + var pathDataCnt = pathData.length; + var p0x = pathData[pathDataCnt - 2]; + var p0y = pathData[pathDataCnt - 1]; + for (var i = 1, last = iterations - 1; i <= last; i++) { + var t = i / last; + pathData.push( + QuadraticBezierInterpolation(t, p0x, cx, x), + QuadraticBezierInterpolation(t, p0y, cy, y) + ); + } + return pathData; +} + +export default QuadraticBezierTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/RotateAround.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/RotateAround.js new file mode 100644 index 000000000..016489e1c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/RotateAround.js @@ -0,0 +1,17 @@ +//import PointRotateAround from '../../utils/math/RotateAround.js'; + +const PointRotateAround = Phaser.Math.RotateAround; + +var RotateAround = function (centerX, centerY, angle, pathData) { + var point = { x: 0, y: 0 }; + for (var i = 0, cnt = pathData.length - 1; i < cnt; i += 2) { + point.x = pathData[i]; + point.y = pathData[i + 1]; + PointRotateAround(point, centerX, centerY, angle); + pathData[i] = point.x; + pathData[i + 1] = point.y; + } + return pathData; +} + +export default RotateAround; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/Scale.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/Scale.js new file mode 100644 index 000000000..caa491b11 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/Scale.js @@ -0,0 +1,13 @@ +var Scale = function (centerX, centerY, scaleX, scaleY, pathData) { + for (var i = 0, cnt = pathData.length - 1; i < cnt; i += 2) { + var x = pathData[i] - centerX; + var y = pathData[i + 1] - centerY; + x *= scaleX; + y *= scaleY; + pathData[i] = x + centerX; + pathData[i + 1] = y + centerY; + } + return pathData; +} + +export default Scale; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/StartAt.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/StartAt.js new file mode 100644 index 000000000..72147fdca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/StartAt.js @@ -0,0 +1,11 @@ +var StartAt = function (x, y, pathData) { + pathData.length = 0; + + if (x != null) { + pathData.push(x, y); + } + + return pathData; +} + +export default StartAt; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/ToPoints.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/ToPoints.js new file mode 100644 index 000000000..f4107e40e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/ToPoints.js @@ -0,0 +1,14 @@ +var ToPoints = function (pathData, points) { + if (points === undefined) { + points = []; + } + for (var i = 0, cnt = pathData.length - 1; i < cnt; i += 2) { + points.push({ + x: pathData[i], + y: pathData[i + 1] + }) + } + return points; +} + +export default ToPoints; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/ToPolygon.js b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/ToPolygon.js new file mode 100644 index 000000000..8c714a5dd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/pathdata/ToPolygon.js @@ -0,0 +1,13 @@ +//import Polygon from '../../utils/geom/polygon/Polygon.js'; + +const Polygon = Phaser.Geom.Polygon; + +var ToPolygon = function (pathData, polygon) { + if (polygon === undefined) { + polygon = new Polygon(); + } + polygon.setTo(pathData); + return polygon; +} + +export default ToPolygon; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/quad/Quad.js b/ui/src/phaser3-rex-plugins/plugins/geom/quad/Quad.js new file mode 100644 index 000000000..bae4df426 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/quad/Quad.js @@ -0,0 +1,191 @@ +// https://www.redblobgames.com/grids/hexagons/ + +import Offset from '../utils/Offset.js'; +import SetPoints from './SetPoints.js'; + +const Polygon = Phaser.Geom.Polygon; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const Line = Phaser.Geom.Line; + +class Quad extends Polygon { + constructor(x, y, width, height, type) { + super(); + if (IsPlainObject(x)) { + var config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 0); + height = GetValue(config, 'height', 0); + type = GetValue(config, 'type', 0); + } + var points = this.points; + for (var i = 0; i < 4; i++) { + points.push({}); + } + this.setTo(x, y, width, height, type); + } + + // override + setTo(x, y, width, height, type) { + if (typeof (type) === 'string') { + type = QUADTYPE[type]; + } + this.type = type; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + + SetPoints(this.centerX, this.centerY, width, height, type, this.points); + this.calculateArea(); + return this; + } + + get x() { + return this._x; + } + + set x(value) { + var offsetX = value - this.x; + if (offsetX === 0) { + return; + } + Offset(this, offsetX, 0); + this._x = value; + } + + get y() { + return this._y; + } + + set y(value) { + var offsetY = value - this.y; + if (offsetY === 0) { + return; + } + Offset(this, 0, offsetY); + this._y = value; + } + + setPosition(x, y) { + var offsetX = x - this.x; + var offsetY = y - this.y; + if ((offsetX === 0) && (offsetY === 0)) { + return this; + } + Offset(this, offsetX, offsetY); + this._x = x; + this._y = y; + return this; + } + + get left() { + return this.x - (this.width / 2); + } + + set left(value) { + this.x += (value - this.left); + } + + get right() { + return this.x + (this.width / 2); + } + + set right(value) { + this.x += (value - this.right); + } + + get top() { + return this.y - (this.height / 2); + } + + set top(value) { + this.y += (value - this.top); + } + + get bottom() { + return this.y + (this.height / 2); + } + + set bottom(value) { + this.y += (value - this.bottom); + } + + get centerX() { + return this.x; + } + + set centerX(value) { + this.x += (value - this.centerX); + } + + get centerY() { + return this.y; + } + + set centerY(value) { + this.y += (value - this.centerY); + } + + get width() { + return this._width; + } + + set width(value) { + this.setTo(this._x, this._y, value, this._height); + } + + get height() { + return this._height; + } + + set height(value) { + this.setTo(this._x, this._y, this._width, value); + } + + setSize(width, height) { + this.setTo(this._x, this._y, width, height); + return this; + } + + isEmpty() { + return (this.width <= 0) || (this.height <= 0); + } + + getEdge(idx, line) { + if (line === undefined) { + line = new Line(); + } + var p0 = this.points[idx]; + var p1 = this.points[(idx + 1) % 4]; + line.setTo(p0.x, p0.y, p1.x, p1.y); + return line; + } + + getLineA(line) { + return this.getEdge(0, line); + } + + getLineB(line) { + return this.getEdge(1, line); + } + + getLineC(line) { + return this.getEdge(2, line); + } + + getLineD(line) { + return this.getEdge(3, line); + } +} + +const QUADTYPE = { + 'rectangle': 0, + 'rhombus': 1 +} + +// use `rexQuad` to prevent name conflict +Phaser.Geom.rexQuad = Quad; + +export default Quad; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/quad/SetPoints.js b/ui/src/phaser3-rex-plugins/plugins/geom/quad/SetPoints.js new file mode 100644 index 000000000..38e41e1b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/quad/SetPoints.js @@ -0,0 +1,41 @@ +import InitPoints from '../utils/InitPoints.js'; + +var SetPoints = function (x, y, width, height, type, points) { + if (points === undefined) { + points = InitPoints(4); + } + + var halfW = width / 2; + var halfH = height / 2; + + if (type === 0) { // rectangle + // top-right + points[0].x = x + halfW; + points[0].y = y - halfH; + // bottom-right + points[1].x = x + halfW; + points[1].y = y + halfH; + // bottom-left + points[2].x = x - halfW; + points[2].y = y + halfH; + // top-left + points[3].x = x - halfW; + points[3].y = y - halfH; + } else { // rhombus + // 0 + points[0].x = x + halfW; + points[0].y = y; + // 90 + points[1].x = x; + points[1].y = y + halfH; + // 180 + points[2].x = x - halfW; + points[2].y = y; + // 270 + points[3].x = x; + points[3].y = y - halfH; + } + return points; +} + +export default SetPoints; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/rhombus/Rhombus.js b/ui/src/phaser3-rex-plugins/plugins/geom/rhombus/Rhombus.js new file mode 100644 index 000000000..8cf05ff3b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/rhombus/Rhombus.js @@ -0,0 +1,196 @@ +// https://www.redblobgames.com/grids/hexagons/ + +import Offset from '../utils/Offset.js'; + +const Polygon = Phaser.Geom.Polygon; +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; +const Line = Phaser.Geom.Line; + +class Rhombus extends Polygon { + constructor(x, y, width, height) { + super(); + if (IsPlainObject(x)) { + var config = x; + x = GetValue(config, 'x', 0); + y = GetValue(config, 'y', 0); + width = GetValue(config, 'width', 0); + height = GetValue(config, 'height', 0); + } + var points = this.points; + for (var i = 0; i < 4; i++) { + points.push({}); + } + this.setTo(x, y, width, height); + } + + // override + setTo(x, y, width, height) { + this._x = x; + this._y = y; + this._width = width; + this._height = height; + var points = this.points; + + var centerX = this.centerX, + centerY = this.centerY; + var helfWidth = width / 2; + var helfHeight = height / 2; + // 0 + points[0].x = centerX + helfWidth; + points[0].y = centerY; + // 90 + points[1].x = centerX; + points[1].y = centerY + helfHeight; + // 180 + points[2].x = centerX - helfWidth; + points[2].y = centerY; + // 270 + points[3].x = centerX; + points[3].y = centerY - helfHeight; + this.calculateArea(); + return this; + } + + get x() { + return this._x; + } + + set x(value) { + var offsetX = value - this.x; + if (offsetX === 0) { + return; + } + Offset(this, offsetX, 0); + this._x = value; + } + + get y() { + return this._y; + } + + set y(value) { + var offsetY = value - this.y; + if (offsetY === 0) { + return; + } + Offset(this, 0, offsetY); + this._y = value; + } + + setPosition(x, y) { + var offsetX = x - this.x; + var offsetY = y - this.y; + if ((offsetX === 0) && (offsetY === 0)) { + return this; + } + Offset(this, offsetX, offsetY); + this._x = x; + this._y = y; + return this; + } + + get left() { + return this.x; + } + + set left(value) { + this.x += (value - this.left); + } + + get right() { + return this.x + this.width; + } + + set right(value) { + this.x += (value - this.right); + } + + get top() { + return this.y; + } + + set top(value) { + this.y += (value - this.top); + } + + get bottom() { + return this.y + this.height; + } + + set bottom(value) { + this.y += (value - this.bottom); + } + + get centerX() { + return this.x + (this.width / 2); + } + + set centerX(value) { + this.x += (value - this.centerX); + } + + get centerY() { + return this.y + (this.height / 2); + } + + set centerY(value) { + this.y += (value - this.centetY); + } + + get width() { + return this._width; + } + + set width(value) { + this.setTo(this._x, this._y, value, this._height); + } + + get height() { + return this._height; + } + + set height(value) { + this.setTo(this._x, this._y, this._width, value); + } + + setSize(width, height) { + this.setTo(this._x, this._y, width, height); + return this; + } + + isEmpty() { + return (this.width <= 0) || (this.height <= 0); + } + + getEdge(idx, line) { + if (line === undefined) { + line = new Line(); + } + var p0 = this.points[idx]; + var p1 = this.points[(idx + 1) % 4]; + line.setTo(p0.x, p0.y, p1.x, p1.y); + return line; + } + + getLineA(line) { + return this.getEdge(0, line); + } + + getLineB(line) { + return this.getEdge(1, line); + } + + getLineC(line) { + return this.getEdge(2, line); + } + + getLineD(line) { + return this.getEdge(3, line); + } +} + +// use `rexRhombus` to prevent name conflict +Phaser.Geom.rexRhombus = Rhombus; + +export default Rhombus; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/roundrectangle/RoundRectangle.js b/ui/src/phaser3-rex-plugins/plugins/geom/roundrectangle/RoundRectangle.js new file mode 100644 index 000000000..6656cde90 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/roundrectangle/RoundRectangle.js @@ -0,0 +1,177 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +class RoundRectangle { + constructor(x, y, width, height, radiusConfig) { + if (x === undefined) { x = 0; } + if (y === undefined) { y = x; } + if (width === undefined) { width = 0; } + if (height === undefined) { height = 0; } + if (radiusConfig === undefined) { radiusConfig = 0; } + + this.cornerRadius = {}; + this._width = 0; + this._height = 0; + this.setTo(x, y, width, height, radiusConfig); + } + + setTo(x, y, width, height, radiusConfig) { + this.setPosition(x, y); + this.setRadius(radiusConfig); + this.setSize(width, height); + return this; + } + + setPosition(x, y) { + this.x = x; + this.y = y; + return this; + } + + setRadius(value) { + if (value === undefined) { + value = 0; + } + this.radius = value; + return this; + } + + setSize(width, height) { + this.width = width; + this.height = height; + return this; + } + + get minWidth() { + var radius = this.cornerRadius; + return Math.max(radius.tl.x + radius.tr.x, radius.bl.x + radius.br.x); + } + + get minHeight() { + var radius = this.cornerRadius; + return Math.max(radius.tl.y + radius.bl.y, radius.tr.y + radius.br.y); + } + + get width() { + return this._width; + } + + set width(value) { + if (value == null) { + value = 0; + } + this._width = Math.max(value, this.minWidth); + } + + get height() { + return this._height; + } + + set height(value) { + if (value == null) { + value = 0; + } + this._height = Math.max(value, this.minHeight); + } + + get radius() { + var radius = this.cornerRadius; + return Math.max( + radius.tl.x, radius.tl.y, + radius.tr.x, radius.tr.y, + radius.bl.x, radius.bl.y, + radius.br.x, radius.br.y + ); + } + + set radius(value) { + var defaultRadiusX, defaultRadiusY; + if (typeof (value) === 'number') { + defaultRadiusX = value; + defaultRadiusY = value; + } else { + defaultRadiusX = GetValue(value, 'x', 0); + defaultRadiusY = GetValue(value, 'y', 0); + } + + var radius = this.cornerRadius; + radius.tl = GetRadius(GetValue(value, 'tl', undefined), defaultRadiusX, defaultRadiusY); + radius.tr = GetRadius(GetValue(value, 'tr', undefined), defaultRadiusX, defaultRadiusY); + radius.bl = GetRadius(GetValue(value, 'bl', undefined), defaultRadiusX, defaultRadiusY); + radius.br = GetRadius(GetValue(value, 'br', undefined), defaultRadiusX, defaultRadiusY); + } + + get radiusTL() { + var radius = this.cornerRadius.tl; + return Math.max(radius.x, radius.y); + } + + set radiusTL(value) { + SetRadius(this.cornerRadius.tl, value); + } + + get radiusTR() { + var radius = this.cornerRadius.tr; + return Math.max(radius.x, radius.y); + } + + set radiusTR(value) { + SetRadius(this.cornerRadius.tr, value); + } + + get radiusBL() { + var radius = this.cornerRadius.bl; + return Math.max(radius.x, radius.y); + } + + set radiusBL(value) { + SetRadius(this.cornerRadius.bl, value); + } + + get radiusBR() { + var radius = this.cornerRadius.br; + return Math.max(radius.x, radius.y); + } + + set radiusBR(value) { + SetRadius(this.cornerRadius.br, value); + } +} + +var GetRadius = function (radius, defaultRadiusX, defaultRadiusY) { + if (radius === undefined) { + radius = { + x: defaultRadiusX, + y: defaultRadiusY + }; + } else if (typeof (radius) === 'number') { + radius = { + x: radius, + y: radius + }; + } + + SetConvex(radius); + return radius; + +} + +var SetRadius = function (radius, value) { + if (typeof (value) === 'number') { + radius.x = value; + radius.y = value; + } else { + radius.x = GetValue(value, 'x', 0); + radius.y = GetValue(value, 'y', 0); + } + + SetConvex(radius); +} + +var SetConvex = function (radius) { + radius.convex = (radius.x >= 0) || (radius.y >= 0); + + radius.x = Math.abs(radius.x); + radius.y = Math.abs(radius.y); +} + +export default RoundRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/utils/GetPoint.js b/ui/src/phaser3-rex-plugins/plugins/geom/utils/GetPoint.js new file mode 100644 index 000000000..9e31efa26 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/utils/GetPoint.js @@ -0,0 +1,56 @@ +const DistanceBetween = Phaser.Math.Distance.Between; +const Linear = Phaser.Math.Linear; + +var GetPoint = function (t, points, lengths, out) { + // points: [x0, y0, x1, y1, ....] + // lengths: [length01, length12, ...] + + if (out === undefined) { + out = {}; + } + + if (lengths === undefined) { + lengths = []; + } + + if (lengths.length === 0) { + var p0x, p0y, p1x, p1y; + for (var i = 0, cnt = points.length; i < cnt; i += 2) { + p1x = points[i]; + p1y = points[i + 1]; + + if (i > 0) { + lengths.push(DistanceBetween(p0x, p0y, p1x, p1y)); + } + + p0x = p1x; + p0y = p1y; + } + } + + var totalLength = lengths.reduce((a, b) => a + b, 0); + + var segmentIndex, + remainderLength = t * totalLength; + for (var i = 0, cnt = lengths.length; i < cnt; i++) { + remainderLength -= lengths[i]; + if (remainderLength <= 0) { + segmentIndex = i; + remainderLength = Math.abs(remainderLength) + break; + } + } + + var p0x = points[(segmentIndex * 2)], + p0y = points[(segmentIndex * 2) + 1], + p1x = points[(segmentIndex * 2) + 2], + p1y = points[(segmentIndex * 2) + 3], + segT = 1 - (remainderLength / lengths[segmentIndex]); + + out.x = Linear(p0x, p1x, segT); + out.y = Linear(p0y, p1y, segT); + + return out; +} + +export default GetPoint; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/utils/InitPoints.js b/ui/src/phaser3-rex-plugins/plugins/geom/utils/InitPoints.js new file mode 100644 index 000000000..c0ff74096 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/utils/InitPoints.js @@ -0,0 +1,12 @@ +var InitPoints = function (count) { + var points = []; + for (var i = 0; i < count; i++) { + points.push({ + x: 0, + y: 0 + }); + } + return points; +} + +export default InitPoints; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/utils/Offset.js b/ui/src/phaser3-rex-plugins/plugins/geom/utils/Offset.js new file mode 100644 index 000000000..73ee5def2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/utils/Offset.js @@ -0,0 +1,12 @@ +var Offset = function (polygon, x, y) { + var points = polygon.points, + point; + for (var i = 0, cnt = points.length; i < cnt; i++) { + point = points[i]; + point.x += x; + point.y += y; + } + return polygon; +}; + +export default Offset; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/geom/utils/RotateAround.js b/ui/src/phaser3-rex-plugins/plugins/geom/utils/RotateAround.js new file mode 100644 index 000000000..c01cc3acb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/geom/utils/RotateAround.js @@ -0,0 +1,11 @@ +const PointRotateAround = Phaser.Math.RotateAround; + +var RotateAround = function (polygon, centerX, centerY, angle) { + var points = polygon.points; + for (var i = 0, cnt = points.length; i < cnt; i++) { + PointRotateAround(points[i], centerX, centerY, angle); + } + return polygon; +}; + +export default RotateAround; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gestures-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/gestures-plugin.d.ts new file mode 100644 index 000000000..58706ee73 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gestures-plugin.d.ts @@ -0,0 +1,38 @@ +import TapFactory from './input/gestures/tap/Factory'; +import PressFactory from './input/gestures/press/Factory'; +import PanFactory from './input/gestures/pan/Factory'; +import SwipeFactory from './input/gestures/swipe/Factory'; +import PinchFactory from './input/gestures/pinch/Factory'; +import RotateFactory from './input/gestures/rotate/Factory'; + +export default GesturesPlugin; + +declare class Factories { + tap: typeof TapFactory; + press: typeof PressFactory; + pan: typeof PanFactory; + swipe: typeof SwipeFactory; + pinch: typeof PinchFactory; + rotate: typeof RotateFactory; +} + +declare class GesturesPlugin extends Phaser.Plugins.BasePlugin { + add: Factories; +} + +import TapClass from './input/gestures/tap/Tap.js'; +import PressClass from './input/gestures/press/Press.js'; +import PanClass from './input/gestures/pan/Pan.js'; +import SwipeClass from './input/gestures/swipe/Swipe.js'; +import PinchClass from './input/gestures/pinch/Pinch.js'; +import RotateClass from './input/gestures/rotate/Rotate.js'; + +declare namespace GesturesPlugin { + type Tap = TapClass; + type Press = PressClass; + type Pan = PanClass; + type Swipe = SwipeClass; + type Pinch = PinchClass; + type Rotate = RotateClass; + +} diff --git a/ui/src/phaser3-rex-plugins/plugins/gestures-plugin.js b/ui/src/phaser3-rex-plugins/plugins/gestures-plugin.js new file mode 100644 index 000000000..a0882df8e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gestures-plugin.js @@ -0,0 +1,18 @@ +import ObjectFactory from './input/gestures/ObjectFactory.js'; + +import TapFactory from './input/gestures/tap/Factory.js'; +import PressFactory from './input/gestures/press/Factory.js'; +import PanFactory from './input/gestures/pan/Factory.js'; +import SwipeFactory from './input/gestures/swipe/Factory.js'; +import PinchFactory from './input/gestures/pinch/Factory.js'; +import RotateFactory from './input/gestures/rotate/Factory.js'; + +class GesturesPlugin extends Phaser.Plugins.ScenePlugin { + constructor(scene, pluginManager) { + super(scene, pluginManager); + + this.add = new ObjectFactory(scene); + } +} + +export default GesturesPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gestures.d.ts b/ui/src/phaser3-rex-plugins/plugins/gestures.d.ts new file mode 100644 index 000000000..4368f2122 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gestures.d.ts @@ -0,0 +1,15 @@ +import Tap from './input/gestures/tap/Tap'; +import Press from './input/gestures/press/Press'; +import Pan from './input/gestures/pan/Pan'; +import Swipe from './input/gestures/swipe/Swipe'; +import Pinch from './input/gestures/pinch/Pinch'; +import Rotate from './input/gestures/rotate/Rotate'; + +export { + Tap, + Press, + Pan, + Swipe, + Pinch, + Rotate +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gestures.js b/ui/src/phaser3-rex-plugins/plugins/gestures.js new file mode 100644 index 000000000..7430ab881 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gestures.js @@ -0,0 +1,15 @@ +import Tap from './input/gestures/tap/Tap.js'; +import Press from './input/gestures/press/Press.js'; +import Pan from './input/gestures/pan/Pan.js'; +import Swipe from './input/gestures/swipe/Swipe.js'; +import Pinch from './input/gestures/pinch/Pinch.js'; +import Rotate from './input/gestures/rotate/Rotate.js'; + +export { + Tap, + Press, + Pan, + Swipe, + Pinch, + Rotate +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline-plugin.d.ts new file mode 100644 index 000000000..1cfe8916a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline-plugin.d.ts @@ -0,0 +1,38 @@ +// import * as Phaser from 'phaser'; +import GlowFilterPostFxPipeline from './glowfilter2pipeline'; + +export default GlowFilterPipelinePlugin; + +declare namespace GlowFilterPipelinePlugin { + + interface IConfig extends GlowFilterPostFxPipeline.IConfig { + quality?: number, + distance?: number, + + name?: string, + } + +} + +declare class GlowFilterPipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: GlowFilterPipelinePlugin.IConfig + ): GlowFilterPostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): GlowFilterPostFxPipeline | GlowFilterPostFxPipeline[]; + + setQuality(value: number): this; + quality: number; + + setDistance(value: number): this; + distance: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline-plugin.js new file mode 100644 index 000000000..1d782a037 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline-plugin.js @@ -0,0 +1,48 @@ +import GlowFilterPostFxPipeline from './glowfilter2pipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class GlowFilterPipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(GlowFilterPostFxPipeline, 'rexGlowFilter2PostFx'); + } + + add(gameObject, config) { + this.setQuality(GetValue(config, 'quality', this.quality)); + this.setDistance(GetValue(config, 'distance', this.distance)); + return super.add(gameObject, config); + } + + setQuality(value) { + GlowFilterPostFxPipeline.setQuality(value); + return this; + } + + set quality(value) { + this.setQuality(value); + } + + get quality() { + return GlowFilterPostFxPipeline.getQuality(); + } + + setDistance(value) { + GlowFilterPostFxPipeline.setDistance(value); + return this; + } + + set distance(value) { + this.setDistance(value); + } + + get distance() { + return GlowFilterPostFxPipeline.getDistance(); + } +} + +SetValue(window, 'RexPlugins.Pipelines.GlowFilter2PostFx', GlowFilterPostFxPipeline); + +export default GlowFilterPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline.d.ts new file mode 100644 index 000000000..6d498486f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline.d.ts @@ -0,0 +1,2 @@ +import GlowFilterPostFxPipeline from './shaders/glowfilter2/GlowFilterPostFxPipeline'; +export default GlowFilterPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline.js b/ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline.js new file mode 100644 index 000000000..9bf7a7348 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/glowfilter2pipeline.js @@ -0,0 +1,2 @@ +import GlowFilterPostFxPipeline from './shaders/glowfilter2/GlowFilterPostFxPipeline.js'; +export default GlowFilterPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline-plugin.d.ts new file mode 100644 index 000000000..30220bb36 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline-plugin.d.ts @@ -0,0 +1,29 @@ +// import * as Phaser from 'phaser'; +import GlowFilterPostFxPipeline from './glowfilterpipeline'; + +export default GlowFilterPipelinePlugin; + +declare namespace GlowFilterPipelinePlugin { + + interface IConfig extends GlowFilterPostFxPipeline.IConfig { + name?: string, + } + +} + +declare class GlowFilterPipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: GlowFilterPipelinePlugin.IConfig + ): GlowFilterPostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): GlowFilterPostFxPipeline | GlowFilterPostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline-plugin.js new file mode 100644 index 000000000..2c5346406 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline-plugin.js @@ -0,0 +1,14 @@ +import GlowFilterPostFxPipeline from './glowfilterpipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class GlowFilterPipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(GlowFilterPostFxPipeline, 'rexGlowFilterPostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.GlowFilterPostFx', GlowFilterPostFxPipeline); + +export default GlowFilterPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline.d.ts new file mode 100644 index 000000000..dca91ed27 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline.d.ts @@ -0,0 +1,2 @@ +import GlowFilterPostFxPipeline from './shaders/glowfilter/GlowFilterPostFxPipeline'; +export default GlowFilterPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline.js b/ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline.js new file mode 100644 index 000000000..cb3f5a294 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/glowfilterpipeline.js @@ -0,0 +1,2 @@ +import GlowFilterPostFxPipeline from './shaders/glowfilter/GlowFilterPostFxPipeline.js'; +export default GlowFilterPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph-plugin.js b/ui/src/phaser3-rex-plugins/plugins/graph-plugin.js new file mode 100644 index 000000000..a7a34dfc4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph-plugin.js @@ -0,0 +1,13 @@ +import ObjectFactory from './graph/ObjectFactory.js'; + +import GraphFactory from './graph/graph/Factory.js'; + +class GraphPlugin extends Phaser.Plugins.ScenePlugin { + constructor(scene, pluginManager) { + super(scene, pluginManager); + + this.add = new ObjectFactory(scene); + } +} + +export default GraphPlugin; diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/ObjectFactory.js b/ui/src/phaser3-rex-plugins/plugins/graph/ObjectFactory.js new file mode 100644 index 000000000..1a385b273 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/ObjectFactory.js @@ -0,0 +1,10 @@ +class ObjectFactory { + constructor(scene) { + this.scene = scene; + } + + static register(type, callback) { + ObjectFactory.prototype[type] = callback; + } +}; +export default ObjectFactory; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/Factory.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/Factory.js new file mode 100644 index 000000000..edcc7a74b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/Factory.js @@ -0,0 +1,11 @@ +import Graph from './Graph.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../utils/object/SetValue.js'; + +ObjectFactory.register('graph', function (config) { + return new Graph(this.scene, config); +}); + +SetValue(window, 'RexPlugins.Graph.Graph', Graph); + +export default Graph; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/Graph.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/Graph.js new file mode 100644 index 000000000..ac9b62074 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/Graph.js @@ -0,0 +1,87 @@ +import EE from '../../utils/eventemitter/EventEmitter.js'; +import Methods from './Methods.js'; +import GetObjUID from '../graphitem/GetObjUID.js'; + +class Graph extends EE { + constructor(scene) { + // scene: scene instance, or undefined + super(); + + this.isShutdown = false; + this.scene = scene; + this.vertices = {}; // {vertex: {edge:vertexUidB, ...} } + this.edges = {}; // {edge: {vA:vertex, vB:vertex, dir:1,2,3} } + this.vertexCount = 0; + this.edgeCount = 0; + + this.boot(); + } + + boot() { + if (this.scene) { + this.scene.sys.events.once('shutdown', this.destroy, this); + } + } + + shutdown(fromScene) { + if (this.isShutdown) { + return; + } + + if (this.scene) { + this.scene.sys.events.off('shutdown', this.destroy, this); + } + + this.clear(); + super.shutdown(); + + this.scene = undefined; + this.vertices = undefined; + this.edges = undefined; + this.vertexCount = 0; + this.edgeCount = 0; + this.isShutdown = true; + return this; + } + + destroy(fromScene) { + if (this.isShutdown) { + return; + } + + this.emit('destroy'); + this.shutdown(fromScene); + } + + exists(gameObject) { + return this.isEdge(gameObject) || this.isVertex(gameObject); + } + + remove(gameObject) { + if (this.isEdge(gameObject)) { + this.removeEdge(gameObject); + } else if (this.isVertex(gameObject)) { + this.removeVertex(gameObject); + } + return this; + } + + clear(destroy) { + if (destroy === undefined) { + destroy = true; + } + this.removeAllVertices(destroy); + return this; + } + + getObjUID(gameObject) { + return GetObjUID(gameObject); + } +} + +Object.assign( + Graph.prototype, + Methods +); + +export default Graph; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/Methods.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/Methods.js new file mode 100644 index 000000000..694d8f815 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/Methods.js @@ -0,0 +1,47 @@ +import GetEdgeData from './edge/GetEdgeData.js'; +import IsEdge from './edge/IsEdge.js'; +import AddEdge from './edge/AddEdge.js'; +import RemoveEdge from './edge/RemoveEdge.js'; +import GetAllEdges from './edge/GetAllEdges.js'; +import GetEdgesOfVertex from './edge/GetEdgesOfVertex.js'; +import GetEdgeLength from './edge/GetEdgeLength.js'; +import IsInLoop from './edge/IsInLoop.js'; + +import GetVertexData from './vertex/GetVertexData.js'; +import IsVertex from './vertex/IsVertex.js'; +import AddVertex from './vertex/AddVertex.js'; +import AddVertices from './vertex/AddVertices.js'; +import RemoveVertex from './vertex/RemoveVertex.js'; +import RemoveAllVertices from './vertex/RemoveAllVertices.js'; +import GetAllVertices from './vertex/GetAllVertices.js'; +import GetVerticesOfEdge from './vertex/GetVerticesOfEdge.js'; +import GetOppositeVertex from './vertex/GetOppositeVertex.js'; +import GetAllConnectedVertices from './vertex/GetAllConnectedVertices.js'; + +import GetNeighborVertices from './neighbors/GetNeighborVertices.js'; +import AreNeighborVertices from './neighbors/AreNeighborVertices.js'; + +export default { + getEdgeData: GetEdgeData, + isEdge: IsEdge, + addEdge: AddEdge, + removeEdge: RemoveEdge, + getAllEdges: GetAllEdges, + getEdgesOfVertex: GetEdgesOfVertex, + getEdgeLength: GetEdgeLength, + isInLoop: IsInLoop, + + getVertexData: GetVertexData, + isVertex: IsVertex, + addVertex: AddVertex, + addVertices: AddVertices, + removeVertex: RemoveVertex, + removeAllVertices: RemoveAllVertices, + getAllVertices: GetAllVertices, + getVerticesOfEdge: GetVerticesOfEdge, + getOppositeVertex: GetOppositeVertex, + getAllConnectedVertices: GetAllConnectedVertices, + + getNeighborVertices: GetNeighborVertices, + areNeighborVertices: AreNeighborVertices, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/AddEdge.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/AddEdge.js new file mode 100644 index 000000000..a220a89be --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/AddEdge.js @@ -0,0 +1,45 @@ +import GetGraphItem from '../../graphitem/GetGraphItem.js'; + +const DIRAtoB = 1; +const DIRBtoA = 2; +const DIRMODE = { + '->': DIRAtoB, + '<-': DIRBtoA, + '<->': (DIRAtoB | DIRBtoA), +}; + +var AddEdge = function (edgeGO, vAGO, vBGO, dir) { + if (this.isEdge(edgeGO)) { + return this; + } + + if (dir === undefined) { + dir = 3; + } + + // Configure edge + var edgeUid = this.getObjUID(edgeGO); + var edge = this.getEdgeData(edgeUid, true); + edge.dir = dir; + edge.vA = this.getObjUID(vAGO); + edge.vB = this.getObjUID(vBGO); + GetGraphItem(edgeGO).setGraph(this); + this.edgeCount++; + + // Configure vertice + this.addVertex(vAGO).addVertex(vBGO); + var vA = this.getVertexData(vAGO, true); + var vB = this.getVertexData(vBGO, true); + if (typeof (dir) === 'string') { + dir = DIRMODE(dir); + } + if (dir & DIRAtoB) { + vA[edgeUid] = edge.vB; + } + if (dir & DIRBtoA) { + vB[edgeUid] = edge.vA; + } + return this; +} + +export default AddEdge; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetAllEdges.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetAllEdges.js new file mode 100644 index 000000000..3c955fa9a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetAllEdges.js @@ -0,0 +1,18 @@ +import UidToObj from '../../graphitem/UidToObj.js'; + +var GetAllEdges = function (out) { + if (out === undefined) { + out = []; + } + + var edgeGO; + for (var edgeUid in this.edges) { + edgeGO = UidToObj(edgeUid); + if (edgeGO) { + out.push(edgeGO); + } + } + return out; +}; + +export default GetAllEdges; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetEdgeData.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetEdgeData.js new file mode 100644 index 000000000..a1cd7019f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetEdgeData.js @@ -0,0 +1,14 @@ +var GetEdgeData = function (gameObejct, createIfNotExisted) { + if (createIfNotExisted === undefined) { + createIfNotExisted = false; + } + + // uid or game object + var uid = this.getObjUID(gameObejct); + if (createIfNotExisted && !this.edges.hasOwnProperty(uid)) { + this.edges[uid] = {}; + } + return this.edges[uid]; +}; + +export default GetEdgeData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetEdgeLength.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetEdgeLength.js new file mode 100644 index 000000000..789da9da2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetEdgeLength.js @@ -0,0 +1,18 @@ +import UidToObj from '../../graphitem/UidToObj.js'; +import DistanceBetween from '../../../utils/math/distance/DistanceBetween.js'; + +var GetEdgeLength = function (gameObejct) { + var edge = this.getEdgeData(gameObejct); + if (!edge) { + return 0; + } + var vAGO = UidToObj(edge.vA); + var vBGO = UidToObj(edge.vB); + if ((!vAGO) || (!vBGO)) { + return 0; + } + + return DistanceBetween(vAGO.x, vAGO.y, vBGO.x, vBGO.y); +}; + +export default GetEdgeLength; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetEdgesOfVertex.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetEdgesOfVertex.js new file mode 100644 index 000000000..6db1b5733 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/GetEdgesOfVertex.js @@ -0,0 +1,23 @@ +import UidToObj from '../../graphitem/UidToObj.js'; + +var GetEdgesOfVertex = function (vertexGameObject, out) { + if (out === undefined) { + out = []; + } + + var vertex = this.getVertexData(vertexGameObject); + if (!vertex) { + return out; + } + + var edgeGO; + for (var edgeUid in vertex) { + edgeGO = UidToObj(edgeUid); + if (edgeGO) { + out.push(edgeGO); + } + } + return out; +}; + +export default GetEdgesOfVertex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/IsEdge.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/IsEdge.js new file mode 100644 index 000000000..83c7cb30c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/IsEdge.js @@ -0,0 +1,7 @@ +var IsEdge = function (gameObejct) { + // uid or game object + var uid = this.getObjUID(gameObejct); + return this.edges.hasOwnProperty(uid); +} + +export default IsEdge; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/IsInLoop.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/IsInLoop.js new file mode 100644 index 000000000..92d28cfd6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/IsInLoop.js @@ -0,0 +1,34 @@ +var IsInLoop = function (vertexGO) { + if (!this.isVertex(vertexGO)) { + return false; + } + + var startVUid = this.getObjUID(vertexGO); + var queue = [[startVUid, null]]; + var node, curVUid, edgeUID, edges, nextVUid; + var addedEdgesUid = {}; + while (queue.length > 0) { + node = queue.pop(); + curVUid = node[0]; + edgeUID = node[1]; + if ((curVUid === startVUid) && (edgeUID !== null)) { + return true; + } + + if (edgeUID !== null) { + addedEdgesUid[edgeUID] = true; + } + edges = this.getVertexData(curVUid); + for (edgeUID in edges) { + if (addedEdgesUid.hasOwnProperty(edgeUID)) { + continue; + } + + nextVUid = edges[edgeUID]; + queue.push([nextVUid, edgeUID]); + } + } + return false; +} + +export default IsInLoop; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/RemoveEdge.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/RemoveEdge.js new file mode 100644 index 000000000..e3dd8c754 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/edge/RemoveEdge.js @@ -0,0 +1,24 @@ +import GetGraphItem from '../../graphitem/GetGraphItem.js'; + +var RemoveEdge = function (gameObejct, destroy) { + if (this.isEdge(gameObejct)) { + return this; + } + + if (destroy === undefined) { + destroy = false; + } + + var uid = this.getObjUID(gameObejct); + // Remove edge + delete this.edges[uid]; + this.edgeCount--; + // Clear reference of graph + GetGraphItem(gameObejct).setGraph(null); + if (destroy && gameObejct.destroy) { + gameObject.destroy(); + } + return this; +} + +export default RemoveEdge; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/neighbors/AreNeighborVertices.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/neighbors/AreNeighborVertices.js new file mode 100644 index 000000000..4e5e6d0c9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/neighbors/AreNeighborVertices.js @@ -0,0 +1,16 @@ +var AreNeighborVertices = function (vertexGOA, vertexGOB) { + var vUidA = this.getObjUID(vertexGOA), + vUidB = this.getObjUID(vertexGOB); + if ((vUidA != null) && (vUidB != null)) { + var vertexA = this.getVertexData(vertexGOA); + vUidB = parseInt(vUidB); + for (var edgeUid in vertexA) { + if (vertexA[edgeUid] === vUidB) { + return true; + } + } + } + return false; +} + +export default AreNeighborVertices; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/neighbors/GetNeighborVertices.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/neighbors/GetNeighborVertices.js new file mode 100644 index 000000000..1426a4c48 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/neighbors/GetNeighborVertices.js @@ -0,0 +1,21 @@ +import UidToObj from '../../graphitem/UidToObj.js'; + +var GetNeighborVertices = function (vAGO, out) { + if (out === undefined) { + out = []; + } + + var vertex = this.getVertexData(vAGO), + vBGO; + if (vertex) { + for (var edgeUid in vertex) { + vBGO = UidToObj(vertex[edgeUid]); + if (vBGO) { + out.push(vBGO); + } + } + } + return out; +}; + +export default GetNeighborVertices; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/AddVertex.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/AddVertex.js new file mode 100644 index 000000000..277922dab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/AddVertex.js @@ -0,0 +1,14 @@ +import GetGraphItem from '../../graphitem/GetGraphItem.js'; + +var AddVertex = function (gameObejct) { + if (this.isVertex(gameObejct)) { + return this; + } + + this.getVertexData(gameObejct, true); + GetGraphItem(gameObejct).setGraph(this); + this.vertexCount++; + return this; +}; + +export default AddVertex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/AddVertices.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/AddVertices.js new file mode 100644 index 000000000..01327f337 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/AddVertices.js @@ -0,0 +1,8 @@ +var AddVertices = function (gameObjects) { + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + this.addVertex(gameObjects[i]); + } + return this; +} + +export default AddVertices; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetAllConnectedVertices.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetAllConnectedVertices.js new file mode 100644 index 000000000..5e9ed610f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetAllConnectedVertices.js @@ -0,0 +1,55 @@ +import UidToObj from '../../graphitem/UidToObj.js'; + +var GetAllConnectedVertices = function (vertexGO, out, travelMode) { + if (out === undefined) { + out = []; + } + if (typeof (travelMode) === 'string') { + travelMode = TRAVELMODE[travelMode]; + } + if (travelMode === undefined) { + travelMode = 0; + } + + if (!this.isVertex(vertexGO)) { + return out; + } + + var startVUid = this.getObjUID(vertexGO); + var isBFS = (travelMode === 0); + var queue = [startVUid]; + var curVUid, edges, nextVUid; + var addedVerticesUid = {}; + while (queue.length > 0) { + curVUid = (isBFS) ? queue.shift() : queue.pop(); + // Already added + if (addedVerticesUid.hasOwnProperty(curVUid)) { + continue; + } + + addedVerticesUid[curVUid] = true; + if (curVUid !== startVUid) { + out.push(UidToObj(curVUid)); // Add vertex into out + } + + // Add new neighbors into queue + edges = this.getVertexData(curVUid); + for (var edgeUid in edges) { + nextVUid = this.getOppositeVertex(curVUid, edgeUid); + if (!addedVerticesUid.hasOwnProperty(nextVUid)) { + queue.push(nextVUid); + } + } + } + + return out; +} + +const TRAVELMODE = { + 'breadth-first': 0, + 'bfs': 0, + 'depth-first': 1, + 'dfs': 1, +} + +export default GetAllConnectedVertices; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetAllVertices.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetAllVertices.js new file mode 100644 index 000000000..59fe16fe4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetAllVertices.js @@ -0,0 +1,18 @@ +import UidToObj from '../../graphitem/UidToObj.js'; + +var GetAllVertices = function (out) { + if (out === undefined) { + out = []; + } + + var vGO; + for (var vUid in this.vertices) { + vGO = UidToObj(vUid); + if (vGO) { + out.push(vGO); + } + } + return out; +}; + +export default GetAllVertices; diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetOppositeVertex.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetOppositeVertex.js new file mode 100644 index 000000000..e62f89e0b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetOppositeVertex.js @@ -0,0 +1,18 @@ +import UidToObj from '../../graphitem/UidToObj.js'; + +var GetOppositeVertex = function (vertexGameObject, edgeGameObject) { + // uid or game object + var vertex = this.getVertexData(vertexGameObject); + if (!vertex) { + return undefined; + } + + var edgeUid = this.getObjUID(edgeGameObject); + if (!edgeUid) { + return undefined; + } + + return UidToObj(vertex[edgeUid]); +}; + +export default GetOppositeVertex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetVertexData.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetVertexData.js new file mode 100644 index 000000000..9ba9653ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetVertexData.js @@ -0,0 +1,14 @@ +var GetVertexData = function (gameObejct, createIfNotExisted) { + if (createIfNotExisted === undefined) { + createIfNotExisted = false; + } + + // uid or game object + var uid = this.getObjUID(gameObejct); + if (createIfNotExisted && !this.vertices.hasOwnProperty(uid)) { + this.vertices[uid] = {}; + } + return this.vertices[uid]; +}; + +export default GetVertexData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetVerticesOfEdge.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetVerticesOfEdge.js new file mode 100644 index 000000000..0bc74eb92 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/GetVerticesOfEdge.js @@ -0,0 +1,26 @@ +import UidToObj from '../../graphitem/UidToObj.js'; + +var GetVerticesOfEdge = function (edgeGameObject, out) { + if (out === undefined) { + out = []; + } + + // uid or game object + var edge = this.getEdgeData(edgeGameObject); + if (!edge) { + return out; + } + + var vGO; + vGO = UidToObj(edge.vA); + if (vGO) { + out.push(vGO); + } + vGO = UidToObj(edge.vB); + if (vGO) { + out.push(vGO); + } + return out; +}; + +export default GetVerticesOfEdge; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/IsVertex.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/IsVertex.js new file mode 100644 index 000000000..8960b3acc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/IsVertex.js @@ -0,0 +1,7 @@ +var IsVertex = function (gameObejct) { + // uid or game object + var uid = this.getObjUID(gameObejct); + return this.vertices.hasOwnProperty(uid); +} + +export default IsVertex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/RemoveAllVertices.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/RemoveAllVertices.js new file mode 100644 index 000000000..de77a0914 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/RemoveAllVertices.js @@ -0,0 +1,8 @@ +var RemoveAllVertices = function (destroy) { + for (var vertexUid in this.vertices) { + this.removeVertex(vertexUid, destroy) + } + return this; +}; + +export default RemoveAllVertices; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/RemoveVertex.js b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/RemoveVertex.js new file mode 100644 index 000000000..86c900bf8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graph/vertex/RemoveVertex.js @@ -0,0 +1,35 @@ +import GetGraphItem from '../../graphitem/GetGraphItem.js'; + +var RemoveVertex = function (gameObejct, destroy, removeEdge) { + if (!this.isVertex(gameObejct)) { + return this; + } + + if (destroy === undefined) { + destroy = false; + } + if (removeEdge === undefined) { + removeEdge = true; + } + + var uid = this.getObjUID(gameObejct); + // Remove connected edges + if (removeEdge) { + var vertex = this.getVertexData(uid); + for (var edgeUid in vertex) { + this.removeEdge(edgeUid, destroy); + } + } + // Remove vertex + delete this.vertices[uid]; + this.vertexCount--; + // Clear reference of graph + GetGraphItem(gameObejct).setGraph(null); + if (destroy && gameObejct.destroy) { + gameObject.destroy(); + } + + return this; +} + +export default RemoveVertex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/GetGraphItem.js b/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/GetGraphItem.js new file mode 100644 index 000000000..1232451ba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/GetGraphItem.js @@ -0,0 +1,18 @@ +import ObjBank from './ObjBank.js'; +import GraphItemData from './GraphItemData.js'; +import IsUID from './IsUID.js'; + +var GetGraphItem = function (gameObject) { + // game object or uid + if (IsUID(gameObject)) { + // uid + return ObjBank.get(gameObject); + } else { + // game object + if (!gameObject.hasOwnProperty('rexGraphItem')) { + gameObject.rexGraphItem = new GraphItemData(gameObject); + } + return gameObject.rexGraphItem; + } +} +export default GetGraphItem; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/GetObjUID.js b/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/GetObjUID.js new file mode 100644 index 000000000..e1c60ee23 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/GetObjUID.js @@ -0,0 +1,16 @@ +import GetGraphItem from './GetGraphItem.js'; +import ObjBank from './ObjBank.js'; +import IsUID from './IsUID.js'; + +const uidKey = ObjBank.uidKey; +var GetObjUID = function (gameObject) { + // Game object or uid + var uid; + if (IsUID(gameObject)) { + uid = gameObject; + } else { + uid = GetGraphItem(gameObject)[uidKey]; + } + return uid; +} +export default GetObjUID; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/GraphItemData.js b/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/GraphItemData.js new file mode 100644 index 000000000..e2c918c19 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/GraphItemData.js @@ -0,0 +1,66 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import ObjBank from './ObjBank.js'; + +const uidKey = ObjBank.uidKey; + +class GraphItemData extends ComponentBase { + constructor(parent, uid) { + super(parent, { eventEmitter: false }); + + ObjBank.add(this, uid); // uid is stored in `this.$uid` + this.graph = null; + this.type = undefined; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + if (this.graph) { + this.graph.remove(this[uidKey]); + } + ObjBank.remove(this[uidKey]); + this.setGraph(null); + + super.shutdown(fromScene); + } + + setGraph(graph) { + this.graph = graph; + if (!graph) { + this.setType(undefined); + } + return this; + } + + setType(type) { + if (typeof (type) === 'string') { + type = OBJTYPE[type]; + } + this.type = type; + return this; + } + + get isVertex() { + return ((!!this.graph) && (this.type === 0)); + } + + get isEdge() { + return ((!!this.graph) && (this.type === 1)); + } +} + +var methods = { +}; +Object.assign( + GraphItemData.prototype, + methods +); + +const OBJTYPE = { + vertex: 0, + edge: 1, +} +export default GraphItemData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/IsUID.js b/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/IsUID.js new file mode 100644 index 000000000..1e16c936b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/IsUID.js @@ -0,0 +1,5 @@ +var IsUID = function (object) { + var type = typeof (object); + return (type === 'number') || (type === 'string'); +} +export default IsUID; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/ObjBank.js b/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/ObjBank.js new file mode 100644 index 000000000..8b4696af1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/ObjBank.js @@ -0,0 +1,7 @@ +import Bank from '../../bank.js'; + +var ObjBank = new Bank({ + uidKey: '$uid', + remove: false, // remove uid manually +}); +export default ObjBank; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/UidToObj.js b/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/UidToObj.js new file mode 100644 index 000000000..c810b3e68 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/graph/graphitem/UidToObj.js @@ -0,0 +1,10 @@ +import ObjBank from './ObjBank.js'; + +var UidToObj = function (uid) { + if (uid == null) { + return null; + } else { + return ObjBank.get(uid).parent; + } +} +export default UidToObj; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/grayscalepipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/grayscalepipeline-plugin.d.ts new file mode 100644 index 000000000..d39f3f587 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/grayscalepipeline-plugin.d.ts @@ -0,0 +1,29 @@ +// import * as Phaser from 'phaser'; +import GrayScalePostFxPipeline from './grayscalepipeline'; + +export default GrayScalePipelinePlugin; + +declare namespace GrayScalePipelinePlugin { + + interface IConfig extends GrayScalePostFxPipeline.IConfig { + name?: string, + } + +} + +declare class GrayScalePipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: GrayScalePipelinePlugin.IConfig + ): GrayScalePostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): GrayScalePostFxPipeline | GrayScalePostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/grayscalepipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/grayscalepipeline-plugin.js new file mode 100644 index 000000000..ff8f32514 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/grayscalepipeline-plugin.js @@ -0,0 +1,14 @@ +import GrayScalePostFxPipeline from './grayscalepipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class GrayScalePipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(GrayScalePostFxPipeline, 'rexGrayScalePostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.GrayScalePostFx', GrayScalePostFxPipeline); + +export default GrayScalePipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/grayscalepipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/grayscalepipeline.d.ts new file mode 100644 index 000000000..a9395cd42 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/grayscalepipeline.d.ts @@ -0,0 +1,2 @@ +import GrayScalePostFxPipeline from './shaders/grayscale/GrayScalePostFxPipeline'; +export default GrayScalePostFxPipeline; diff --git a/ui/src/phaser3-rex-plugins/plugins/grayscalepipeline.js b/ui/src/phaser3-rex-plugins/plugins/grayscalepipeline.js new file mode 100644 index 000000000..9c246972f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/grayscalepipeline.js @@ -0,0 +1,2 @@ +import GrayScalePostFxPipeline from './shaders/grayscale/GrayScalePostFxPipeline.js'; +export default GrayScalePostFxPipeline; diff --git a/ui/src/phaser3-rex-plugins/plugins/gridalign-plugin.js b/ui/src/phaser3-rex-plugins/plugins/gridalign-plugin.js new file mode 100644 index 000000000..74cdb7adb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gridalign-plugin.js @@ -0,0 +1,26 @@ +import { + HexagonGridAlign, + QuadGridAlign +} from './gridalign.js'; + +class GridAlignPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + hexagon(items, options) { + return HexagonGridAlign(items, options); + } + + quad(items, options) { + return QuadGridAlign(items, options); + } +} + +export default GridAlignPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gridalign.js b/ui/src/phaser3-rex-plugins/plugins/gridalign.js new file mode 100644 index 000000000..4bb232e36 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gridalign.js @@ -0,0 +1,7 @@ +import HexagonGridAlign from './actions/HexagonGridAlign.js'; +import QuadGridAlign from './actions/QuadGridAlign.js'; + +export { + HexagonGridAlign, + QuadGridAlign +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gridcutimage-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/gridcutimage-plugin.d.ts new file mode 100644 index 000000000..1d6a685aa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gridcutimage-plugin.d.ts @@ -0,0 +1,5 @@ +import GridCutImage from './gridcutimage'; + +export default class GridCutImagenPlugin extends Phaser.Plugins.BasePlugin { + gridCut: typeof GridCutImage; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gridcutimage-plugin.js b/ui/src/phaser3-rex-plugins/plugins/gridcutimage-plugin.js new file mode 100644 index 000000000..db2bbe530 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gridcutimage-plugin.js @@ -0,0 +1,19 @@ +import GridCutImage from './gridcutimage'; + +class GridCutImagePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + gridCut(gameObject, columns, rows, config) { + return GridCutImage(gameObject, columns, rows, config); + } +} + +export default GridCutImagePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gridcutimage.d.ts b/ui/src/phaser3-rex-plugins/plugins/gridcutimage.d.ts new file mode 100644 index 000000000..428876d17 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gridcutimage.d.ts @@ -0,0 +1,2 @@ +import GridCutImage from './actions/GridCutImage'; +export default GridCutImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gridcutimage.js b/ui/src/phaser3-rex-plugins/plugins/gridcutimage.js new file mode 100644 index 000000000..2c3fb10bf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gridcutimage.js @@ -0,0 +1,2 @@ +import GridCutImage from './actions/GridCutImage.js'; +export default GridCutImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gridtable-plugin.js b/ui/src/phaser3-rex-plugins/plugins/gridtable-plugin.js new file mode 100644 index 000000000..441a44dfe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gridtable-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/container/gridtable/Factory.js'; +import Creator from './gameobjects/container/gridtable/Creator.js'; +import GridTable from './gameobjects/container/gridtable/GridTable.js'; +import SetValue from './utils/object/SetValue.js'; + +class GridTablePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexGridTable', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.GridTable', GridTable); + +export default GridTablePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gridtable.d.ts b/ui/src/phaser3-rex-plugins/plugins/gridtable.d.ts new file mode 100644 index 000000000..356513490 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gridtable.d.ts @@ -0,0 +1,2 @@ +import GridTable from './gameobjects/container/gridtable/GridTable'; +export default GridTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/gridtable.js b/ui/src/phaser3-rex-plugins/plugins/gridtable.js new file mode 100644 index 000000000..d4ba2985f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/gridtable.js @@ -0,0 +1,2 @@ +import GridTable from './gameobjects/container/gridtable/GridTable.js'; +export default GridTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/hexagon-plugin.js b/ui/src/phaser3-rex-plugins/plugins/hexagon-plugin.js new file mode 100644 index 000000000..9a19c32b0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/hexagon-plugin.js @@ -0,0 +1,19 @@ +import Hexagon from './hexagon.js'; + +class HexagonPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(x, y, size, type) { + return new Hexagon(x, y, size, type); + } +} + +export default HexagonPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/hexagon.js b/ui/src/phaser3-rex-plugins/plugins/hexagon.js new file mode 100644 index 000000000..1ab67fae5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/hexagon.js @@ -0,0 +1,2 @@ +import Hexagon from './geom/hexagon/Hexagon.js'; +export default Hexagon; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/hiddeninputtext-plugin.js b/ui/src/phaser3-rex-plugins/plugins/hiddeninputtext-plugin.js new file mode 100644 index 000000000..d49b991ad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/hiddeninputtext-plugin.js @@ -0,0 +1,22 @@ +import HiddenInputText from './behaviors/hiddentextedit/HiddenTextEdit.js'; +import SetValue from './utils/object/SetValue.js'; + +class HiddenInputTextPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(textObject, config) { + return new HiddenInputText(textObject, config); + } +} + +SetValue(window, 'RexPlugins.GameObjects.HiddenInputText', HiddenInputText); + +export default HiddenInputTextPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/hiddeninputtext.d.ts b/ui/src/phaser3-rex-plugins/plugins/hiddeninputtext.d.ts new file mode 100644 index 000000000..85bf2a054 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/hiddeninputtext.d.ts @@ -0,0 +1,2 @@ +import HiddenInputText from './behaviors/hiddentextedit/HiddenTextEdit'; +export default HiddenInputText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/hiddeninputtext.js b/ui/src/phaser3-rex-plugins/plugins/hiddeninputtext.js new file mode 100644 index 000000000..38aaa488a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/hiddeninputtext.js @@ -0,0 +1,2 @@ +import HiddenInputText from './behaviors/hiddentextedit/HiddenTextEdit.js'; +export default HiddenInputText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/horrifipipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/horrifipipeline-plugin.d.ts new file mode 100644 index 000000000..8c21d2c8f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/horrifipipeline-plugin.d.ts @@ -0,0 +1,29 @@ +// import * as Phaser from 'phaser'; +import HorrifiPostFxPipeline from './horrifipipeline'; + +export default HorrifiPipelinePlugin; + +declare namespace HorrifiPipelinePlugin { + + interface IConfig extends HorrifiPostFxPipeline.IConfig { + name?: string, + } + +} + +declare class HorrifiPipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: HorrifiPipelinePlugin.IConfig + ): HorrifiPostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): HorrifiPostFxPipeline | HorrifiPostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/horrifipipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/horrifipipeline-plugin.js new file mode 100644 index 000000000..b6d770a99 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/horrifipipeline-plugin.js @@ -0,0 +1,19 @@ +import HorrifiPostFxPipeline from './horrifipipeline.js'; +import HorrifiPostFxPipelineBehavior from './horrifipipelinebehavior.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class HorrifiPipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(HorrifiPostFxPipeline, 'rexHorrifiPostFx'); + } + + addBehavior(gameObject, config) { + return new HorrifiPostFxPipelineBehavior(gameObject, config); + } +} + +SetValue(window, 'RexPlugins.Pipelines.HorrifiPostFx', HorrifiPostFxPipeline); + +export default HorrifiPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/horrifipipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/horrifipipeline.d.ts new file mode 100644 index 000000000..4eb5c8dfa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/horrifipipeline.d.ts @@ -0,0 +1,2 @@ +import HorrifiPostFxPipeline from './shaders/horrifi/HorrifiPostFxPipeline'; +export default HorrifiPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/horrifipipeline.js b/ui/src/phaser3-rex-plugins/plugins/horrifipipeline.js new file mode 100644 index 000000000..49b718b12 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/horrifipipeline.js @@ -0,0 +1,2 @@ +import HorrifiPostFxPipeline from './shaders/horrifi/HorrifiPostFxPipeline.js'; +export default HorrifiPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/horrifipipelinebehavior.d.ts b/ui/src/phaser3-rex-plugins/plugins/horrifipipelinebehavior.d.ts new file mode 100644 index 000000000..1d9787d7d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/horrifipipelinebehavior.d.ts @@ -0,0 +1,2 @@ +import HorrifiPostFxPipelineBehavior from './shaders/horrifi/HorrifiPostFxPipelineBehavior'; +export default HorrifiPostFxPipelineBehavior; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/horrifipipelinebehavior.js b/ui/src/phaser3-rex-plugins/plugins/horrifipipelinebehavior.js new file mode 100644 index 000000000..13973b45c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/horrifipipelinebehavior.js @@ -0,0 +1,2 @@ +import HorrifiPostFxPipelineBehavior from './shaders/horrifi/HorrifiPostFxPipelineBehavior.js'; +export default HorrifiPostFxPipelineBehavior; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline-plugin.d.ts new file mode 100644 index 000000000..5ff310b0f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline-plugin.d.ts @@ -0,0 +1,30 @@ +// import * as Phaser from 'phaser'; +import HslAdjustPostFxPipeline from './hsladjustpipeline'; + + +export default HslAdjustPipelinePlugin; + +declare namespace HslAdjustPipelinePlugin { + + interface IConfig extends HslAdjustPostFxPipeline.IConfig { + name?: string, + } + +} + +declare class HslAdjustPipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: HslAdjustPipelinePlugin.IConfig + ): HslAdjustPostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): HslAdjustPostFxPipeline | HslAdjustPostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline-plugin.js new file mode 100644 index 000000000..af72d4a5f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline-plugin.js @@ -0,0 +1,14 @@ +import HslAdjustPostFxPipeline from './hsladjustpipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class HslAdjustPipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(HslAdjustPostFxPipeline, 'rexHslAdjustPostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.HslAdjustPostFx', HslAdjustPostFxPipeline); + +export default HslAdjustPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline.d.ts new file mode 100644 index 000000000..972c890b2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline.d.ts @@ -0,0 +1,2 @@ +import HslAdjustPostFxPipeline from './shaders/hsladjust/HslAdjustPostFxPipeline'; +export default HslAdjustPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline.js b/ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline.js new file mode 100644 index 000000000..ff1f2f4cd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/hsladjustpipeline.js @@ -0,0 +1,2 @@ +import HslAdjustPostFxPipeline from './shaders/hsladjust/HslAdjustPostFxPipeline.js'; +export default HslAdjustPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/imagebox-plugin.js b/ui/src/phaser3-rex-plugins/plugins/imagebox-plugin.js new file mode 100644 index 000000000..b13dd39da --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/imagebox-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/container/imagebox/Factory.js'; +import Creator from './gameobjects/container/imagebox/Creator.js'; +import ImageBox from './gameobjects/container/imagebox/ImageBox.js'; +import SetValue from './utils/object/SetValue.js'; + +class ImageBoxPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexImageBox', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.ImageBox', ImageBox); + +export default ImageBoxPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/imagebox.d.ts b/ui/src/phaser3-rex-plugins/plugins/imagebox.d.ts new file mode 100644 index 000000000..e0127bedf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/imagebox.d.ts @@ -0,0 +1,2 @@ +import ImageBox from './gameobjects/container/imagebox/ImageBox'; +export default ImageBox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/imagebox.js b/ui/src/phaser3-rex-plugins/plugins/imagebox.js new file mode 100644 index 000000000..ee832da87 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/imagebox.js @@ -0,0 +1,2 @@ +import ImageBox from './gameobjects/container/imagebox/ImageBox.js'; +export default ImageBox; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/imageuriloader-plugin.js b/ui/src/phaser3-rex-plugins/plugins/imageuriloader-plugin.js new file mode 100644 index 000000000..4caf9a20f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/imageuriloader-plugin.js @@ -0,0 +1,16 @@ + +import LoaderCallback from './loader/imageuri/ImageURILoaderCallback.js'; + +class ImageURILoaderPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + + pluginManager.registerFileType('rexImageURI', LoaderCallback); + } + + addToScene(scene) { + scene.sys.load['rexImageURI'] = LoaderCallback; + } +} + +export default ImageURILoaderPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/imageuriloader.d.ts b/ui/src/phaser3-rex-plugins/plugins/imageuriloader.d.ts new file mode 100644 index 000000000..2b8d8494c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/imageuriloader.d.ts @@ -0,0 +1,3 @@ + +import LoaderCallback from './loader/imageuri/ImageURILoaderCallback.js'; +export default LoaderCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/imageuriloader.js b/ui/src/phaser3-rex-plugins/plugins/imageuriloader.js new file mode 100644 index 000000000..e3ee1c45d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/imageuriloader.js @@ -0,0 +1,6 @@ + +import LoaderCallback from './loader/imageuri/ImageURILoaderCallback.js'; + +Phaser.Loader.FileTypesManager.register('rexImageURI', LoaderCallback); + +export default LoaderCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/button/Button.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/button/Button.d.ts new file mode 100644 index 000000000..6bb1ec6e5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/button/Button.d.ts @@ -0,0 +1,53 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; + +export default Button; + +declare namespace Button { + + interface IConfig { + mode?: 0 | 1 | 'pointerdown' | 'pointerup' | 'press' | 'release', + clickInterval?: number, + threshold?: number, + enable?: boolean, + + eventEmitter?: boolean | Phaser.Events.EventEmitter + } + + namespace Events { + type ClickCallbackType = + ( + button: Button, + gameObject: Phaser.GameObjects.GameObject, + pointer: Phaser.Input.Pointer, + event: Phaser.Types.Input.EventData + ) => void; + + type EnableCallbackType = ( + button: Button, + gameObject: Phaser.GameObjects.GameObject, + ) => void; + + type DisableCallbackType = ( + button: Button, + gameObject: Phaser.GameObjects.GameObject, + ) => void; + + } +} + +declare class Button extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Button.IConfig + ) + + setEnable(enable?: boolean): this; + toggleEnable(): this; + enable: boolean; + + setMode( + mode?: 0 | 1 | 'pointerdown' | 'press' | 'pointerup' | 'release' + ): this; + + setClickInterval(interval: number): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/button/Button.js b/ui/src/phaser3-rex-plugins/plugins/input/button/Button.js new file mode 100644 index 000000000..0aece3304 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/button/Button.js @@ -0,0 +1,198 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Button extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this._enable = undefined; + gameObject.setInteractive(GetValue(config, "inputConfig", undefined)); + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.pointer = undefined; + this.lastClickTime = undefined; + this.setEnable(GetValue(o, "enable", true)); + this.setMode(GetValue(o, "mode", 1)); + this.setClickInterval(GetValue(o, "clickInterval", 100)); + this.setDragThreshold(GetValue(o, 'threshold', undefined)); + return this; + } + + boot() { + var gameObject = this.parent; + gameObject.on('pointerdown', this.onPress, this); + gameObject.on('pointerup', this.onRelease, this); + gameObject.on('pointerout', this.onPointOut, this); + gameObject.on('pointermove', this.onMove, this); + + gameObject.on('pointerover', this.onOver, this); + gameObject.on('pointerout', this.onOut, this); + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + // GameObject events will be removed when this gameObject destroyed + // this.parent.on('pointerdown', this.onPress, this); + // this.parent.on('pointerup', this.onRelease, this); + // this.parent.on('pointerout', this.onPointOut, this); + // this.parent.on('pointermove', this.onMove, this); + this.pointer = null; + + super.shutdown(fromScene); + } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + + if (!e) { + this.cancel(); + } + this._enable = e; + + var eventName = (e) ? 'enable' : 'disable'; + this.emit(eventName, this, this.parent); + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + setMode(m) { + if (typeof (m) === 'string') { + m = CLICKMODE[m]; + } + this.mode = m; + return this; + } + + setClickInterval(interval) { + this.clickInterval = interval; // ms + return this; + } + + setDragThreshold(distance) { + this.dragThreshold = distance; + return this; + } + + // internal + onPress(pointer, localX, localY, event) { + if (this.pointer !== undefined) { + return; + } + this.pointer = pointer; + if (this.mode === 0) { + this.click(pointer.downTime, pointer, event); + } + } + + onRelease(pointer, localX, localY, event) { + if (this.pointer !== pointer) { + return; + } + if (this.mode === 1) { + this.click(pointer.upTime, pointer, event); + } + this.pointer = undefined; + } + + onPointOut(pointer, event) { + if (this.pointer !== pointer) { + return; + } + this.cancel(); + } + + onMove(pointer, localX, localY, event) { + if (this.pointer !== pointer) { + return; + } + + if (this.dragThreshold === undefined) { + return; + } + + if (pointer.getDistance() >= this.dragThreshold) { + this.cancel(); + } + } + + click(nowTime, pointer, event) { + if (!this.enable) { + return this; + } + + if (nowTime === undefined) { + // fires 'click' event manually + this.emit('click', this, this.parent, pointer, event); + return this; + } + + this.pointer = undefined; + var lastClickTime = this.lastClickTime; + if ((lastClickTime !== undefined) && + ((nowTime - lastClickTime) <= this.clickInterval)) { + return this; + } + this.lastClickTime = nowTime; + this.emit('click', this, this.parent, pointer, event); + return this; + } + + cancel() { + this.pointer = undefined; + return this; + } + + onOver(pointer, localX, localY, event) { + if (!this.enable) { + return this; + } + + this.emit('over', this, this.parent, pointer, event); + return this; + } + + onOut(pointer, event) { + if (!this.enable) { + return this; + } + + this.emit('out', this, this.parent, pointer, event); + return this; + } +} + +const CLICKMODE = { + press: 0, + pointerdown: 0, + release: 1, + pointerup: 1, +}; + +export default Button; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/clickoutside/ClickOutside.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/clickoutside/ClickOutside.d.ts new file mode 100644 index 000000000..8be2e991d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/clickoutside/ClickOutside.d.ts @@ -0,0 +1,52 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default ClickOutside; + +declare namespace ClickOutside { + + interface IConfig { + mode?: 0 | 1 | 'pointerdown' | 'pointerup' | 'press' | 'release', + clickInterval?: number, + enable?: boolean, + + eventEmitter?: boolean | Phaser.Events.EventEmitter + } + + namespace Events { + type ClickOutsideCallbackType = + ( + button: ClickOutside, + gameObject: Phaser.GameObjects.GameObject, + pointer: Phaser.Input.Pointer, + event: Phaser.Types.Input.EventData + ) => void; + + type EnableCallbackType = ( + button: ClickOutside, + gameObject: Phaser.GameObjects.GameObject, + ) => void; + + type DisableCallbackType = ( + button: ClickOutside, + gameObject: Phaser.GameObjects.GameObject, + ) => void; + + } +} + +declare class ClickOutside extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: ClickOutside.IConfig + ) + + setEnable(enable?: boolean): this; + toggleEnable(): this; + enable: boolean; + + setMode( + mode?: 0 | 1 | 'pointerdown' | 'press' | 'pointerup' | 'release' + ): this; + + setClickInterval(interval: number): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/clickoutside/ClickOutside.js b/ui/src/phaser3-rex-plugins/plugins/input/clickoutside/ClickOutside.js new file mode 100644 index 000000000..09158161b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/clickoutside/ClickOutside.js @@ -0,0 +1,153 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import IsPointerInHitArea from '../../utils/input/IsPointerInHitArea.js'; +import IsPointerInBounds from '../../utils/input/IsPointerInBounds.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class ClickOutside extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this._enable = undefined; + + var inputConfig = GetValue(config, "inputConfig", undefined); + if (inputConfig) { + gameObject.setInteractive(inputConfig); + } + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.setEnable(GetValue(o, "enable", true)); + this.setMode(GetValue(o, "mode", 1)); + this.setClickInterval(GetValue(o, "clickInterval", 100)); + return this; + } + + boot() { + var scene = this.parent.scene; + scene.input.on('pointerdown', this.onPress, this); + scene.input.on('pointerup', this.onRelease, this); + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + var scene = this.parent.scene; + scene.input.off('pointerdown', this.onPress, this); + scene.input.off('pointerup', this.onRelease, this); + + super.shutdown(fromScene); + } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + + this._enable = e; + var eventName = (e) ? 'enable' : 'disable'; + this.emit(eventName, this, this.parent); + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + setMode(m) { + if (typeof (m) === 'string') { + m = CLICKMODE[m]; + } + this.mode = m; + return this; + } + + setClickInterval(interval) { + this.clickInterval = interval; // ms + return this; + } + + isPointerInside(pointer) { + var gameObject = this.parent; + var isInsideCallback = (gameObject.input) ? IsPointerInHitArea : IsPointerInBounds; + return isInsideCallback(gameObject, pointer); + } + + // internal + onPress(pointer) { + if (this.mode === 0) { + // Do nothing if game object is not visible + if (!this.parent.willRender(pointer.camera)) { + return; + } + + if (!this.isPointerInside(pointer)) { + this.click(pointer.downTime, pointer); + } + } + } + + onRelease(pointer) { + if (this.mode === 1) { + // Do nothing if game object is not visible + if (!this.parent.willRender(pointer.camera)) { + return; + } + + if (!this.isPointerInside(pointer)) { + this.click(pointer.upTime, pointer); + } + } + } + + click(nowTime, pointer) { + if (!this.enable) { + return this; + } + + if (nowTime === undefined) { + // fires 'clickoutside' event manually + this.emit('clickoutside', this, this.parent, pointer); + return this; + } + + var lastClickTime = this.lastClickTime; + if ((lastClickTime !== undefined) && + ((nowTime - lastClickTime) <= this.clickInterval)) { + return this; + } + this.lastClickTime = nowTime; + this.emit('clickoutside', this, this.parent, pointer); + + return this; + } +} + +const CLICKMODE = { + press: 0, + pointerdown: 0, + release: 1, + pointerup: 1, +}; + +export default ClickOutside; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/cursoratbound/CursorAtBound.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/cursoratbound/CursorAtBound.d.ts new file mode 100644 index 000000000..74f13edc3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/cursoratbound/CursorAtBound.d.ts @@ -0,0 +1,28 @@ +export default CursorAtBound; + +declare namespace CursorAtBound { + interface IConfig { + bounds?: Phaser.Geom.Rectangle, + sensitiveDistance?: number, + } +} + +declare class CursorAtBound { + constructor( + scene: Phaser.Scene, + config?: CursorAtBound.IConfig + ) + + createCursorKeys(): { + up: Phaser.Input.Keyboard.Key, + down: Phaser.Input.Keyboard.Key, + left: Phaser.Input.Keyboard.Key, + right: Phaser.Input.Keyboard.Key, + }; + + readonly left: boolean; + readonly right: boolean; + readonly up: boolean; + readonly down: boolean; + readonly nokey: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/cursoratbound/CursorAtBound.js b/ui/src/phaser3-rex-plugins/plugins/input/cursoratbound/CursorAtBound.js new file mode 100644 index 000000000..5cce6b530 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/cursoratbound/CursorAtBound.js @@ -0,0 +1,84 @@ +import CursorKeys from '../../utils/input/CursorKeys.js' +import GetViewport from '../../utils/system/GetViewport.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class CursorAtBound extends CursorKeys { + constructor(scene, config) { + super(scene); + + this.scene = scene; + this.sensitiveDistance = GetValue(config, 'sensitiveDistance', 20); + + var bounds = GetValue(config, 'bounds', undefined); + if (bounds === undefined) { + bounds = GetViewport(scene); + } + this.bounds = bounds; + + this.boot(); + } + + boot() { + this.scene.input.on('pointermove', this.onPointerMove, this); + this.scene.sys.events.once('shutdown', this.destroy, this); + } + + shutdown() { + if (!this.scene) { + return; + } + + this.scene.input.off('pointermove', this.onPointerMove, this); + this.scene.sys.events.off('shutdown', this.destroy, this); + this.scene = undefined; + + super.shutdown(); + } + + destroy() { + this.shutdown(); + } + + onPointerMove(pointer) { + var cursorX = pointer.x, + cursorY = pointer.y; + var left = this.bounds.left, + right = this.bounds.right, + top = this.bounds.top, + bottom = this.bounds.bottom, + sensitiveDistance = this.sensitiveDistance; + var atLeftBound = (cursorX >= left) && (cursorX <= (left + sensitiveDistance)), + atRightBound = (cursorX <= right) && (cursorX >= (right - sensitiveDistance)), + atTopBound = (cursorY >= top) && (cursorY <= (top + sensitiveDistance)), + atBottomBound = (cursorY <= bottom) && (cursorY >= (bottom - sensitiveDistance)) + + this.clearAllKeysState(); + this.setKeyState('left', atLeftBound); + this.setKeyState('right', atRightBound); + this.setKeyState('up', atTopBound); + this.setKeyState('down', atBottomBound); + } + + get up() { + return this.upKeyDown; + } + + get down() { + return this.downKeyDown; + } + + get left() { + return this.leftKeyDown; + } + + get right() { + return this.rightKeyDown; + } + + get noKey() { + return this.noKeyDown; + } +} + +export default CursorAtBound; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/drag/Drag.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/drag/Drag.d.ts new file mode 100644 index 000000000..b9a538dae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/drag/Drag.d.ts @@ -0,0 +1,34 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default Drag; + +declare namespace Drag { + type AixsModeType = 0 | 1 | 2 | 'both' | 'h&v' | 'horizontal' | 'h' | 'vertical' | 'v'; + + interface IConfig { + enable?: boolean, + axis?: AixsModeType, + rotation?: number + } +} + +declare class Drag extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Drag.IConfig + ) + + setEnable(enable?: boolean): this; + toggleEnable(): this; + enable: boolean; + + setAxisRotation(rad?: number): this; + axisRotation: number; + + setAxisMode(axisMode: Drag.AixsModeType): this; + axisMode: number; + + drag(): this; + dragend(): this; + +} diff --git a/ui/src/phaser3-rex-plugins/plugins/input/drag/Drag.js b/ui/src/phaser3-rex-plugins/plugins/input/drag/Drag.js new file mode 100644 index 000000000..1aeb02aa5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/drag/Drag.js @@ -0,0 +1,181 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import RequestDrag from '../../utils/input/RequestDrag.js' + +const GetValue = Phaser.Utils.Objects.GetValue; +const DistanceBetween = Phaser.Math.Distance.Between; +const RotateAroundDistance = Phaser.Math.RotateAroundDistance; + +class Drag extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, { eventEmitter: false }); + // No event emitter + // this.parent = gameObject; + + this._enable = undefined; + gameObject.setInteractive(GetValue(config, "inputConfig", undefined)); + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.pointer = undefined; + this.setEnable(GetValue(o, "enable", true)); + this.setAxisMode(GetValue(o, "axis", 0)); + this.setAxisRotation(GetValue(o, "rotation", 0)); + return this; + } + + toJSON() { + return { + enable: this.enable, + axis: this.axisMode, + rotation: this.axisRotation + }; + } + + boot() { + var gameObject = this.parent; + gameObject.on('dragstart', this.onDragStart, this); + gameObject.on('drag', this.onDrag, this); + gameObject.on('dragend', this.onDragEnd, this); + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + // GameObject events will be removed when this gameObject destroyed + // this.parent.on('dragstart', this.onDragStart, this); + // this.parent.on('drag', this.onDrag, this); + // this.parent.on('dragend', this.onDragEnd, this); + this.pointer = undefined; + + super.shutdown(fromScene); + } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + + if (!e) { + this.dragend(); + } + this._enable = e; + this.scene.input.setDraggable(this.parent, e); + return this; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + setAxisMode(m) { + if (typeof (m) === 'string') { + m = DIRECTIONNODE[m]; + } + this.axisMode = m; + return this; + } + + setAxisRotation(a) { + this.axisRotation = a; + return this; + } + + drag() { + RequestDrag(this.parent); + return this; + } + + dragend() { + if (!this.isDragging) { + return; + } + this.scene.input.setDragState(this.pointer, 5); + return this; + } + + onDragStart(pointer, dragX, dragY) { + if (this.isDragging) { + return; + } + this.pointer = pointer; + } + + onDrag(pointer, dragX, dragY) { + if (this.pointer !== pointer) { + return; + } + var gameObject = this.parent; + if (this.axisMode === 0) { + gameObject.x = dragX; + gameObject.y = dragY; + } else if (this.axisRotation === 0) { + if (this.axisMode === 1) { + gameObject.x = dragX; + } else if (this.axisMode === 2) { + gameObject.y = dragY; + } + } else { + var dist; + var p1 = { x: dragX, y: dragY }; + dist = DistanceBetween(p1.x, p1.y, gameObject.x, gameObject.y); + p1 = RotateAroundDistance(p1, gameObject.x, gameObject.y, -this.axisRotation, dist); + + if (this.axisMode === 1) { + p1.y = gameObject.y; + } else if (this.axisMode === 2) { + p1.x = gameObject.x; + } + dist = DistanceBetween(p1.x, p1.y, gameObject.x, gameObject.y); + p1 = RotateAroundDistance(p1, gameObject.x, gameObject.y, this.axisRotation, dist); + + gameObject.x = p1.x; + gameObject.y = p1.y; + } + + } + + onDragEnd(pointer, dragX, dragY, dropped) { + if (this.pointer !== pointer) { + return; + } + this.pointer = undefined; + } + + get isDragging() { + return (this.pointer !== undefined); + } +} + +const DIRECTIONNODE = { + 'both': 0, + 'h&v': 0, + 'x&y': 0, + 'horizontal': 1, + 'h': 1, + 'x': 1, + 'vertical': 2, + 'v': 2, + 'y': 2 +}; + + +export default Drag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/dragrotate/DragRotate.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/dragrotate/DragRotate.d.ts new file mode 100644 index 000000000..1b64eff35 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/dragrotate/DragRotate.d.ts @@ -0,0 +1,48 @@ +export default DragRotate; + +declare namespace DragRotate { + interface IConfig { + x?: number, + y?: number, + maxRadius?: number, + minRadius?: number, + enable?: boolean, + } + + namespace Evenets { + type DragCallbackType = ( + dragRotate: DragRotate + ) => void; + } +} + +declare class DragRotate extends Phaser.Events.EventEmitter { + constructor( + scene: Phaser.Scene, + config?: DragRotate.IConfig + ); + + setEnable(enable?: boolean): this; + toggleEnable(): this; + enable: boolean; + + setOrigin(x: number, y: number): this; + setOrigin(pointer: { x: number, y: number }): this; + x: number; + y: number; + + setRadius(maxRadius: number, minRadius?: number): this; + maxRadius: number; + minRadius: number; + + readonly deltaRotation: number; + readonly deltAangle: number; + readonly cw: boolean; + readonly ccw: boolean; + + dragCancel(): this; + + readonly pointer: Phaser.Input.Pointer; + readonly isDrag: boolean; + readonly state: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/dragrotate/DragRotate.js b/ui/src/phaser3-rex-plugins/plugins/input/dragrotate/DragRotate.js new file mode 100644 index 000000000..79f1f6608 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/dragrotate/DragRotate.js @@ -0,0 +1,222 @@ +import EventEmitterMethods from '../../utils/eventemitter/EventEmitterMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const DistanceBetween = Phaser.Math.Distance.Between; +const GetAngle = Phaser.Math.Angle.Between; +const WrapAngle = Phaser.Math.Angle.Wrap; +const RadToDeg = Phaser.Math.RadToDeg; + +class DragRotate { + constructor(scene, config) { + this.scene = scene; + // Event emitter + this.setEventEmitter(GetValue(config, 'eventEmitter', undefined)); + + this._enable = undefined; + this._deltaRotation = undefined; + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.pointer = undefined; + this.setEnable(GetValue(o, "enable", true)); + this.setOrigin(o); + this.setRadius(GetValue(o, 'maxRadius', 100), GetValue(o, 'minRadius', 0)); + this.state = TOUCH0; + } + + boot() { + this.scene.input.on('pointerdown', this.onPointerDown, this); + this.scene.input.on('pointerup', this.onPointerUp, this); + this.scene.input.on('pointermove', this.onPointerMove, this); + this.scene.sys.events.once('shutdown', this.destroy, this); + } + + shutdown() { + if (!this.scene) { + return + } + + this.destroyEventEmitter(); + this.scene.input.off('pointerdown', this.onPointerDown, this); + this.scene.input.off('pointerup', this.onPointerUp, this); + this.scene.input.off('pointermove', this.onPointerMove, this); + this.scene.sys.events.off('shutdown', this.destroy, this); + this.scene = undefined; + } + + destroy() { + this.shutdown(); + } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + + if (!e) { + this.dragCancel(); + } + this._enable = e; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + setOrigin(x, y) { + if (y === undefined) { + var point = x; + x = GetValue(point, 'x', 0); + y = GetValue(point, 'y', 0); + } + this.x = x; + this.y = y; + return this; + } + + setRadius(maxRadius, minRadius) { + if (minRadius === undefined) { + minRadius = 0; + } + this.maxRadius = maxRadius; + this.minRadius = minRadius; + return this; + } + + contains(x, y) { + var r = DistanceBetween(this.x, this.y, x, y); + return (r >= this.minRadius) && (r <= this.maxRadius); + } + + onPointerDown(pointer) { + if ((!this.enable) || + this.pointer) { + return; + } + + if (!this.contains(pointer.worldX, pointer.worldY)) { + return; + } + + this.onDragStart(pointer); + } + + onPointerUp(pointer) { + if ((!this.enable) || + (this.pointer !== pointer)) { + return; + } + + this.onDragEnd(); + } + + onPointerMove(pointer) { + if ((!this.enable) || + (!pointer.isDown)) { + return; + } + + switch (this.state) { + case TOUCH0: + if (this.contains(pointer.worldX, pointer.worldY)) { + this.onDragStart(pointer); + } + break; + + case TOUCH1: + if (this.contains(pointer.worldX, pointer.worldY)) { + this.onDrag(); + } else { + this.onDragEnd(); + } + break; + } + } + + dragCancel() { + if (this.state === TOUCH1) { + this.onDragEnd(); + } + this.pointer = undefined; + this.state = TOUCH0; + return this; + } + + onDragStart(pointer) { + this.pointer = pointer; + this.state = TOUCH1; + this._deltaRotation = undefined; + this.emit('dragstart', this); + } + + onDragEnd() { + this.pointer = undefined; + this.state = TOUCH0; + this._deltaRotation = undefined; + this.emit('dragend', this); + } + + onDrag() { + this._deltaRotation = undefined; + this.emit('drag', this); + } + + get deltaRotation() { + if (this.state === TOUCH0) { + return 0; + } + + if (this._deltaRotation === undefined) { + var p0 = this.pointer.prevPosition, + p1 = this.pointer.position; + var a0 = GetAngle(this.x, this.y, p0.x, p0.y), + a1 = GetAngle(this.x, this.y, p1.x, p1.y); + this._deltaRotation = WrapAngle(a1 - a0); + } + + return this._deltaRotation; + + } + + get deltaAngle() { + if (this.state === TOUCH0) { + return 0; + } + + return RadToDeg(this.deltaRotation); + } + + get cw() { + return (this.deltaRotation >= 0); + } + + get ccw() { + return !this.cw; + } +} + +Object.assign( + DragRotate.prototype, + EventEmitterMethods +); + +const TOUCH0 = 0; +const TOUCH1 = 1; + +export default DragRotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/dragspeed/DragSpeed.js b/ui/src/phaser3-rex-plugins/plugins/input/dragspeed/DragSpeed.js new file mode 100644 index 000000000..a88459e85 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/dragspeed/DragSpeed.js @@ -0,0 +1,223 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import GetTickDelta from '../../utils/system/GetTickDelta.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const DistanceBetween = Phaser.Math.Distance.Between; + +class DragSpeed extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this._enable = undefined; + gameObject.setInteractive(GetValue(config, "inputConfig", undefined)); + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.pointer = undefined; + this.isInTouched = false; + this.holdStartTime = undefined; + this.x = undefined; + this.y = undefined; + this.preX = undefined; + this.preY = undefined; + this.localX = undefined; + this.localY = undefined; + this.justMoved = false; + this.setEnable(GetValue(o, 'enable', true)); + this.holdThreshold = GetValue(o, 'holdThreshold', 50); // ms + this.pointerOutReleaseEnable = GetValue(o, 'pointerOutRelease', true); + return this; + } + + boot() { + // Drag start only when pointer down + this.parent.on('pointerdown', this.onPointIn, this); + // this.parent.on('pointerover', this.onPointIn, this); + + this.parent.on('pointerup', this.onPointOut, this); + + if (this.pointerOutReleaseEnable) { + this.parent.on('pointerout', this.onPointOut, this); + } + + this.parent.on('pointermove', this.onPointerMove, this); + this.scene.sys.events.on('preupdate', this.preupdate, this); + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + // GameObject events will be removed when this gameObject destroyed + // this.parent.off('pointerdown', this.onPointIn, this); + // this.parent.off('pointerup', this.onPointOut, this); + // this.parent.off('pointerout', this.onPointOut, this); + // this.parent.off('pointermove', this.onPointerMove, this); + + this.scene.sys.events.off('preupdate', this.preupdate, this); + + this.pointer = undefined; + + super.shutdown(fromScene); + } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + + if (!e) { + this.isInTouched = false; + this.pointer = undefined; + } + this._enable = e; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + setPointerOutReleaseEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.pointerOutReleaseEnable = enable; + return this; + } + + get isDown() { + return this.pointer && this.pointer.isDown; + } + + get isUp() { + return !this.isDown; + } + + get dx() { + return this.x - this.preX; + } + + get dy() { + return this.y - this.preY; + } + + get dt() { + var delta = GetTickDelta(this.scene); + return delta; + } + + get speed() { + if ((this.x === this.preX) && (this.y === this.preY)) { + return 0; + } + var d = DistanceBetween(this.preX, this.preY, this.x, this.y); + var speed = d / (this.dt * 0.001); + return speed; + } + + get speedX() { + return this.dx / (this.dt * 0.001); + } + + get speedY() { + return this.dy / (this.dt * 0.001); + } + + // internal + onPointIn(pointer, localX, localY) { + if ((!this.enable) || + (!pointer.isDown) || + (this.pointer !== undefined)) { + return; + } + this.pointer = pointer; + this.localX = localX; + this.localY = localY; + } + + onPointOut(pointer) { + if ((!this.enable) || + (this.pointer !== pointer)) { + return; + } + this.pointer = undefined; + } + + onPointerMove(pointer, localX, localY) { + if ((!this.enable) || + (!pointer.isDown) || + (this.pointer !== pointer)) { + return; + } + this.localX = localX; + this.localY = localY; + } + + preupdate(time, delta) { + if (!this.enable) { + return; + } + + var pointer = this.pointer; + this.justMoved = false; + if (pointer && (!this.isInTouched)) { + // Touch start + this.x = pointer.worldX; + this.y = pointer.worldY; + this.preX = pointer.worldX; + this.preY = pointer.worldY; + this.isInTouched = true; + this.holdStartTime = undefined; + this.emit('touchstart', pointer, this.localX, this.localY); + + } else if (pointer && this.isInTouched) { + // In touch + if ((this.x === pointer.x) && (this.y === pointer.y)) { + // Hold + if (this.holdStartTime === undefined) { + this.holdStartTime = time; + } else if (time - this.holdStartTime > this.holdThreshold) { + this.preX = this.x; + this.preY = this.y; + } + } else { + // Move + this.preX = this.x; + this.preY = this.y; + this.x = pointer.worldX; + this.y = pointer.worldY; + this.holdStartTime = undefined; + this.justMoved = true; + this.emit('touchmove', pointer, this.localX, this.localY); + } + + } else if ((!pointer) && this.isInTouched) { + // Touch end + this.isInTouched = false; + this.holdStartTime = undefined; + this.emit('touchend', pointer); + + } + } +} + +export default DragSpeed; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/ObjectFactory.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/ObjectFactory.js new file mode 100644 index 000000000..1a385b273 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/ObjectFactory.js @@ -0,0 +1,10 @@ +class ObjectFactory { + constructor(scene) { + this.scene = scene; + } + + static register(type, callback) { + ObjectFactory.prototype[type] = callback; + } +}; +export default ObjectFactory; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/onepointertracer/OnePointerTracer.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/onepointertracer/OnePointerTracer.d.ts new file mode 100644 index 000000000..8aea95ca0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/onepointertracer/OnePointerTracer.d.ts @@ -0,0 +1,27 @@ +// import * as Phaser from 'phaser'; + + +export default OnePointerTracer; + +declare namespace OnePointerTracer { + + interface IConfig { + enable?: boolean, + bounds?: Phaser.Geom.Rectangle, + eventEmitter?: boolean | Phaser.Events.EventEmitter, + } + +} + +declare class OnePointerTracer extends Phaser.Events.EventEmitter { + + enable: boolean; + setEnable(enable?: boolean): this; + toggleEnable(): this; + + bounds: Phaser.Geom.Rectangle | undefined; + setDetectBounds(bounds?: Phaser.Geom.Rectangle): this; + + pointer: Phaser.Input.Pointer | undefined; + lastPointer: Phaser.Input.Pointer | undefined; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/onepointertracer/OnePointerTracer.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/onepointertracer/OnePointerTracer.js new file mode 100644 index 000000000..80c4f68c4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/onepointertracer/OnePointerTracer.js @@ -0,0 +1,260 @@ +import TickTask from '../../../utils/componentbase/TickTask.js'; +import GetSceneObject from '../../../utils/system/GetSceneObject.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class OnePointerTracer extends TickTask { + constructor(gameObject, config) { + var scene = GetSceneObject(gameObject); + if (scene === gameObject) { + gameObject = undefined; + } + super(scene, config); + + this.gameObject = gameObject; + if (gameObject) { + gameObject.setInteractive(GetValue(config, "inputConfig", undefined)); + } + this._enable = undefined; + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.setEnable(GetValue(o, 'enable', true)); + + this.setDetectBounds() + if (this.gameObject === undefined) { + this.setDetectBounds(GetValue(o, 'bounds', undefined)); + } else { + this.setDetectBounds(); + } + + this.tracerState = TOUCH0; + // this.recongizedState = new stateClass(this); + this.pointer = undefined; + this.lastPointer = undefined; // Last catched pointer + this.movedState = false; + this.isTouchingAnyObject = false; + return this; + } + + boot() { + super.boot(); + if (this.gameObject) { + this.gameObject.on('pointerdown', this.onPointerDown, this); + } else { + this.scene.input.on('pointerdown', this.onPointerDown, this); + } + this.scene.input.on('pointerup', this.onPointerUp, this); + this.scene.input.on('gameout', this.dragCancel, this); + + this.scene.input.on('pointermove', this.onPointerMove, this); + this.scene.sys.events.once('shutdown', this.destroy, this); + } + + shutdown(fromScene) { + if (!this.scene) { + return + } + + if (this.gameObject) { + // GameObject events will be removed when this gameObject destroyed + // this.gameObject.off('pointerdown', this.onPointerDown, this); + } else { + this.scene.input.off('pointerdown', this.onPointerDown, this); + } + this.scene.input.off('pointerup', this.onPointerUp, this); + this.scene.input.off('gameout', this.dragCancel, this); + + this.scene.input.off('pointermove', this.onPointerMove, this); + this.scene.sys.events.off('shutdown', this.destroy, this); + + this.gameObject = undefined; + this.bounds = undefined; + this.pointer = undefined; + this.lastPointer = undefined; // Last catched pointer + this.movedState = false; + + super.shutdown(fromScene); + } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + + if (!e) { + this.dragCancel(); + } + this._enable = e; + return this; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + setDetectBounds(bounds) { + this.bounds = bounds; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + onPointerDown(pointer, gameObjects) { + if (!this.enable) { + return; + } + + if (this.pointer !== undefined) { + return; + } + + var isInsideBounds = (this.bounds) ? this.bounds.contains(pointer.x, pointer.y) : true; + if (!isInsideBounds) { + return; + } + + if (this.pointer === pointer) { + return; + } + + this.pointer = pointer; + this.lastPointer = pointer; + this.movedState = false; + this.tracerState = TOUCH1; + + if (this.gameObject === undefined) { + this.isTouchingAnyObject = (gameObjects.length > 0); + } + this.onDragStart(); + } + + onPointerUp(pointer) { + if (!this.enable) { + return; + } + + var isInsideBounds = (this.bounds) ? this.bounds.contains(pointer.x, pointer.y) : true; + if (!isInsideBounds) { + return; + } + + if (this.pointer !== pointer) { + return; + } + + this.pointer = undefined; + this.movedState = false; + this.tracerState = TOUCH0; + this.onDragEnd(); + } + + onPointerMove(pointer) { + if (!this.enable) { + return; + } + + if (pointer.isDown) { + var isInsideBounds = (this.bounds) ? this.bounds.contains(pointer.x, pointer.y) : true; + var isCatchedPointer = (this.pointer === pointer); + if (!isCatchedPointer && isInsideBounds) { // Pointer moves into bounds + // this.onPointerDown(pointer); + } else if (isCatchedPointer && !isInsideBounds) { // Pointer moves out of bounds + this.onPointerUp(pointer); + } else { // Pointer drags in bounds + if (!this.movedState) { + this.movedState = (pointer.x !== pointer.downX) || (pointer.y !== pointer.downY); + } + if (this.movedState) { + this.onDrag(); + } + } + } else { + // var isInsideBounds = (this.bounds) ? this.bounds.contains(pointer.x, pointer.y) : true; + // var isLastCatchedPointer = (this.lastPointer === pointer); + // if (isLastCatchedPointer && isInsideBounds) { + // this.onLastPointerMove(); + // } + } + } + + dragCancel() { + if (this.tracerState === TOUCH1) { + this.onDragEnd(); + } + this.pointer = undefined; + this.tracerState = TOUCH0; + return this; + } + + onDragStart() { + this.emit('dragstart', this); + } + + onDragEnd() { + this.emit('dragend', this); + } + + onDrag() { + this.emit('drag', this); + } + + // onLastPointerMove() { } + + preUpdate(time, delta) { } + + postUpdate(time, delta) { } + + startTicking() { + super.startTicking(); + this.scene.sys.events.on('preupdate', this.preUpdate, this); + this.scene.sys.events.on('postupdate', this.postUpdate, this); + } + + stopTicking() { + super.stopTicking(); + if (this.scene) { // Scene might be destoryed + this.scene.sys.events.off('preupdate', this.preUpdate, this); + this.scene.sys.events.off('postupdate', this.postUpdate, this); + } + } + + setRecongizedStateObject(stateObject) { + this.recongizedState = stateObject; + return this; + } + + get state() { + return this.recongizedState.state; + } + + set state(newState) { + this.recongizedState.state = newState; + } + + cancel() { + this.state = IDLE; + return this; + } +} + +const TOUCH0 = 0; +const TOUCH1 = 1; + +const IDLE = 'IDLE'; + +export default OnePointerTracer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Factory.d.ts new file mode 100644 index 000000000..459567703 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Factory.d.ts @@ -0,0 +1,6 @@ +import Pan from "./Pan"; + +export default function ( + gameObject: Phaser.GameObjects.GameObject | Phaser.Scene, + config?: Pan.IConfig +): Pan; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Factory.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Factory.js new file mode 100644 index 000000000..cd51d12d9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Factory.js @@ -0,0 +1,16 @@ +import Pan from './Pan.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; +import IsGameObject from '../../../utils/system/IsGameObject.js'; + +ObjectFactory.register('pan', function (gameObject, config) { + if (!IsGameObject(gameObject)) { + config = gameObject; + gameObject = this.scene; + } + return new Pan(gameObject, config); +}); + +SetValue(window, 'RexPlugins.Gestures.Pan', Pan); + +export default Pan; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Pan.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Pan.d.ts new file mode 100644 index 000000000..8b27ed914 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Pan.d.ts @@ -0,0 +1,32 @@ +// import * as Phaser from 'phaser'; +import OnePointerTracer from '../onepointertracer/OnePointerTracer'; + + +export default Pan; + +declare namespace Pan { + + interface IConfig extends OnePointerTracer.IConfig { + threshold?: number, + } + + namespace Events { + type PanCallbackType = ( + pan: Pan, + gameObject: Phaser.GameObjects.GameObject, + lastPointer: Phaser.Input.Pointer + ) => void; + } +} + +declare class Pan extends OnePointerTracer { + constructor( + gameObject: Phaser.GameObjects.GameObject | Phaser.Scene, + config?: Pan.IConfig + ) + + setDragThreshold(distance: number): this; + dragThreshold: number; + + readonly isPanned: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Pan.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Pan.js new file mode 100644 index 000000000..706318772 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pan/Pan.js @@ -0,0 +1,100 @@ +import OnePointerTracer from "../onepointertracer/OnePointerTracer.js"; +import FSM from '../../../fsm.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Pan extends OnePointerTracer { + constructor(gameObject, config) { + super(gameObject, config); + + var self = this; + var stateConfig = { + states: { + IDLE: { + }, + BEGIN: { + enter: function () { + var pointer = self.pointer; + self.startX = pointer.x; + self.startY = pointer.y; + self.startWorldX = pointer.worldX; + self.startWorldY = pointer.worldY; + } + }, + RECOGNIZED: { + enter: function () { + self.emit('panstart', self, self.gameObject, self.lastPointer); + }, + exit: function () { + var pointer = self.lastPointer; + self.endX = pointer.x; + self.endY = pointer.y; + self.endWorldX = pointer.worldX; + self.endWorldY = pointer.worldY; + self.emit('panend', self, self.gameObject, self.lastPointer); + } + } + }, + init: function () { + this.state = IDLE; + }, + eventEmitter: false, + } + this.setRecongizedStateObject(new FSM(stateConfig)); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + this.setDragThreshold(GetValue(o, 'threshold', 10)); + return this; + } + + onDragStart() { + this.state = BEGIN; + if (this.dragThreshold === 0) { + this.state = RECOGNIZED; + } + } + + onDragEnd() { + this.state = IDLE; + } + + onDrag() { + switch (this.state) { + case BEGIN: + if (this.pointer.getDistance() >= this.dragThreshold) { + this.state = RECOGNIZED; + } + break; + + case RECOGNIZED: + var p1 = this.pointer.position; + var p0 = this.pointer.prevPosition; + this.dx = p1.x - p0.x; + this.dy = p1.y - p0.y; + var pointer = this.pointer; + this.x = pointer.x; + this.y = pointer.y; + this.worldX = pointer.worldX; + this.worldY = pointer.worldY; + this.emit('pan', this, this.gameObject, this.lastPointer); + break; + } + } + + get isPanned() { + return (this.state === RECOGNIZED); + } + + setDragThreshold(distance) { + this.dragThreshold = distance; + return this; + } +} + +const IDLE = 'IDLE'; +const BEGIN = 'BEGIN'; +const RECOGNIZED = 'RECOGNIZED'; + +export default Pan; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Factory.d.ts new file mode 100644 index 000000000..3af0755e0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Factory.d.ts @@ -0,0 +1,7 @@ +// import * as Phaser from 'phaser'; +import Pinch from "./Pinch"; + +export default function ( + gameObject: Phaser.GameObjects.GameObject | Phaser.Scene, + config?: Pinch.IConfig +): Pinch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Factory.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Factory.js new file mode 100644 index 000000000..c7f70180a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Factory.js @@ -0,0 +1,11 @@ +import Pinch from './Pinch.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('pinch', function (config) { + return new Pinch(this.scene, config); +}); + +SetValue(window, 'RexPlugins.Gestures.Pinch', Pinch); + +export default Pinch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Pinch.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Pinch.d.ts new file mode 100644 index 000000000..d3529f24d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Pinch.d.ts @@ -0,0 +1,30 @@ +// import * as Phaser from 'phaser'; +import TwoPointersTracer from '../twopointerstracer/TwoPointersTracer'; + +export default Pinch; + +declare namespace Pinch { + + interface IConfig extends TwoPointersTracer.IConfig { + threshold?: number, + } + + namespace Events { + type PinchCallbackType = ( + pinch: Pinch, + ) => void; + } +} + +declare class Pinch extends TwoPointersTracer { + constructor( + gameObject: Phaser.GameObjects.GameObject | Phaser.Scene, + config?: Pinch.IConfig + ) + + setDragThreshold(distance: number): this; + dragThreshold: number; + + readonly scaleFactor: number; + readonly isPinched: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Pinch.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Pinch.js new file mode 100644 index 000000000..2a02aff68 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/pinch/Pinch.js @@ -0,0 +1,91 @@ +import TwoPointersTracer from '../twopointerstracer/TwoPointersTracer.js'; +import FSM from '../../../fsm.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Pinch extends TwoPointersTracer { + constructor(scene, config) { + super(scene, config); + + var self = this; + var stateConfig = { + states: { + IDLE: { + enter: function () { + self.prevDistance = undefined; + self.scaleFactor = 1; + }, + }, + BEGIN: { + }, + RECOGNIZED: { + enter: function () { + self.emit('pinchstart', self); + }, + exit: function () { + self.emit('pinchend', self); + } + } + }, + init: function () { + this.state = IDLE; + }, + eventEmitter: false, + } + this.setRecongizedStateObject(new FSM(stateConfig)); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + this.setDragThreshold(GetValue(o, 'threshold', 0)); + return this; + } + + onDrag2Start() { + this.scaleFactor = 1; + this.prevDistance = this.distanceBetween; + this.state = BEGIN; + if (this.dragThreshold === 0) { + this.state = RECOGNIZED; + } + } + + onDrag2End() { + this.state = IDLE; + } + + onDrag2() { + switch (this.state) { + case BEGIN: + if ((this.pointers[0].getDistance() >= this.dragThreshold) && + (this.pointers[1].getDistance() >= this.dragThreshold)) { + var curDistance = this.distanceBetween; + this.scaleFactor = curDistance / this.prevDistance; + this.prevDistance = curDistance; + this.state = RECOGNIZED; + } + break; + case RECOGNIZED: + var curDistance = this.distanceBetween; + this.scaleFactor = curDistance / this.prevDistance; + this.emit('pinch', this); + this.prevDistance = curDistance; + break; + } + } + + get isPinched() { + return (this.state === RECOGNIZED); + } + + setDragThreshold(distance) { + this.dragThreshold = distance; + return this; + } +} + +const IDLE = 'IDLE'; +const BEGIN = 'BEGIN'; +const RECOGNIZED = 'RECOGNIZED'; + +export default Pinch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Factory.d.ts new file mode 100644 index 000000000..51c70e26a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Factory.d.ts @@ -0,0 +1,7 @@ +// import * as Phaser from 'phaser'; +import Press from "./Press"; + +export default function ( + gameObject: Phaser.GameObjects.GameObject | Phaser.Scene, + config?: Press.IConfig +): Press; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Factory.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Factory.js new file mode 100644 index 000000000..9d8ab8d94 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Factory.js @@ -0,0 +1,16 @@ +import Press from './Press.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; +import IsGameObject from '../../../utils/system/IsGameObject.js'; + +ObjectFactory.register('press', function (gameObject, config) { + if (!IsGameObject(gameObject)) { + config = gameObject; + gameObject = this.scene; + } + return new Press(gameObject, config); +}); + +SetValue(window, 'RexPlugins.Gestures.Press', Press); + +export default Press; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Press.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Press.d.ts new file mode 100644 index 000000000..4e7bd9c71 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Press.d.ts @@ -0,0 +1,34 @@ +// import * as Phaser from 'phaser'; +import OnePointerTracer from '../onepointertracer/OnePointerTracer'; + +export default Press; + +declare namespace Press { + + export interface IConfig extends OnePointerTracer.IConfig { + time?: number, + threshold?: number, + } + + namespace Events { + type PressCallbackType = ( + press: Press, + gameObject: Phaser.GameObjects.GameObject, + lastPointer: Phaser.Input.Pointer + ) => void; + } +} + +declare class Press extends OnePointerTracer { + constructor( + gameObject: Phaser.GameObjects.GameObject | Phaser.Scene, + config?: Press.IConfig + ) + + setHoldTime(time: number): this; + holdTime: number; + setDragThreshold(distance: number): this; + dragThreshold: number; + + readonly isPressed: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Press.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Press.js new file mode 100644 index 000000000..b4bef7d1f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/press/Press.js @@ -0,0 +1,112 @@ +import OnePointerTracer from "../onepointertracer/OnePointerTracer.js"; +import FSM from '../../../fsm.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Press extends OnePointerTracer { + constructor(gameObject, config) { + super(gameObject, config); + + var self = this; + var stateConfig = { + states: { + IDLE: { + enter: function () { + self.x = 0; + self.y = 0; + self.worldX = 0; + self.worldY = 0; + }, + exit: function () { + var pointer = self.lastPointer; + self.x = pointer.x; + self.y = pointer.y; + self.worldX = pointer.worldX; + self.worldY = pointer.worldY; + } + }, + BEGIN: { + enter: function () { + self.start(); + }, + exit: function () { + self.stop(); + } + }, + RECOGNIZED: { + enter: function () { + self.emit('pressstart', self, self.gameObject, self.lastPointer); + }, + exit: function () { + self.emit('pressend', self, self.gameObject, self.lastPointer); + } + } + }, + init: function () { + this.state = IDLE; + }, + eventEmitter: false, + } + this.setRecongizedStateObject(new FSM(stateConfig)); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + this.setDragThreshold(GetValue(o, 'threshold', 9)); + this.setHoldTime(GetValue(o, 'time', 251)); + return this; + } + + onDragStart() { + this.state = BEGIN; + if (this.holdTime === 0) { + this.state = RECOGNIZED; + } + } + + onDragEnd() { + this.state = IDLE; + } + + onDrag() { + if (this.state === IDLE) { + return; + } + + if (this.pointer.getDistance() > this.dragThreshold) { + this.state = IDLE; + } + } + + preUpdate(time, delta) { + if ((!this.isRunning) || (!this.enable)) { + return; + } + if (this.state === BEGIN) { + var holdTime = time - this.pointer.downTime; + if (holdTime >= this.holdTime) { + this.state = RECOGNIZED; + } + } + } + + get isPressed() { + return (this.state === RECOGNIZED); + } + + setHoldTime(time) { + this.holdTime = time; // ms + return this; + } + + setDragThreshold(distance) { + this.dragThreshold = distance; + return this; + } +} + +const IDLE = 'IDLE'; +const BEGIN = 'BEGIN'; +const RECOGNIZED = 'RECOGNIZED'; + +export default Press; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Factory.d.ts new file mode 100644 index 000000000..655baafa3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Factory.d.ts @@ -0,0 +1,7 @@ +// import * as Phaser from 'phaser'; +import Rotate from "./Rotate"; + +export default function ( + gameObject: Phaser.GameObjects.GameObject | Phaser.Scene, + config?: Rotate.IConfig +): Rotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Factory.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Factory.js new file mode 100644 index 000000000..4adf70368 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Factory.js @@ -0,0 +1,11 @@ +import Rotate from './Rotate.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('rotate', function (config) { + return new Rotate(this.scene, config); +}); + +SetValue(window, 'RexPlugins.Gestures.Rotate', Rotate); + +export default Rotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Rotate.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Rotate.d.ts new file mode 100644 index 000000000..c4bbab87a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Rotate.d.ts @@ -0,0 +1,35 @@ +// import * as Phaser from 'phaser'; +import TwoPointersTracer from '../twopointerstracer/TwoPointersTracer'; + + +export default Rotate; + +declare namespace Rotate { + + interface IConfig extends TwoPointersTracer.IConfig { + threshold?: number, + } + + namespace Events { + type RotateCallbackType = ( + rotate: Rotate, + ) => void; + } +} + +declare class Rotate extends TwoPointersTracer { + constructor( + gameObject: Phaser.GameObjects.GameObject | Phaser.Scene, + config?: Rotate.IConfig + ) + + setDragThreshold(distance: number): this; + dragThreshold: number; + + spinObject( + gameObejects: Phaser.GameObjects.GameObject | Phaser.GameObjects.GameObject[] + ): this; + + readonly rotation: number; + readonly isRotated: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Rotate.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Rotate.js new file mode 100644 index 000000000..a328076d7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/Rotate.js @@ -0,0 +1,109 @@ +import TwoPointersTracer from '../twopointerstracer/TwoPointersTracer.js'; +import FSM from '../../../fsm.js'; +import SpinObject from './SpinObject.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const WrapDegrees = Phaser.Math.Angle.WrapDegrees; // Wrap degrees: -180 to 180 +const ShortestBetween = Phaser.Math.Angle.ShortestBetween; +const RadToDeg = Phaser.Math.RadToDeg; +const DegToRad = Phaser.Math.DegToRad; + +class Rotate extends TwoPointersTracer { + constructor(scene, config) { + super(scene, config); + + var self = this; + var stateConfig = { + states: { + IDLE: { + enter: function () { + self.prevAngle = undefined; + self.angle = 0; + }, + }, + BEGIN: { + }, + RECOGNIZED: { + enter: function () { + self.emit('rotatestart', self); + }, + exit: function () { + self.emit('rotateend', self); + } + } + }, + init: function () { + this.state = IDLE; + }, + eventEmitter: false, + } + this.setRecongizedStateObject(new FSM(stateConfig)); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + this.setDragThreshold(GetValue(o, 'threshold', 0)); + return this; + } + + onDrag2Start() { + this.prevAngle = WrapDegrees(RadToDeg(this.angleBetween)); // Degrees + this.state = BEGIN; + if (this.dragThreshold === 0) { + this.state = RECOGNIZED; + } + } + + onDrag2End() { + this.state = IDLE; + } + + onDrag2() { + switch (this.state) { + case BEGIN: + if ((this.pointers[0].getDistance() >= this.dragThreshold) && + (this.pointers[1].getDistance() >= this.dragThreshold)) { + var curAngle = WrapDegrees(RadToDeg(this.angleBetween)); + this.angle = ShortestBetween(this.prevAngle, curAngle); + this.prevAngle = curAngle; + this.state = RECOGNIZED; + } + break; + case RECOGNIZED: + var curAngle = WrapDegrees(RadToDeg(this.angleBetween)); + this.angle = ShortestBetween(this.prevAngle, curAngle); + this.prevAngle = curAngle; + this.emit('rotate', this); + break; + } + } + + get isRotated() { + return (this.state === RECOGNIZED); + } + + get rotation() { + return DegToRad(this.angle); + } + + setDragThreshold(distance) { + this.dragThreshold = distance; + return this; + } + +} + +var methods = { + spinObject: SpinObject, +}; +Object.assign( + Rotate.prototype, + methods +); + + +const IDLE = 'IDLE'; +const BEGIN = 'BEGIN'; +const RECOGNIZED = 'RECOGNIZED'; + +export default Rotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/SpinObject.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/SpinObject.js new file mode 100644 index 000000000..d96410626 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/rotate/SpinObject.js @@ -0,0 +1,37 @@ +import RotateObjectAround from '../../../utils/actions/RotateObjectAround.js'; +import ScreenXYToWorldXY from '../../../utils/position/ScreenXYToWorldXY.js'; + +var SpinObject = function (gameObject, camera) { + if (!this.isRotation) { + return this; + } + + if (camera === undefined) { + camera = this.pointers[0].camera; + } + + var movementX = this.movementCenterX, + movementY = this.movementCenterY; + + var worldXY = ScreenXYToWorldXY(this.centerX, this.centerY, camera, true); + var centerWorldX = worldXY.x; + var centerWorldY = worldXY.y; + + var angle = this.rotation; + if (Array.isArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + gameObject = gameObjects[i]; + gameObject.x += movementX; + gameObject.y += movementY; + RotateObjectAround(gameObject, centerWorldX, centerWorldY, angle); + } + } else { + gameObject.x += movementX; + gameObject.y += movementY; + RotateObjectAround(gameObject, centerWorldX, centerWorldY, angle); + } + return this; +} + +export default SpinObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Factory.d.ts new file mode 100644 index 000000000..2f737eb8b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Factory.d.ts @@ -0,0 +1,7 @@ +// import * as Phaser from 'phaser'; +import Swipe from "./Swipe"; + +export default function ( + gameObject: Phaser.GameObjects.GameObject | Phaser.Scene, + config?: Swipe.IConfig +): Swipe; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Factory.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Factory.js new file mode 100644 index 000000000..5eeaa5186 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Factory.js @@ -0,0 +1,16 @@ +import Swipe from './Swipe.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; +import IsGameObject from '../../../utils/system/IsGameObject.js'; + +ObjectFactory.register('swipe', function (gameObject, config) { + if (!IsGameObject(gameObject)) { + config = gameObject; + gameObject = this.scene; + } + return new Swipe(gameObject, config); +}); + +SetValue(window, 'RexPlugins.Gestures.Swipe', Swipe); + +export default Swipe; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Swipe.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Swipe.d.ts new file mode 100644 index 000000000..bc9861c3c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Swipe.d.ts @@ -0,0 +1,41 @@ +// import * as Phaser from 'phaser'; +import OnePointerTracer from '../onepointertracer/OnePointerTracer'; + +export default Swipe; + +declare namespace Swipe { + + interface IConfig extends OnePointerTracer.IConfig { + threshold?: number, + velocityThreshold?: number, + dir: 0 | 1 | 2 | 3 | 'up&down' | 'left&right' | '4dir' | '8dir', + } + + namespace Events { + type SwipeCallbackType = ( + swipe: Swipe, + gameObject: Phaser.GameObjects.GameObject, + lastPointer: Phaser.Input.Pointer + ) => void; + } +} + +declare class Swipe extends OnePointerTracer { + constructor( + gameObject: Phaser.GameObjects.GameObject | Phaser.Scene, + config?: Swipe.IConfig + ) + + setDragThreshold(distance: number): this; + dragThreshold: number; + + setVelocityThreshold(velocity: number): this; + velocityThreshold: number; + + setDirectionMode( + dirMode: 0 | 1 | 2 | 3 | 'up&down' | 'left&right' | '4dir' | '8dir' + ): this; + dirMode: number; + + readonly isSwiped: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Swipe.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Swipe.js new file mode 100644 index 000000000..644ea4e9b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/Swipe.js @@ -0,0 +1,152 @@ +import OnePointerTracer from "../onepointertracer/OnePointerTracer.js"; +import FSM from '../../../fsm.js'; +import VelocityMethods from './VelocityMethods.js'; +import DIRMODE from '../../../utils/math/angle/angletodirections/Const.js'; +import AngleToDirections from '../../../utils/math/angle/angletodirections/AngleToDirections.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const RadToDeg = Phaser.Math.RadToDeg; + +class Swipe extends OnePointerTracer { + constructor(gameObject, config) { + super(gameObject, config); + + var self = this; + var stateConfig = { + states: { + IDLE: { + enter: function () { + self.x = 0; + self.y = 0; + self.worldX = 0; + self.worldY = 0; + }, + exit: function () { + var pointer = self.lastPointer; + self.x = pointer.x; + self.y = pointer.y; + self.worldX = pointer.worldX; + self.worldY = pointer.worldY; + } + }, + BEGIN: { + enter: function () { + self.validDrag = false; + } + }, + RECOGNIZED: { + enter: function () { + self.start(); + self.updateDirectionStates(); + self.emit('swipe', self, self.gameObject, self.lastPointer); + }, + + exit: function () { + self.stop(); + self.clearDirectionStates(); + } + } + }, + init: function () { + this.state = IDLE; + }, + eventEmitter: false, + } + this.setRecongizedStateObject(new FSM(stateConfig)); + this.clearDirectionStates(); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + this.setDragThreshold(GetValue(o, 'threshold', 10)); + this.setVelocityThreshold(GetValue(o, 'velocityThreshold', 1000)); + this.setDirectionMode(GetValue(o, 'dir', '8dir')); + return this; + } + + onDragStart() { + this.state = BEGIN; + } + + onDragEnd() { + this.state = IDLE; + } + + onDrag() { + if (this.state === BEGIN) { + if (!this.validDrag) { + this.validDrag = (this.dragThreshold === 0) || (this.pointer.getDistance() >= this.dragThreshold); + } + if (this.validDrag && (this.dragVelocity > this.velocityThreshold)) { + this.state = RECOGNIZED; + } + } + } + + postUpdate(time, delta) { + if ((!this.isRunning) || (!this.enable)) { + return; + } + // Clear RECOGNIZED after update() + if (this.state === RECOGNIZED) { + this.state = IDLE; + } + } + + get isSwiped() { + return (this.state === RECOGNIZED); + } + + get dragVelocity() { + var velocity; + switch (this.dirMode) { + case 0: velocity = this.getVelocityY(); break; // up & down + case 1: velocity = this.getVelocityX(); break; // left & right + default: velocity = this.getVelocity(); break; // 4 dir, 8 dir + } + return velocity; + } + + setDragThreshold(distance) { + this.dragThreshold = distance; + return this; + } + + setVelocityThreshold(velocity) { + this.velocityThreshold = velocity; + return this; + } + + setDirectionMode(m) { + if (typeof (m) === 'string') { + m = DIRMODE[m]; + } + this.dirMode = m; + return this; + } + + updateDirectionStates() { + var angle = RadToDeg(this.getVelocityAngle()); + AngleToDirections(angle, this.dirMode, this); + return this; + } + + clearDirectionStates() { + this.left = false; + this.right = false; + this.up = false; + this.down = false; + return this; + } +} + +Object.assign( + Swipe.prototype, + VelocityMethods +); + +const IDLE = 'IDLE'; +const BEGIN = 'BEGIN'; +const RECOGNIZED = 'RECOGNIZED'; + +export default Swipe; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/VelocityMethods.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/VelocityMethods.js new file mode 100644 index 000000000..b89e667c2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/swipe/VelocityMethods.js @@ -0,0 +1,42 @@ +import GetTickDelta from '../../../utils/system/GetTickDelta.js'; + +const DistanceBetween = Phaser.Math.Distance.Between; +const AngleBetween = Phaser.Math.Angle.Between; + +export default { + getDt: function () { + var dt = GetTickDelta(this.scene); + return dt; + }, + + getVelocity: function () { + var p1 = this.pointer.position; + var p0 = this.pointer.prevPosition; + var d = DistanceBetween(p0.x, p0.y, p1.x, p1.y); + var velocity = d / (this.getDt() * 0.001); + return velocity; + }, + + getVelocityX: function () { + var p1 = this.pointer.position; + var p0 = this.pointer.prevPosition; + var d = Math.abs(p1.x - p0.x); + var velocity = d / (this.getDt() * 0.001); + return velocity; + }, + + getVelocityY: function () { + var p1 = this.pointer.position; + var p0 = this.pointer.prevPosition; + var d = Math.abs(p1.y - p0.y); + var velocity = d / (this.getDt() * 0.001); + return velocity; + }, + + getVelocityAngle: function () { + var p1 = this.pointer.position; + var p0 = this.pointer.prevPosition; + var angle = AngleBetween(p0.x, p0.y, p1.x, p1.y); + return angle; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Factory.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Factory.d.ts new file mode 100644 index 000000000..6830651c7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Factory.d.ts @@ -0,0 +1,7 @@ +// import * as Phaser from 'phaser'; +import Tap from "./Tap"; + +export default function ( + gameObject: Phaser.GameObjects.GameObject | Phaser.Scene, + config?: Tap.IConfig +): Tap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Factory.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Factory.js new file mode 100644 index 000000000..ff151f0b0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Factory.js @@ -0,0 +1,16 @@ +import Tap from './Tap.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; +import IsGameObject from '../../../utils/system/IsGameObject.js'; + +ObjectFactory.register('tap', function (gameObject, config) { + if (!IsGameObject(gameObject)) { + config = gameObject; + gameObject = this.scene; + } + return new Tap(gameObject, config); +}); + +SetValue(window, 'RexPlugins.Gestures.Tap', Tap); + +export default Tap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Tap.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Tap.d.ts new file mode 100644 index 000000000..0bb260f5d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Tap.d.ts @@ -0,0 +1,52 @@ +// import * as Phaser from 'phaser'; +import OnePointerTracer from '../onepointertracer/OnePointerTracer'; + +export default Tap; + +declare namespace Tap { + + export interface IConfig extends OnePointerTracer.IConfig { + time?: number, + tapInterval?: number, + threshold?: number, + tapOffset?: number, + + taps?: number | undefined, + minTaps?: number | undefined, + maxTaps?: number | undefined, + } + + namespace Events { + type TapCallbackType = ( + tap: Tap, + gameObject: Phaser.GameObjects.GameObject, + lastPointer: Phaser.Input.Pointer + ) => void; + } + +} + +declare class Tap extends OnePointerTracer { + constructor( + gameObject: Phaser.GameObjects.GameObject | Phaser.Scene, + config?: Tap.IConfig + ) + + setHoldTime(time: number): this; + holdTime: number; + setTapInterval(time: number): this; + tapInterval: number; + + setDragThreshold(distance: number): this; + dragThreshold: number; + setTapOffset(distance: number): this; + tapOffset: number; + + setMaxTaps(amount: number): this; + maxTaps: number; + setMaxTaps(amount: number): this; + minTaps: number; + setTaps(minTaps: number, maxTaps: number): this; + + readonly isTapped: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Tap.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Tap.js new file mode 100644 index 000000000..9d28d66fc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/tap/Tap.js @@ -0,0 +1,198 @@ +import OnePointerTracer from "../onepointertracer/OnePointerTracer.js"; +import FSM from '../../../fsm.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const DistanceBetween = Phaser.Math.Distance.Between; + +class Tap extends OnePointerTracer { + constructor(gameObject, config) { + super(gameObject, config); + + var self = this; + var stateConfig = { + states: { + IDLE: { + enter: function () { + self.stop(); + self.tapsCount = 0; + self.x = 0; + self.y = 0; + self.worldX = 0; + self.worldY = 0; + }, + exit: function () { + var pointer = self.lastPointer; + self.x = pointer.x; + self.y = pointer.y; + self.worldX = pointer.worldX; + self.worldY = pointer.worldY; + } + }, + BEGIN: { + enter: function () { + self.start(); + self.tapsCount = 0; + self.emit('tappingstart', self, self.gameObject, self.lastPointer); + }, + }, + RECOGNIZED: { + enter: function () { + self.start(); + self.emit('tap', self, self.gameObject, self.lastPointer); + self.emit(`${self.tapsCount}tap`, self, self.gameObject, self.lastPointer); + }, + } + }, + init: function () { + this.state = IDLE; + }, + eventEmitter: false, + } + this.setRecongizedStateObject(new FSM(stateConfig)); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + this.setHoldTime(GetValue(o, 'time', 250)); // min-hold-time of Press is 251 + this.setTapInterval(GetValue(o, 'tapInterval', 200)); + this.setDragThreshold(GetValue(o, 'threshold', 9)); + this.setTapOffset(GetValue(o, 'tapOffset', 10)); + + var taps = GetValue(o, 'taps', undefined); + if (taps !== undefined) { + this.setTaps(taps); + } else { + this.setMaxTaps(GetValue(o, 'maxTaps', undefined)); + this.setMinTaps(GetValue(o, 'minTaps', undefined)); + } + return this; + } + + onDragStart() { + switch (this.state) { + case IDLE: + this.state = BEGIN; + break; + + case BEGIN: + var pointer = this.lastPointer; + var tapsOffset = DistanceBetween( + pointer.upX, + pointer.upY, + pointer.x, + pointer.y); + if (tapsOffset > this.tapOffset) { // Can't recognize next level, restart here + this.state = RECOGNIZED; + this.state = BEGIN; + } + break; + + case RECOGNIZED: + this.state = BEGIN; + break; + } + } + + onDragEnd() { + if (this.state === BEGIN) { + this.tapsCount++; // Try recognize next level + this.emit('tapping', this, this.gameObject, this.lastPointer); + + if ((this.maxTaps !== undefined) && (this.tapsCount === this.maxTaps)) { // Reach to maxTaps, stop here + this.state = RECOGNIZED; + } + } + } + + onDrag() { + if (this.state === IDLE) { + return; + } + + if (this.pointer.getDistance() > this.dragThreshold) { // Cancel + this.state = IDLE; + } + } + + preUpdate(time, delta) { + if ((!this.isRunning) || (!this.enable)) { + return; + } + if (this.state === BEGIN) { + var pointer = this.lastPointer; + if (pointer.isDown) { + var holdTime = time - pointer.downTime; + if (holdTime > this.holdTime) { + this.state = IDLE; + } + } else { // isUp + var releasedTime = time - pointer.upTime; + if (releasedTime > this.tapInterval) { + if ((this.minTaps === undefined) || (this.tapsCount >= this.minTaps)) { + this.state = RECOGNIZED; + } else { + this.state = IDLE; + } + } + } + } + } + + postUpdate(time, delta) { + if ((!this.isRunning) || (!this.enable)) { + return; + } + // Clear RECOGNIZED after update() + if (this.state === RECOGNIZED) { + this.state = IDLE; + } + } + + get isTapped() { + return (this.state === RECOGNIZED); + } + + setHoldTime(time) { + this.holdTime = time; // ms + return this; + } + + setTapInterval(time) { + this.tapInterval = time; // ms + return this; + } + + setDragThreshold(distance) { + this.dragThreshold = distance; + return this; + } + + setTapOffset(distance) { + this.tapOffset = distance; + return this; + } + + setMaxTaps(taps) { + this.maxTaps = taps; + return this; + } + + setMinTaps(taps) { + this.minTaps = taps; + return this; + } + + setTaps(minTaps, maxTaps) { + if (maxTaps === undefined) { + maxTaps = minTaps; + } + this.setMinTaps(minTaps).setMaxTaps(maxTaps); + return this; + } +} + +const IDLE = 'IDLE'; +const BEGIN = 'BEGIN'; +const RECOGNIZED = 'RECOGNIZED'; + +export default Tap; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/twopointerstracer/TwoPointersTracer.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/gestures/twopointerstracer/TwoPointersTracer.d.ts new file mode 100644 index 000000000..8cee72d67 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/twopointerstracer/TwoPointersTracer.d.ts @@ -0,0 +1,36 @@ +// import * as Phaser from 'phaser'; + +export default TwoPointersTracer; + +declare namespace TwoPointersTracer { + + interface IConfig { + enable?: boolean, + bounds?: Phaser.Geom.Rectangle, + eventEmitter?: boolean | Phaser.Events.EventEmitter, + } + +} + +declare class TwoPointersTracer extends Phaser.Events.EventEmitter { + + enable: boolean; + setEnable(enable?: boolean): this; + toggleEnable(): this; + + bounds: Phaser.Geom.Rectangle | undefined; + setDetectBounds(bounds?: Phaser.Geom.Rectangle): this; + + cancel(): this; + + pointers: Phaser.Input.Pointer[]; + readonly distanceBetween: number; + readonly angleBetween: number; + readonly centerX: number; + readonly centerY: number; + readonly prevCenterX: number; + readonly prevCenterY: number; + readonly movementCenterX: number; + readonly movementCenterY: number; + readonly drag1Vector: { x: number, y: number }; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/gestures/twopointerstracer/TwoPointersTracer.js b/ui/src/phaser3-rex-plugins/plugins/input/gestures/twopointerstracer/TwoPointersTracer.js new file mode 100644 index 000000000..ec4f3aad8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/gestures/twopointerstracer/TwoPointersTracer.js @@ -0,0 +1,336 @@ +import EventEmitterMethods from '../../../utils/eventemitter/EventEmitterMethods.js'; +import Clear from '../../../utils/object/Clear.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const SpliceOne = Phaser.Utils.Array.SpliceOne; +const DistanceBetween = Phaser.Math.Distance.Between; +const AngleBetween = Phaser.Math.Angle.Between; + +class TwoPointersTracer { + constructor(scene, config) { + var amount = scene.input.manager.pointersTotal - 1; + if (amount < 2) { + scene.input.addPointer(2 - amount); + } + + this.scene = scene; + // Event emitter + this.setEventEmitter(GetValue(config, 'eventEmitter', undefined)); + + this._enable = undefined; + this.pointers = []; + this.movedState = {}; + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.setEnable(GetValue(o, "enable", true)); + this.bounds = GetValue(o, 'bounds', undefined); + + this.tracerState = TOUCH0; + this.pointers.length = 0; + Clear(this.movedState); + return this; + } + + boot() { + this.scene.input.on('pointerdown', this.onPointerDown, this); + + this.scene.input.on('pointerup', this.onPointerUp, this); + this.scene.input.on('gameout', this.dragCancel, this); + + this.scene.input.on('pointermove', this.onPointerMove, this); + this.scene.sys.events.once('shutdown', this.destroy, this); + } + + shutdown() { + if (!this.scene) { + return + } + + this.destroyEventEmitter(); + this.pointers.length = 0; + Clear(this.movedState); + this.scene.input.off('pointerdown', this.onPointerDown, this); + + this.scene.input.off('pointerup', this.onPointerUp, this); + this.scene.input.off('gameout', this.dragCancel, this); + + this.scene.input.off('pointermove', this.onPointerMove, this); + this.scene.sys.events.off('shutdown', this.destroy, this); + this.scene = undefined; + } + + destroy() { + this.shutdown(); + } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + + if (!e) { + this.dragCancel(); + } + this._enable = e; + return this; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + onPointerDown(pointer) { + if (!this.enable) { + return; + } + + if (this.pointers.length === 2) { + return; + } + + var isInsideBounds = (this.bounds) ? this.bounds.contains(pointer.x, pointer.y) : true; + if (!isInsideBounds) { + return; + } + + var index = this.pointers.indexOf(pointer); + if (index !== -1) { // Already in catched pointers + return; + } + + this.movedState[pointer.id] = false; + this.pointers.push(pointer); + + switch (this.tracerState) { + case TOUCH0: + this.tracerState = TOUCH1; + this.onDrag1Start(); + break; + case TOUCH1: + this.tracerState = TOUCH2; + this.onDrag2Start(); + break; + } + } + + onPointerUp(pointer) { + if (!this.enable) { + return; + } + + var isInsideBounds = (this.bounds) ? this.bounds.contains(pointer.x, pointer.y) : true; + if (!isInsideBounds) { + return; + } + + var index = this.pointers.indexOf(pointer); + if (index === -1) { // Not in catched pointers + return; + } else { + delete this.movedState[pointer.id]; + SpliceOne(this.pointers, index); + } + + switch (this.tracerState) { + case TOUCH1: + this.tracerState = TOUCH0; + this.onDrag1End(); + break; + case TOUCH2: + this.tracerState = TOUCH1; + this.onDrag2End(); + this.onDrag1Start(); + break; + } + } + + onPointerMove(pointer) { + if (!this.enable) { + return; + } + + if (pointer.isDown) { + var isInsideBounds = (this.bounds) ? this.bounds.contains(pointer.x, pointer.y) : true; + var isCatchedPointer = (this.pointers.indexOf(pointer) !== -1); + if (!isCatchedPointer && isInsideBounds) { // Pointer moves into bounds + // this.onPointerDown(pointer); + } else if (isCatchedPointer && !isInsideBounds) { // Pointer moves out of bounds, lose pointer + this.onPointerUp(pointer); + } else { // Pointer drags in bounds + if (!this.movedState[pointer.id]) { + this.movedState[pointer.id] = (pointer.x !== pointer.downX) || (pointer.y !== pointer.downY); + } + if (this.movedState[pointer.id]) { + switch (this.tracerState) { + case TOUCH1: + this.onDrag1(); + break; + case TOUCH2: + this.onDrag2(); + break; + } + } + } + } + } + + dragCancel() { + if (this.tracerState === TOUCH2) { + this.onDrag2End(); + } + this.pointers.length = 0; + Clear(this.movedState); + this.tracerState = TOUCH0; + return this; + } + + onDrag1Start() { + this.emit('drag1start', this); + } + + onDrag1End() { + this.emit('drag1end', this); + } + + onDrag1() { + this.emit('drag1', this); + } + + onDrag2Start() { + this.emit('drag2start', this); + } + + onDrag2End() { + this.emit('drag2end', this); + } + + onDrag2() { + this.emit('drag2', this); + } + + get distanceBetween() { + if (this.tracerState !== TOUCH2) { + return 0; + } + var p0 = this.pointers[0], + p1 = this.pointers[1]; + return DistanceBetween(p0.x, p0.y, p1.x, p1.y); + } + + get angleBetween() { + if (this.tracerState !== TOUCH2) { + return 0; + } + var p0 = this.pointers[0], + p1 = this.pointers[1]; + return AngleBetween(p0.x, p0.y, p1.x, p1.y); + } + + get drag1Vector() { + var pointer = this.pointers[0]; + if (pointer && this.movedState[pointer.id]) { + var p1 = pointer.position; + var p0 = pointer.prevPosition; + tmpDragVector.x = p1.x - p0.x; + tmpDragVector.y = p1.y - p0.y; + } else { + tmpDragVector.x = 0; + tmpDragVector.y = 0; + } + return tmpDragVector; + } + + get centerX() { + if (this.tracerState !== TOUCH2) { + return 0; + } + var p0 = this.pointers[0].position; + var p1 = this.pointers[1].position; + return (p0.x + p1.x) / 2; + } + + get centerY() { + if (this.tracerState !== TOUCH2) { + return 0; + } + var p0 = this.pointers[0].position; + var p1 = this.pointers[1].position; + return (p0.y + p1.y) / 2; + } + + get prevCenterX() { + if (this.tracerState !== TOUCH2) { + return 0; + } + var preP0 = (this.movedState[this.pointers[0].id]) ? this.pointers[0].prevPosition : this.pointers[0].position; + var preP1 = (this.movedState[this.pointers[1].id]) ? this.pointers[1].prevPosition : this.pointers[1].position; + return (preP0.x + preP1.x) / 2; + } + + get prevCenterY() { + if (this.tracerState !== TOUCH2) { + return 0; + } + var preP0 = (this.movedState[this.pointers[0].id]) ? this.pointers[0].prevPosition : this.pointers[0].position; + var preP1 = (this.movedState[this.pointers[1].id]) ? this.pointers[1].prevPosition : this.pointers[1].position; + return (preP0.y + preP1.y) / 2; + } + + get movementCenterX() { + return this.centerX - this.prevCenterX; + } + + get movementCenterY() { + return this.centerY - this.prevCenterY; + } + + setRecongizedStateObject(stateObject) { + this.recongizedState = stateObject; + return this; + } + + get state() { + return this.recongizedState.state; + } + + set state(newState) { + this.recongizedState.state = newState; + } + + cancel() { + this.state = IDLE; + return this; + } +} + +Object.assign( + TwoPointersTracer.prototype, + EventEmitterMethods +); + +var tmpDragVector = {}; + +const TOUCH0 = 0; +const TOUCH1 = 1; +const TOUCH2 = 2; + +const IDLE = 'IDLE'; + +export default TwoPointersTracer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/intouching/InTouching.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/intouching/InTouching.d.ts new file mode 100644 index 000000000..2898a9b12 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/intouching/InTouching.d.ts @@ -0,0 +1,28 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default InTouching; + +declare namespace InTouching { + + interface IConfig { + enable?: boolean, + cooldown?: number, + } +} + +declare class InTouching extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: InTouching.IConfig + ) + + prevIsInTouch: boolean; + isInTouching: boolean; + + setEnable(enable?: boolean): this; + toggleEnable(): this; + enable: boolean; + + setCooldown(time: number): this; + cooldownTime: number; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/input/intouching/InTouching.js b/ui/src/phaser3-rex-plugins/plugins/input/intouching/InTouching.js new file mode 100644 index 000000000..20363f1b7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/intouching/InTouching.js @@ -0,0 +1,137 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import Cooldown from '../../utils/time/cooldown/Cooldown.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class InTouching extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this._enable = undefined; + this.cooldown = new Cooldown(); + this.parent.setInteractive(GetValue(config, 'inputConfig', undefined)); + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.pointer = undefined; + this.prevIsInTouch = false; + this.isInTouching = false; + this.setEnable(GetValue(o, 'enable', true)); + this.setCooldown(GetValue(o, 'cooldown', undefined)); + return this; + } + + boot() { + var gameObject = this.parent; + gameObject.on('pointerdown', this.onPointIn, this); + gameObject.on('pointerover', this.onPointIn, this); + gameObject.on('pointerup', this.onPointOut, this); + gameObject.on('pointerout', this.onPointOut, this); + this.scene.sys.events.on('preupdate', this.preupdate, this); + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + // GameObject events will be removed when this gameObject destroyed + // this.parent.off('pointerdown', this.onPointIn, this); + // this.parent.off('pointerover', this.onPointIn, this); + // this.parent.off('pointerup', this.onPointOut, this); + // this.parent.off('pointerout', this.onPointOut, this); + this.scene.sys.events.off('preupdate', this.preupdate, this); + + this.pointer = undefined; + super.shutdown(fromScene); + } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + + if (!e) { + this.prevIsInTouch = false; + this.isInTouching = false; + this.pointer = undefined; + } + this._enable = e; + return this; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + get cooldownTime() { + return this.cooldown.cooldownTime; + } + + set cooldownTime(time) { + this.cooldown.setCooldownTime(time); + } + + setCooldown(time) { + this.cooldownTime = time; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + // internal + onPointIn(pointer, localX, localY) { + if ((!this.enable) || + (!pointer.isDown) || + (this.pointer !== undefined)) { + return; + } + this.pointer = pointer; + this.isInTouching = true; + } + + onPointOut(pointer) { + if ((!this.enable) || + (this.pointer !== pointer)) { + return; + } + this.pointer = undefined; + this.isInTouching = false; + } + + preupdate(time, delta) { + this.cooldown.update(time, delta); + + if (!this.prevIsInTouch && this.isInTouching) { + this.emit('touchstart', this, this.parent); + } + + if (this.isInTouching && this.cooldown.request()) { + this.emit('intouch', this, this.parent, this.pointer); + } + + if (this.prevIsInTouch && !this.isInTouching) { + this.emit('touchend', this, this.parent); + } + + this.prevIsInTouch = this.isInTouching; + } +} + +export default InTouching; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/keyshub/KeyHub.js b/ui/src/phaser3-rex-plugins/plugins/input/keyshub/KeyHub.js new file mode 100644 index 000000000..66144799d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/keyshub/KeyHub.js @@ -0,0 +1,113 @@ +import KeyMap from '../../utils/input/KeyMap.js'; + +const Key = Phaser.Input.Keyboard.Key; +const AddItem = Phaser.Utils.Array.Add; +const RemoveItem = Phaser.Utils.Array.Remove; + +class KeyHub extends Key { + constructor(parent, keyCode) { + super(parent, keyCode); + + this.ports = []; + } + + destroy() { + for (var i = 0, cnt = this.ports.length; i < cnt; i++) { + this.ports[i] + .off('down', this.update, this) + .off('up', this.update, this) + } + this.ports = undefined; + super.destroy(); + } + + plug(key) { + AddItem(this.ports, key, 0, function (key) { + key + .on('down', this.update, this) + .on('up', this.update, this) + + this.update(FakeEvent); + }, this); + return this; + } + + unplug(key) { + RemoveItem(this.ports, key, function (key) { + key + .off('down', this.update, this) + .off('up', this.update, this) + + this.update(FakeEvent); + }, this); + return this; + } + + update(event) { + // Override the default functions (it's too late for the browser to use them anyway, so we may as well) + if (event.cancelled === undefined) { + // Event allowed to flow across all handlers in this Scene, and any other Scene in the Scene list + event.cancelled = 0; + + // Won't reach any more local (Scene level) handlers + event.stopImmediatePropagation = function () { + event.cancelled = 1; + }; + + // Won't reach any more handlers in any Scene further down the Scene list + event.stopPropagation = function () { + event.cancelled = -1; + }; + } + + if (event.cancelled === -1) { + // This event has been stopped from broadcasting to any other Scene, so abort. + event.cancelled = 0; + return; + } + + var isDown = false; + for (var i = 0, cnt = this.ports.length; i < cnt; i++) { + if (this.ports[i].isDown) { + isDown = true; + break; + } + } + + if (this.isDown !== isDown) { + event = FakeEvent; + event.timeStamp = Date.now(); + event.keyCode = this.keyCode; + + if (isDown) { + this.onDown(event); + } else { + this.onUp(event); + } + + if (!event.cancelled) { + var eventName = ((isDown) ? 'keydown-' : 'keyup-') + KeyMap[this.keyCode]; + this.plugin.emit(eventName, event); + } + + if (!event.cancelled) { + var eventName = (isDown) ? 'keydown' : 'keyup'; + this.plugin.emit(eventName, event); + } + } + + event.cancelled = 0; + } +} + +var FakeEvent = { + timeStamp: 0, + keyCode: 0, + altKey: false, + ctrlKey: false, + shiftKey: false, + metaKey: false, + location: 0, +}; + +export default KeyHub; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/keyshub/KeysHub.js b/ui/src/phaser3-rex-plugins/plugins/input/keyshub/KeysHub.js new file mode 100644 index 000000000..d162bb058 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/keyshub/KeysHub.js @@ -0,0 +1,122 @@ +import EventEmitterMethods from '../../utils/eventemitter/EventEmitterMethods.js'; +import KeyHub from './KeyHub.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const KeyCodes = Phaser.Input.Keyboard.KeyCodes; + +class KeysHub { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + config.eventEmitter = this.getEventEmitter(); + + this.scene = scene; + this.keys = {}; + } + + destroy() { + this.destroyEventEmitter(); + + for (var keyCode in this.keys) { + this.keys[keyCode].destroy(); + } + this.keys = undefined; + } + + plugKey(key, keyCode) { + if (keyCode === undefined) { + keyCode = key.keyCode; + } + + this.addKey(keyCode).plug(key); + return this; + } + + plugKeys(keys) { + if (Array.isArray(keys)) { + for (var i = 0, cnt = keys.length; i < cnt; i++) { + this.plugKey(keys[i]); + } + } else { + for (var keyCode in keys) { + this.plugKey(keys[keyCode], keyCode); + } + } + return this; + } + + unplug(key) { + for (var keyCode in this.keys) { + this.keys[keyCode].unplug(key); + } + return this; + } + + unplug(keys) { + if (Array.isArray(keys)) { + for (var i = 0, cnt = keys.length; i < cnt; i++) { + this.unplugKey(keys[i]); + } + } else { + for (var keyCode in keys) { + this.unplugKey(keys[keyCode]); + } + } + return this; + } + + addKey(keyCode) { + if (typeof (keyCode) === 'string') { + keyCode = KeyCodes[keyCode.toUpperCase()]; + } + if (!this.keys.hasOwnProperty(keyCode)) { + this.keys[keyCode] = new KeyHub(this, keyCode); + } + return this.keys[keyCode]; + } + + addKeys(keys) { + var output = {}; + if (typeof (keys) === 'string') { + keys = keys.split(','); + + for (var i = 0, cnt = keys.length; i < cnt; i++) { + var currentKey = keys[i].trim(); + + if (currentKey) { + output[currentKey] = this.addKey(currentKey); + } + } + } else { + for (var key in keys) { + output[key] = this.addKey(keys[key]); + } + } + + return output; + } + + createCursorKeys() { + return this.addKeys({ + up: KeyCodes.UP, + down: KeyCodes.DOWN, + left: KeyCodes.LEFT, + right: KeyCodes.RIGHT, + space: KeyCodes.SPACE, + shift: KeyCodes.SHIFT + }); + } +} + +Object.assign( + KeysHub.prototype, + EventEmitterMethods +); + +export default KeysHub; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/mousewheelscroller/MouseWheelScroller.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/mousewheelscroller/MouseWheelScroller.d.ts new file mode 100644 index 000000000..2c5274168 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/mousewheelscroller/MouseWheelScroller.d.ts @@ -0,0 +1,32 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default MouseWheelScroller; + +declare namespace MouseWheelScroller { + interface IConfig { + focus?: boolean, + speed?: number, + enable?: boolean + } + + namespace Events { + type ScrollCallbackType = ( + inc: number, + gameObject: Phaser.GameObjects.GameObject, + scroller: MouseWheelScroller + ) => void; + } +} + +declare class MouseWheelScroller extends ComponentBase { + constructor( + scene: Phaser.Scene, + config?: MouseWheelScroller.IConfig + ) + + setSpeed(speed: number): this; + speed: number; + + setEnable(enable?: boolean): this; + enable: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/mousewheelscroller/MouseWheelScroller.js b/ui/src/phaser3-rex-plugins/plugins/input/mousewheelscroller/MouseWheelScroller.js new file mode 100644 index 000000000..5b14895cb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/mousewheelscroller/MouseWheelScroller.js @@ -0,0 +1,70 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class MouseWheelScroller extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + if (this.parent !== this.scene) { + this.focusMode = GetValue(config, 'focus', false); + } else { + this.focusMode = false; + } + + this.setSpeed(GetValue(config, 'speed', 0.1)); + this.setEnable(GetValue(config, 'enable', true)); + + if (!this.focusMode) { // Register on scene + this.scene.input.on('wheel', this.onSceneScroll, this); + } else { + var gameObject = this.parent; + gameObject + .setInteractive(GetValue(config, "inputConfig", undefined)) + .on('wheel', function (pointer, dx, dy, dz, event) { + if (!this.enable) { + return; + } + this.scroll(dy); + }, this); + + } + } + + destroy() { + if (!this.focusMode) { + this.scene.input.off('wheel', this.onSceneScroll, this); + } else { + // GameObject events will be removed when this gameObject destroyed + } + } + + onSceneScroll(pointer, currentlyOver, dx, dy, dz, event) { + if (!this.enable) { + return; + } + this.scroll(dy); + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + setSpeed(speed) { + this.speed = speed; + return this; + } + + scroll(dy) { + dy *= this.speed; + this.emit('scroll', dy, this.parent, this); + } +} + +export default MouseWheelScroller; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/mousewheeltoupdown/MouseWheelToUpDown.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/mousewheeltoupdown/MouseWheelToUpDown.d.ts new file mode 100644 index 000000000..16a9f06c1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/mousewheeltoupdown/MouseWheelToUpDown.d.ts @@ -0,0 +1,26 @@ +export default MouseWheelToUpDown; + +declare namespace MouseWheelToUpDown { + interface IConfig { + bounds?: Phaser.Geom.Rectangle, + sensitiveDistance?: number, + } +} + +declare class MouseWheelToUpDown { + constructor( + scene: Phaser.Scene, + config?: MouseWheelToUpDown.IConfig + ) + + createCursorKeys(): { + up: Phaser.Input.Keyboard.Key, + down: Phaser.Input.Keyboard.Key, + left: Phaser.Input.Keyboard.Key, + right: Phaser.Input.Keyboard.Key, + }; + + readonly up: boolean; + readonly down: boolean; + readonly nokey: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/mousewheeltoupdown/MouseWheelToUpDown.js b/ui/src/phaser3-rex-plugins/plugins/input/mousewheeltoupdown/MouseWheelToUpDown.js new file mode 100644 index 000000000..04fc4d4c9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/mousewheeltoupdown/MouseWheelToUpDown.js @@ -0,0 +1,52 @@ +import CursorKeys from '../../utils/input/CursorKeys.js'; + +class MouseWheelToUpDown extends CursorKeys { + constructor(scene, config) { + super(scene); + + this.scene = scene; + this.boot(); + } + + boot() { + this.scene.input.on('wheel', this.onWheeling, this); + this.scene.sys.events.on('postupdate', this.clearAllKeysState, this); + this.scene.sys.events.once('shutdown', this.destroy, this); + } + + shutdown() { + if (!this.scene) { + return + } + + this.scene.input.off('wheel', this.onWheeling, this); + this.scene.sys.events.off('postupdate', this.clearAllKeysState, this); + this.scene.sys.events.off('shutdown', this.destroy, this); + this.scene = undefined; + + super.shutdown(); + } + + destroy() { + this.shutdown(); + } + + onWheeling(pointer, currentlyOver, dx, dy, dz, event) { + this.setKeyState('up', dy < 0); + this.setKeyState('down', dy > 0); + } + + get up() { + return this.upKeyDown; + } + + get down() { + return this.downKeyDown; + } + + get noKey() { + return this.noKeyDown; + } +} + +export default MouseWheelToUpDown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/scroller/Scroller.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/scroller/Scroller.d.ts new file mode 100644 index 000000000..02641dc9e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/scroller/Scroller.d.ts @@ -0,0 +1,64 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default Scroller; + +declare namespace Scroller { + + type OrientationType = 0 | 1 | 'x' | 'y' | 'v' | 'vertical' | 'h' | 'horizontal'; + type ValueChangeCallbackType = (newValue: number, oldValue: number) => void; + + interface IConfig { + bounds?: [ + bottomBound: number, + topBound: number + ], + value?: number, + threshold?: number, + slidingDeceleration?: number, + backDeceleration?: number, + + dragReverse?: boolean, + dragRate?: number, + + enable?: boolean, + orientation?: OrientationType, + + valuechangeCallback?: ValueChangeCallbackType, + valuechangeCallbackScope?: Object, + + overmaxCallback?: ValueChangeCallbackType, + overmaxCallbackScope?: Object, + + overminCallback?: ValueChangeCallbackType, + overminCallbackScope?: Object, + } + + namespace Events { + type ValueChageCallbackType = (newValue: number, oldValue: number) => void; + } +} + +declare class Scroller extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Scroller.IConfig + ) + + setEnable(enable?: boolean): this; + toggleEnable(): this; + enable: boolean; + + setBounds(bound0: number, bound1: number): this; + setBounds(bounds: [bottomBound: number, topBound: number]): this; + + setSlidingDeceleration(dec: number | false): this; + setBackDeceleration(dec: number | false): this; + + setValue(value: number, clamp?: boolean): this; + addValue(inc: number, clamp?: boolean): this; + value: number; + + readonly isDragging: boolean; + + readonly state: string; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/scroller/Scroller.js b/ui/src/phaser3-rex-plugins/plugins/input/scroller/Scroller.js new file mode 100644 index 000000000..e2c1de238 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/scroller/Scroller.js @@ -0,0 +1,365 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import State from './State.js'; +import DrapSpeed from '../../dragspeed.js'; +import SlowDown from '../../utils/movement/SlowDown.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Clamp = Phaser.Math.Clamp; + +class Scroller extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + var enable = GetValue(config, 'enable', true); + this._state = new State(this, { + enable: enable, + eventEmitter: false, + }); + + var drapSpeedConfig = { + inputConfig: GetValue(config, 'inputConfig', undefined), + enable: enable, + pointerOutRelease: GetValue(config, 'pointerOutRelease', true), + eventEmitter: false, + }; + this.dragState = new DrapSpeed(gameObject, drapSpeedConfig); + + this._enable = undefined; + this._value = undefined; + this._slowDown = new SlowDown(); + + var callback = GetValue(config, 'valuechangeCallback', null); + if (callback !== null) { + var scope = GetValue(config, 'valuechangeCallbackScope', undefined); + this.on('valuechange', callback, scope); + } + callback = GetValue(config, 'overmaxCallback', null); + if (callback !== null) { + var scope = GetValue(config, 'overmaxCallbackScope', undefined); + this.on('overmax', callback, scope); + } + callback = GetValue(config, 'overminCallback', null); + if (callback !== null) { + var scope = GetValue(config, 'overminCallbackScope', undefined); + this.on('overmin', callback, scope); + } + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.setOrientationMode(GetValue(o, 'orientation', 0)); + this.setDragThreshold(GetValue(o, 'threshold', 10)); + this.setSlidingDeceleration(GetValue(o, 'slidingDeceleration', 5000)); + this.setBackDeceleration(GetValue(o, 'backDeceleration', 2000)); + + var dragRate = GetValue(o, 'dragRate', 1); + dragRate = dragRate * (GetValue(o, 'dragReverse', false) ? -1 : 1); + this.setDragRate(dragRate); + + var bounds = GetValue(o, 'bounds', undefined); + if (bounds) { + this.setBounds(bounds); + } else { + this.setBounds(GetValue(o, 'max', 0), GetValue(o, 'min', 0)); + } + this.setValue(GetValue(o, 'value', this.maxValue || 0)); + this.setEnable(GetValue(o, "enable", true)); + return this; + } + + boot() { + this.scene.sys.events.on('preupdate', this._state.update, this._state); + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.scene.sys.events.off('preupdate', this._state.update, this._state); + this._state.destroy(fromScene); + this.dragState.destroy(fromScene); + this._state = undefined; + this.dragState = undefined; + + super.shutdown(fromScene); + } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + + this._enable = e; + this._state.setEnable(e); + this.dragState.setEnable(e); + + return this; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + setOrientationMode(m) { + if (typeof (m) === 'string') { + m = ORIENTATIONMODE[m]; + } + this.orientationMode = m; + return this; + } + + setDragThreshold(distance) { + this.dragThreshold = distance; + return this; + } + + setSlidingDeceleration(dec) { + this.slidingDeceleration = dec; + return this; + } + + setBackDeceleration(dec) { + this.backDeceleration = dec; + return this; + } + + setDragRate(ratio) { + this.dragRate = ratio; + return this; + } + + setBounds(value0, value1) { + if (Array.isArray(value0)) { + var bounds = value0; + value0 = bounds[0]; + value1 = bounds[1]; + } + if (value0 < value1) { + this.minValue = value0; + this.maxValue = value1; + } else { + this.minValue = value1; + this.maxValue = value0; + } + return this; + } + + get value() { + return this._value; + } + + set value(value) { + if (value === this._value) { + return; + } + + var oldValue = this._value; + var isOverMax = this.overMax(value); + var isOverMin = this.overMin(value); + if (isOverMax) { + this.emit('overmax', value, oldValue); + } + if (isOverMin) { + this.emit('overmin', value, oldValue); + } + if (!this.backEnable) { + if (isOverMax) { + value = this.maxValue; + } + if (isOverMin) { + value = this.minValue; + } + } + + this._value = value; + this.emit('valuechange', value, oldValue); + } + + setValue(value, clamp) { + if (clamp === undefined) { + clamp = false; + } + + if (clamp) { + value = Clamp(value, this.minValue, this.maxValue); + } + + this.value = value; + return this; + } + + addValue(inc, clamp) { + this.setValue(this.value + inc, clamp); + return this; + } + + get state() { + return this._state.state; + } + + get isDragging() { + return this.dragState.isInTouched; + } + + get outOfMaxBound() { + return this.overMax(this.value); + } + + get outOfMinBound() { + return this.overMin(this.value); + } + + get outOfBounds() { + return this.outOfMinBound || this.outOfMaxBound; + } + + // internal + overMax(value) { + return (this.maxValue != null) && (value > this.maxValue); + } + + overMin(value) { + return (this.minValue != null) && (value < this.minValue); + } + + get backEnable() { + return (typeof (this.backDeceleration) === 'number'); + } + + get isPullBack() { + return this._slowDown.isMoving; + } + + get slidingEnable() { + return (typeof (this.slidingDeceleration) === 'number'); + } + + get isSliding() { + return this._slowDown.isMoving; + } + + get dragDelta() { + var delta; + if (this.orientationMode === 0) { // y + delta = this.dragState.dy; + } else if (this.orientationMode === 1) { // x + delta = this.dragState.dx; + } else { + delta = 0; + } + delta *= this.dragRate; + return delta; + } + + get dragSpeed() { + var speed; + if (this.orientationMode === 0) { // y + speed = this.dragState.speedY; + } else if (this.orientationMode === 1) { // x + speed = this.dragState.speedX; + } else { + speed = 0; + } + speed *= this.dragRate; + return speed; + } + + // enter_DRAG + onDragStart() { + this.emit('dragstart'); + } + + // exit_DRAG + onDragEnd() { + this.emit('dragend'); + } + + // everyTick_DRAG + dragging() { + this.value += this.dragDelta; + } + + // enter_SLIDE + onSliding() { + var start = this.value; + var speed = this.dragSpeed; + if (speed === 0) { + this._slowDown.stop(); + this._state.next(); + return; + } + var dec = this.slidingDeceleration; + this._slowDown.init(start, (speed > 0), Math.abs(speed), dec) + } + + // everyTick_SLIDE + sliding(time, delta) { + delta *= 0.001; + var newValue = this._slowDown.update(delta).value; + if (this.overMax(newValue)) { + this.value = this.maxValue; + this._slowDown.stop(); + } else if (this.overMin(newValue)) { + this.value = this.minValue; + this._slowDown.stop(); + } else { + this.value = newValue; + } + } + + // enter_BACK + onPullBack() { + var start = this.value; + var end = (this.outOfMinBound) ? this.minValue : this.maxValue; + var dist = Math.abs(end - start); + var dec = this.backDeceleration; + var speed = Math.sqrt(2 * dec * dist); + this._slowDown.init(start, undefined, speed, dec, end); + } + + // everyTick_BACK + pullBack(time, delta) { + delta *= 0.001; + this.value = this._slowDown.update(delta).value; + + if (!this._slowDown.isMoving) { + this._state.next(); + } + } + + // exit_SLIDE, exit_BACK + stop() { + this._slowDown.stop(); + } + +} + +const ORIENTATIONMODE = { + y: 0, + v: 0, + vertical: 0, + x: 1, + h: 1, + horizontal: 1, +}; + +export default Scroller; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/scroller/State.js b/ui/src/phaser3-rex-plugins/plugins/input/scroller/State.js new file mode 100644 index 000000000..bb052271a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/scroller/State.js @@ -0,0 +1,127 @@ +import FSM from '../../fsm.js'; + +class State extends FSM { + constructor(parent, config) { + super(config); + this.parent = parent; + this.init(); + } + + init() { + this.start('IDLE'); + } + + // IDLE -> DRAGBEGIN|DRAG + next_IDLE() { + var nextState, + parent = this.parent, + dragState = parent.dragState; + if (dragState.isDown) { + nextState = (parent.dragThreshold === 0) ? 'DRAG' : 'DRAGBEGIN'; + } + return nextState; + } + update_IDLE(time, delta) { + this.next(); + } + // IDLE + + // DRAGBEGIN -> DRAG|IDLE + next_DRAGBEGIN() { + var nextState, + parent = this.parent, + dragState = parent.dragState; + if (dragState.isDown) { + nextState = (dragState.pointer.getDistance() >= parent.dragThreshold) ? 'DRAG' : 'DRAGBEGIN'; + } else { // dragState.isUp + nextState = 'IDLE'; + } + return nextState; + } + update_DRAGBEGIN(time, delta) { + this.next(); + } + // DRAGBEGIN + + // DRAG -> BACK|SLIDE|IDLE + next_DRAG() { + var nextState, + parent = this.parent, + dragState = parent.dragState; + if (dragState.isUp) { + if (parent.outOfBounds) { + nextState = 'BACK'; + } else if (parent.slidingEnable) { + nextState = 'SLIDE'; + } else { + nextState = 'IDLE'; + } + } + return nextState; + } + update_DRAG(time, delta) { + var parent = this.parent, + dragState = parent.dragState; + if (dragState.justMoved) { + parent.dragging(); + } + this.next(); + } + enter_DRAG() { + this.parent.onDragStart(); + } + exit_DRAG() { + this.parent.onDragEnd(); + } + // DRAG + + // SLIDE -> DRAG|IDLE + next_SLIDE() { + var nextState, + parent = this.parent, + dragState = parent.dragState; + if (dragState.isDown) { + nextState = 'DRAG'; + } else if (!parent.isSliding) { + nextState = 'IDLE'; + } + return nextState; + } + enter_SLIDE() { + this.parent.onSliding(); + } + exit_SLIDE() { + this.parent.stop(); + } + update_SLIDE(time, delta) { + this.parent.sliding(time, delta); + this.next(); + } + // SLIDE + + // BACK -> DRAG|IDLE + next_BACK() { + var nextState, + parent = this.parent, + dragState = parent.dragState; + if (dragState.isDown) { + nextState = 'DRAG'; + } else if (!parent.isPullBack) { + nextState = 'IDLE'; + } + return nextState; + } + enter_BACK() { + this.parent.onPullBack(); + } + exit_BACK() { + this.parent.stop(); + } + update_BACK(time, delta) { + this.parent.pullBack(time, delta); + this.next(); + } + // BACK +} + +export default State; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/slider/Slider.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/slider/Slider.d.ts new file mode 100644 index 000000000..29a73f649 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/slider/Slider.d.ts @@ -0,0 +1,66 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default Slider; + +declare namespace Slider { + type ValueChangeCallbackType = (newValue: number, oldValue: number) => void; + + interface IConfig { + endPoints?: [ + { x: number, y: number }, + { x: number, y: number } + ], + value?: number, + enable?: boolean, + + valuechangeCallback?: ValueChangeCallbackType, + valuechangeCallbackScope?: Object + } + + namespace Events { + type ValueChangeCallbackType = (newValue: number, oldValue: number) => void; + } +} + +declare class Slider extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: Slider.IConfig + ); + + setEnable(enable?: boolean): this; + toggleEnable(): this; + enable: boolean; + + setEndPoints( + p0x: number, p0y: number, + p1x: number, p1y: number + ): this; + + setEndPoints( + p0: { x: number, y: number }, + p1: { x: number, y: number } + ): this; + + setEndPoints( + points: [ + { x: number, y: number }, + { x: number, y: number } + ] + ): this; + + getValue(min?: number, max?: number): number; + value: number; + + setValue( + newValue: number, + min?: number, max?: number + ): this; + + addValue( + inc: number, + min?: number, max?: number + ): this; + + readonly isDragging: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/slider/Slider.js b/ui/src/phaser3-rex-plugins/plugins/input/slider/Slider.js new file mode 100644 index 000000000..f132d745f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/slider/Slider.js @@ -0,0 +1,178 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import ProgressValueMethods from '../../utils/progressvalue/ProgressValueMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const BetweenPoints = Phaser.Math.Angle.BetweenPoints; +const DistanceBetween = Phaser.Math.Distance.Between; +const RotateAroundDistance = Phaser.Math.RotateAroundDistance; +const Clamp = Phaser.Math.Clamp; +const Linear = Phaser.Math.Linear; +const Percent = Phaser.Math.Percent; + +class Slider extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this._enable = undefined; + this._value = undefined; + this.endPoints = [ + { x: 0, y: 0 }, + { x: 0, y: 0 } + ]; + + var callback = GetValue(config, 'valuechangeCallback', null); + if (callback !== null) { + var scope = GetValue(config, 'valuechangeCallbackScope', undefined); + this.on('valuechange', callback, scope); + } + + this.parent.setInteractive(GetValue(config, "inputConfig", undefined)); + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.setValue(GetValue(o, "value", 0)); + var endPoints = GetValue(o, "endPoints", undefined); + if (endPoints !== undefined) { + this.setEndPoints(endPoints); + } + this.setEnable(GetValue(o, "enable", true)); + return this; + } + + toJSON() { + return { + value: this.value, + endPoints: this.endPoints, + enable: this.enable + }; + } + + boot() { + this.parent.on('drag', this.onDragging, this); + } + + // shutdown(fromScene) { + // // Already shutdown + // if (this.isShutdown) { + // return; + // } + // // GameObject events will be removed when this gameObject destroyed + // // this.parent.off('drag', this.onDragging, this); + // super.shutdown(fromScene); + // } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + + this._enable = e; + this.scene.input.setDraggable(this.parent, e); + return this; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + setEndPoints(p0x, p0y, p1x, p1y) { + var points = this.endPoints; + if (typeof (p0x) === 'number') { + points[0].x = p0x; + points[0].y = p0y; + points[1].x = p1x; + points[1].y = p1y; + } else if (Array.isArray(p0x)) { // single array with 2 points + points[0] = p0x[0]; + points[1] = p0x[1]; + } else { + points[0] = p0x; + points[1] = p0y; + } + this.axisRotation = BetweenPoints(points[0], points[1]); + this.updatePos(); + return this; + } + + get value() { + return this._value; + } + + set value(value) { + value = Clamp(value, 0, 1); + if (value === this._value) { + return; + } + + var oldValue = this._value; + this._value = value + this.updatePos(this._value); + this.emit('valuechange', this._value, oldValue); + } + + get isDragging() { + return (this.parent.input.dragState > 0); + } + + onDragging(pointer, dragX, dragY) { + var endPoints = this.endPoints; + var newValue; + if (endPoints[0].y === endPoints[1].y) { + var min = Math.min(endPoints[0].x, endPoints[1].x); + var max = Math.max(endPoints[0].x, endPoints[1].x); + newValue = Percent(dragX, min, max); + } else if (endPoints[0].x === endPoints[1].x) { + var min = Math.min(endPoints[0].y, endPoints[1].y); + var max = Math.max(endPoints[0].y, endPoints[1].y); + newValue = Percent(dragY, min, max); + } else { + var gameObject = this.parent; + var dist; + var p1 = { x: dragX, y: dragY }; + + dist = DistanceBetween(p1.x, p1.y, gameObject.x, gameObject.y); + p1 = RotateAroundDistance(p1, gameObject.x, gameObject.y, -this.axisRotation, dist); + p1.y = gameObject.y; + dist = DistanceBetween(p1.x, p1.y, gameObject.x, gameObject.y); + p1 = RotateAroundDistance(p1, gameObject.x, gameObject.y, this.axisRotation, dist); + + var min = Math.min(endPoints[0].x, endPoints[1].x); + var max = Math.max(endPoints[0].x, endPoints[1].x); + newValue = Percent(p1.x, min, max); + } + + this.value = newValue; + } + + updatePos() { + var gameObject = this.parent; + var points = this.endPoints; + gameObject.x = Linear(points[0].x, points[1].x, this._value); + gameObject.y = Linear(points[0].y, points[1].y, this._value); + return this; + } +} + +Object.assign( + Slider.prototype, + ProgressValueMethods, +) + +export default Slider; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/touchcursor/TouchCursor.js b/ui/src/phaser3-rex-plugins/plugins/input/touchcursor/TouchCursor.js new file mode 100644 index 000000000..3e43dc188 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/touchcursor/TouchCursor.js @@ -0,0 +1,160 @@ +import VectorToCursorKeys from '../../utils/input/VectorToCursorKeys.js'; +import EventEmitterMethods from '../../utils/eventemitter/EventEmitterMethods.js'; +import ScreenXYToWorldXY from '../../utils/position/ScreenXYToWorldXY.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const CircleClass = Phaser.Geom.Circle; +const CircleContains = Phaser.Geom.Circle.Contains; + +class TouchCursor extends VectorToCursorKeys { + constructor(gameObject, config) { + var scene = gameObject.scene; + super(scene, config); + //this.resetFromJSON(config); // this function had been called in super(config) + + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + + this.scene = scene; + this.mainCamera = scene.sys.cameras.main; + this.pointer = undefined; + this.gameObject = gameObject; + this.radius = GetValue(config, 'radius', 100); + + gameObject.setInteractive(new CircleClass(gameObject.displayOriginX, gameObject.displayOriginY, this.radius), CircleContains); + + this.boot(); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + this.pointer = undefined; + + return this; + } + + toJSON() { + var o = super.toJSON(); + o.radius = this.radius; + + return o; + } + + boot() { + this.gameObject.on('pointerdown', this.onKeyDownStart, this); + this.gameObject.on('pointerover', this.onKeyDownStart, this); + + this.scene.input.on('pointermove', this.onKeyDown, this); + this.scene.input.on('pointerup', this.onKeyUp, this); + + this.gameObject.once('destroy', this.onParentDestroy, this); + } + + shutdown(fromScene) { + if (!this.scene) { + return; + } + + // gameObject events will be removed when this gameObject destroyed + // this.gameObject.off('pointerdown', this.onKeyDownStart, this); + // this.gameObject.off('pointerover', this.onKeyDownStart, this); + + this.scene.input.off('pointermove', this.onKeyDown, this); + this.scene.input.off('pointerup', this.onKeyUp, this); + + this.destroyEventEmitter(); + + this.scene = undefined; + this.mainCamera = undefined; + this.pointer = undefined; + this.gameObject = undefined; + + super.shutdown(); + } + + destroy(fromScene) { + this.shutdown(fromScene); + } + + onParentDestroy(parent, fromScene) { + this.destroy(fromScene); + } + + onKeyDownStart(pointer) { + if ((!pointer.isDown) || + (this.pointer !== undefined)) { + return; + } + this.pointer = pointer; + this.onKeyDown(pointer); + this.emit('pointerdown', pointer); + } + + onKeyDown(pointer) { + if (this.pointer !== pointer) { + return; + } + + var camera = pointer.camera; + if (!camera) { + // Pointer is outside of any camera, no worldX/worldY available + return; + } + + // Vector of world position + var gameObject = this.gameObject; + var worldXY = this.end; + + // Note: pointer.worldX, pointer.worldY might not be the world position of this camera, + // if this camera is not main-camera + if (camera !== this.mainCamera) { + worldXY = ScreenXYToWorldXY(pointer.x, pointer.y, camera, worldXY); + } else { + worldXY.x = pointer.worldX; + worldXY.y = pointer.worldY; + } + + var startX = gameObject.x; + var startY = gameObject.y; + if (gameObject.scrollFactorX === 0) { + startX += camera.scrollX; + } + if (gameObject.scrollFactorY === 0) { + startY += camera.scrollY; + } + + this.setVector(startX, startY, worldXY.x, worldXY.y); + + this.emit('update'); + } + + onKeyUp(pointer) { + if (this.pointer !== pointer) { + return; + } + this.pointer = undefined; + this.clearVector(); + this.emit('update'); + this.emit('pointerup', pointer); + } + + forceUpdate() { + var pointer = this.pointer; + if (!pointer || !pointer.isDown) { + return this; + } + + this.onKeyDown(pointer); + return this; + } + +} + +Object.assign( + TouchCursor.prototype, + EventEmitterMethods +); + +export default TouchCursor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/toucheventstop/TouchEventStop.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/toucheventstop/TouchEventStop.d.ts new file mode 100644 index 000000000..c342a0a96 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/toucheventstop/TouchEventStop.d.ts @@ -0,0 +1,30 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default TouchEventStop; + +declare namespace TouchEventStop { + type HitAreaMode = 0 | 1 | 'default' | 'fullWindow'; + + interface IConfig { + hitAreaMode?: HitAreaMode, + enable?: boolean, + stopAllLevels?: boolean, + } +} + +declare class TouchEventStop extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: TouchEventStop.IConfig + ) + + setHitAreaMode( + mode?: TouchEventStop.HitAreaMode + ): this; + + setEnable(enable?: boolean): this; + toggleEnable(): this; + enable: boolean; + + setStopMode(stopAllLevels?: boolean): this; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/input/toucheventstop/TouchEventStop.js b/ui/src/phaser3-rex-plugins/plugins/input/toucheventstop/TouchEventStop.js new file mode 100644 index 000000000..edf539311 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/toucheventstop/TouchEventStop.js @@ -0,0 +1,107 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class TouchEventStop extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, { eventEmitter: false }); + // No event emitter + // this.parent = gameObject; + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.setHitAreaMode(GetValue(o, 'hitAreaMode', 0)); + this.setEnable(GetValue(o, 'enable', true)); + this.setStopMode(GetValue(o, 'stopAllLevels', true)); + return this; + } + + boot() { + this.parent + .on('pointerdown', function (pointer, localX, localY, event) { + if (this.stopAllLevels) { + event.stopPropagation(); + } + }, this) + .on('pointerup', function (pointer, localX, localY, event) { + if (this.stopAllLevels) { + event.stopPropagation(); + } + }, this) + .on('pointermove', function (pointer, localX, localY, event) { + if (this.stopAllLevels) { + event.stopPropagation(); + } + }, this) + .on('pointerover', function (pointer, localX, localY, event) { + if (this.stopAllLevels) { + event.stopPropagation(); + } + }, this) + .on('pointerout', function (pointer, event) { + if (this.stopAllLevels) { + event.stopPropagation(); + } + }, this) + } + + setHitAreaMode(mode) { + if (typeof (mode) === 'string') { + mode = HitAreaMode[mode]; + } + + var gameObject = this.parent; + if (gameObject.input) { + gameObject.removeInteractive(); + } + + if (mode === 0) { + gameObject.setInteractive(); + } else { + gameObject.setInteractive({ + hitArea: {}, + hitAreaCallback: function () { return true; } + }); + } + + return this; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + if (e) { + this.parent.setInteractive(); + } else { + this.parent.disableInteractive(); + } + + this.enable = e; + return this; + } + + setStopMode(allLevels) { + if (allLevels === undefined) { + allLevels = true; + } + this.stopAllLevels = allLevels; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } +} + +var HitAreaMode = { + default: 0, + fullWindow: 1 +} + +export default TouchEventStop; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/touchgroup/TouchGroup.js b/ui/src/phaser3-rex-plugins/plugins/input/touchgroup/TouchGroup.js new file mode 100644 index 000000000..7e09a9d0b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/touchgroup/TouchGroup.js @@ -0,0 +1,50 @@ +import IsSceneObject from '../../utils/system/IsSceneObject'; +import Clear from '../../utils/object/Clear.js'; + +class TouchGroup { + constructor(game) { + if (IsSceneObject(game)) { + game = game.game + } + this.ticker = game.loop; + this.topObjects = {}; + } + + destroy() { + this.ticker = undefined; + this.topObjects = undefined; + } + + isAtTop(groupName, key) { + var result; + var tick = this.ticker.frame; + var item = this.topObjects[groupName]; + if (item) { + if (item.tick < tick) { + result = true; + } else if (item.tick === tick) { + result = (key !== undefined) && (item.key === key); + } else { + result = false; + } + if (result) { + item.tick = tick; + item.key = key; + } + } else { + this.topObjects[groupName] = { + tick: tick, + key: key + } + result = true; + } + return result; + } + + clear() { + Clear(this.topObjects); + return this; + } +} + +export default TouchGroup; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/touchstate/TouchState.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/touchstate/TouchState.d.ts new file mode 100644 index 000000000..7b97db6e9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/touchstate/TouchState.d.ts @@ -0,0 +1,21 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase'; + +export default TouchState; + +declare namespace TouchState { + + interface IConfig { + enable?: boolean, + } +} + +declare class TouchState extends ComponentBase { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: TouchState.IConfig + ) + + setEnable(enable?: boolean): this; + toggleEnable(): this; + enable: boolean; +} diff --git a/ui/src/phaser3-rex-plugins/plugins/input/touchstate/TouchState.js b/ui/src/phaser3-rex-plugins/plugins/input/touchstate/TouchState.js new file mode 100644 index 000000000..1e733f952 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/touchstate/TouchState.js @@ -0,0 +1,177 @@ +import ComponentBase from '../../utils/componentbase/ComponentBase.js'; +import GetTickDelta from '../../utils/system/GetTickDelta.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const DistanceBetween = Phaser.Math.Distance.Between; + +class TouchState extends ComponentBase { + constructor(gameObject, config) { + super(gameObject, config); + // this.parent = gameObject; + + this._enable = undefined; + this.parent.setInteractive(GetValue(config, "inputConfig", undefined)); + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.pointer = undefined; + this.isInTouching = false; + this.x = undefined; + this.y = undefined; + this.preX = undefined; + this.preY = undefined; + this.justMoved = false; + this.setEnable(GetValue(o, "enable", true)); + return this; + } + + boot() { + var gameObject = this.parent; + gameObject.on('pointerdown', this.onPointIn, this); + gameObject.on('pointerover', this.onPointIn, this); + gameObject.on('pointerup', this.onPointOut, this); + gameObject.on('pointerout', this.onPointOut, this); + gameObject.on('pointermove', this.onPointerMove, this); + this.scene.sys.events.on('postupdate', this.postupdate, this); + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + // GameObject events will be removed when this gameObject destroyed + // this.parent.off('pointerdown', this.onPointIn, this); + // this.parent.off('pointerover', this.onPointIn, this); + // this.parent.off('pointerup', this.onPointOut, this); + // this.parent.off('pointerout', this.onPointOut, this); + // this.parent.off('pointermove', this.onPointerMove, this); + this.scene.sys.events.off('postupdate', this.postupdate, this); + + this.pointer = undefined; + super.shutdown(fromScene); + } + + get enable() { + return this._enable; + } + + set enable(e) { + if (this._enable === e) { + return; + } + + if (!e) { + this.isInTouching = false; + this.pointer = undefined; + } + this._enable = e; + return this; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + get isDown() { + return this.pointer && this.pointer.isDown; + } + + get isUp() { + return this.pointer === undefined; + } + + get dx() { + return this.x - this.preX; + } + + get dy() { + return this.y - this.preY; + } + + get dt() { + var delta = GetTickDelta(this.scene); + return delta; + } + + get speed() { + if ((this.x === this.preX) && (this.y === this.preY)) { + return 0; + } + var d = DistanceBetween(this.x, this.preX, this.y, this.preY); + var speed = d / (this.dt * 0.001); + return speed; + } + + get speedX() { + return this.dx / (this.dt * 0.001); + } + + get speedY() { + return this.dy / (this.dt * 0.001); + } + + // internal + onPointIn(pointer, localX, localY, event) { + if ((!this.enable) || + (!pointer.isDown) || + (this.pointer !== undefined)) { + return; + } + this.pointer = pointer; + this.isInTouching = true; + this.preX = pointer.x; + this.preY = pointer.y; + this.x = pointer.x; + this.y = pointer.y; + this.localX = localX; + this.localY = localY; + this.emit('touchstart', this, this.parent, pointer, localX, localY, event); + } + + onPointOut(pointer) { + if ((!this.enable) || + (this.pointer !== pointer)) { + return; + } + this.pointer = undefined; + this.isInTouching = false; + this.emit('touchend', this, this.parent, pointer); + } + + onPointerMove(pointer, localX, localY, event) { + if ((!this.enable) || + (!pointer.isDown) || + (this.pointer !== pointer)) { + return; + } + this.preX = this.x; + this.preY = this.y; + this.x = pointer.x; + this.y = pointer.y; + this.localX = localX; + this.localY = localY; + this.justMoved = true; + this.emit('touchmove', this, this.parent, pointer, localX, localY, event); + } + + postupdate(time, delta) { + this.justMoved = false; + } + +} + +export default TouchState; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/virtualjoystick/VirtualJoyStick.d.ts b/ui/src/phaser3-rex-plugins/plugins/input/virtualjoystick/VirtualJoyStick.d.ts new file mode 100644 index 000000000..b3a97ce2c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/virtualjoystick/VirtualJoyStick.d.ts @@ -0,0 +1,72 @@ +// import * as Phaser from 'phaser'; + +export default VirtualJoyStick; + +declare namespace VirtualJoyStick { + + type DirTypes = 0 | 1 | 2 | 3 | 'up&down' | 'left&right' | '4dir' | '8dir'; + + export interface IConfig { + x?: number, y?: number, + radius?: number, + + base?: Phaser.GameObjects.GameObject, + thumb?: Phaser.GameObjects.GameObject, + dir?: DirTypes, + + forceMin?: number, + fixed?: boolean, + enable?: boolean, + } + + namespace Events { + type UpdateCallbackType = () => void; + } +} + +declare class VirtualJoyStick { + constructor( + scene: Phaser.Scene, + config?: VirtualJoyStick.IConfig + ) + + createCursorKeys(): { + up: Phaser.Input.Keyboard.Key, + down: Phaser.Input.Keyboard.Key, + left: Phaser.Input.Keyboard.Key, + right: Phaser.Input.Keyboard.Key, + }; + + destroy(): void; + + setEnable(enable?: boolean): this; + toggleEnable(): this; + enable: boolean; + + setVisible(visible?: boolean): this; + toggleVisible(): this; + visible: boolean; + + setPosition(x: number, y: number): this; + x: number; + y: number; + + setScrollFactor(factor: number): this; + + base: Phaser.GameObjects.GameObject; + thumb: Phaser.GameObjects.GameObject; + + readonly left: boolean; + readonly right: boolean; + readonly up: boolean; + readonly down: boolean; + readonly noKey: boolean; + readonly force: number; + readonly forceX: number; + readonly forceY: number; + readonly angle: number; + readonly rotation: number; + readonly pointerX: number; + readonly pointerY: number; + readonly pointer: Phaser.Input.Pointer; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/input/virtualjoystick/VirtualJoyStick.js b/ui/src/phaser3-rex-plugins/plugins/input/virtualjoystick/VirtualJoyStick.js new file mode 100644 index 000000000..efd2dd2c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/input/virtualjoystick/VirtualJoyStick.js @@ -0,0 +1,262 @@ +import TouchCursor from '../../touchcursor.js'; +import EventEmitterMethods from '../../utils/eventemitter/EventEmitterMethods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class VirtualJoyStick { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + config.eventEmitter = this.getEventEmitter(); + + this.scene = scene; + this.base = undefined; + this.thumb = undefined; + this.touchCursor = undefined; + this.setRadius(GetValue(config, 'radius', 100)); + + this.addBase(GetValue(config, 'base', undefined), config); + this.addThumb(GetValue(config, 'thumb', undefined)); + + var x = GetValue(config, 'x', 0); + var y = GetValue(config, 'y', 0); + this.base.setPosition(x, y); + this.thumb.setPosition(x, y); + + if (GetValue(config, 'fixed', true)) { + this.setScrollFactor(0); + } + + this.boot(); + } + + destroy() { + this.destroyEventEmitter(); + this.base.destroy(); // Also destroy touchCursor behavior + this.thumb.destroy(); + + this.scene = undefined; + this.base = undefined; + this.thumb = undefined; + this.touchCursor = undefined; + } + + createCursorKeys() { + return this.touchCursor.createCursorKeys(); + } + + get forceX() { + return this.touchCursor.forceX; + } + + get forceY() { + return this.touchCursor.forceY; + } + + get force() { + return this.touchCursor.force; + } + + get rotation() { + return this.touchCursor.rotation; + } + + get angle() { + return this.touchCursor.angle; // -180 ~ 180 + } + + get up() { + return this.touchCursor.upKeyDown; + } + + get down() { + return this.touchCursor.downKeyDown; + } + + get left() { + return this.touchCursor.leftKeyDown; + } + + get right() { + return this.touchCursor.rightKeyDown; + } + + get noKey() { + return this.touchCursor.noKeyDown; + } + + get pointerX() { + return this.touchCursor.end.x; + } + + get pointerY() { + return this.touchCursor.end.y; + } + + get pointer() { + return this.touchCursor.pointer; + } + + setPosition(x, y) { + if ((this.x === x) && (this.y === y)) { + return this; + } + + this.x = x; + this.y = y; + + this.forceUpdateThumb(); + return this; + } + + set x(value) { + if (this.x === value) { + return; + } + this.base.x = value; + this.thumb.x = value; + } + + set y(value) { + if (this.y === value) { + return; + } + this.base.y = value; + this.thumb.y = value; + } + + get x() { + return this.base.x; + } + + get y() { + return this.base.y; + } + + setVisible(visible) { + this.visible = visible; + return this; + } + + toggleVisible() { + this.visible = !this.visible; + return this; + } + + get visible() { + return this.base.visible; + } + + set visible(visible) { + this.base.visible = visible; + this.thumb.visible = visible; + } + + get enable() { + return this.touchCursor.enable; + } + + set enable(value) { + this.touchCursor.setEnable(value); + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + setRadius(radius) { + this.radius = radius; + return this; + } + + addBase(gameObject, config) { + if (this.base) { + this.base.destroy(); + // Also destroy touchCursor behavior + } + + if (gameObject === undefined) { + gameObject = this.scene.add.circle(0, 0, this.radius) + .setStrokeStyle(3, 0x0000ff); + } + + if (config === undefined) { + config = {}; + } + config.eventEmitter = this.getEventEmitter(); + this.touchCursor = new TouchCursor(gameObject, config) + this.base = gameObject; + return this; + } + + addThumb(gameObject) { + if (this.thumb) { + this.thumb.destroy(); + } + + if (gameObject === undefined) { + gameObject = this.scene.add.circle(0, 0, 40) + .setStrokeStyle(3, 0x00ff00); + } + this.thumb = gameObject; + return this; + } + + setScrollFactor(scrollFactor) { + this.base.setScrollFactor(scrollFactor); + this.thumb.setScrollFactor(scrollFactor); + return this; + } + + boot() { + this.on('update', this.update, this); + } + + // Internal method + update() { + var touchCursor = this.touchCursor; + // Start from (0,0) + var x = this.base.x; + var y = this.base.y; + if (touchCursor.anyKeyDown) { + if (touchCursor.force > this.radius) { // Exceed radius + var rad = touchCursor.rotation; + x += Math.cos(rad) * this.radius; + y += Math.sin(rad) * this.radius; + } else { + x += touchCursor.forceX; + y += touchCursor.forceY; + } + } + this.thumb.x = x; + this.thumb.y = y + return this; + } + + forceUpdateThumb() { + this.touchCursor.forceUpdate(); + return this; + } +} + +Object.assign( + VirtualJoyStick.prototype, + EventEmitterMethods +); + +export default VirtualJoyStick; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/inputtext-plugin.js b/ui/src/phaser3-rex-plugins/plugins/inputtext-plugin.js new file mode 100644 index 000000000..a6ead2e75 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/inputtext-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/dom/inputtext/Factory.js'; +import Creator from './gameobjects/dom/inputtext/Creator.js'; +import InputText from './gameobjects/dom/inputtext/InputText.js'; +import SetValue from './utils/object/SetValue.js'; + +class InputTextPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexInputText', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.InputText', InputText); + +export default InputTextPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/inputtext.d.ts b/ui/src/phaser3-rex-plugins/plugins/inputtext.d.ts new file mode 100644 index 000000000..8f494ad65 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/inputtext.d.ts @@ -0,0 +1,2 @@ +import InputText from './gameobjects/dom/inputtext/InputText'; +export default InputText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/inputtext.js b/ui/src/phaser3-rex-plugins/plugins/inputtext.js new file mode 100644 index 000000000..38daae84a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/inputtext.js @@ -0,0 +1,2 @@ +import InputText from './gameobjects/dom/inputtext/InputText.js'; +export default InputText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/interception-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/interception-plugin.d.ts new file mode 100644 index 000000000..75bfca7ea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/interception-plugin.d.ts @@ -0,0 +1,9 @@ +import Interception from './interception'; + +export default class InterceptionPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Interception.IConfig + ): Interception; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/interception-plugin.js b/ui/src/phaser3-rex-plugins/plugins/interception-plugin.js new file mode 100644 index 000000000..4a23462d1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/interception-plugin.js @@ -0,0 +1,13 @@ +import Interception from './interception.js'; + +class InterceptionPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + add(gameObject, config) { + return new Interception(gameObject, config); + } +} +export default InterceptionPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/interception.d.ts b/ui/src/phaser3-rex-plugins/plugins/interception.d.ts new file mode 100644 index 000000000..96aaa5a36 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/interception.d.ts @@ -0,0 +1,2 @@ +import Interception from './behaviors/interception/Interception'; +export default Interception; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/interception.js b/ui/src/phaser3-rex-plugins/plugins/interception.js new file mode 100644 index 000000000..0d50ad32d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/interception.js @@ -0,0 +1,2 @@ +import Interception from './behaviors/interception/Interception.js'; +export default Interception; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/intouching-plugin.js b/ui/src/phaser3-rex-plugins/plugins/intouching-plugin.js new file mode 100644 index 000000000..b8c0ade6e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/intouching-plugin.js @@ -0,0 +1,20 @@ +import InTouching from './intouching.js'; + +class InTouchingPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new InTouching(gameObject, config); + } + +} + +export default InTouchingPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/intouching.d.ts b/ui/src/phaser3-rex-plugins/plugins/intouching.d.ts new file mode 100644 index 000000000..2629a2bba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/intouching.d.ts @@ -0,0 +1,2 @@ +import InTouching from './input/intouching/InTouching'; +export default InTouching; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/intouching.js b/ui/src/phaser3-rex-plugins/plugins/intouching.js new file mode 100644 index 000000000..4e9ce11d0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/intouching.js @@ -0,0 +1,2 @@ +import InTouching from './input/intouching/InTouching.js'; +export default InTouching; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/inversepipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/inversepipeline-plugin.d.ts new file mode 100644 index 000000000..ba8a9f683 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/inversepipeline-plugin.d.ts @@ -0,0 +1,30 @@ +// import * as Phaser from 'phaser'; +import InversePostFxPipeline from './inversepipeline'; + + +export default InversePipelinePlugin; + +declare namespace InversePipelinePlugin { + + interface IConfig extends InversePostFxPipeline.IConfig { + name?: string, + } + +} + +declare class InversePipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: InversePipelinePlugin.IConfig + ): InversePostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): InversePostFxPipeline | InversePostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/inversepipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/inversepipeline-plugin.js new file mode 100644 index 000000000..91f7b4971 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/inversepipeline-plugin.js @@ -0,0 +1,14 @@ +import InversePostFxPipeline from './inversepipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class InversePipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(InversePostFxPipeline, 'rexInversePostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.InversePostFx', InversePostFxPipeline); + +export default InversePipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/inversepipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/inversepipeline.d.ts new file mode 100644 index 000000000..e1c6988a8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/inversepipeline.d.ts @@ -0,0 +1,2 @@ +import InversePostFxPipeline from './shaders/inverse/InversePostFxPipeline'; +export default InversePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/inversepipeline.js b/ui/src/phaser3-rex-plugins/plugins/inversepipeline.js new file mode 100644 index 000000000..97d41c27c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/inversepipeline.js @@ -0,0 +1,2 @@ +import InversePostFxPipeline from './shaders/inverse/InversePostFxPipeline.js'; +export default InversePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline-plugin.d.ts new file mode 100644 index 000000000..dde746c86 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline-plugin.d.ts @@ -0,0 +1,30 @@ +// import * as Phaser from 'phaser'; +import KawaseBlurFilterPostFxPipeline from './kawaseblurpipeline'; + + +export default KawaseBlurFilterPipelinePlugin; + +declare namespace KawaseBlurFilterPipelinePlugin { + + interface IConfig extends KawaseBlurFilterPostFxPipeline.IConfig { + name?: string, + } + +} + +declare class KawaseBlurFilterPipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject | Phaser.Cameras.Scene2D.Camera, + config?: KawaseBlurFilterPipelinePlugin.IConfig + ): KawaseBlurFilterPostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): KawaseBlurFilterPostFxPipeline | KawaseBlurFilterPostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline-plugin.js new file mode 100644 index 000000000..0cc87b461 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline-plugin.js @@ -0,0 +1,16 @@ +import KawaseBlurFilterPostFxPipeline from './kawaseblurpipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class KawaseBlurFilterPipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(KawaseBlurFilterPostFxPipeline, 'rexKawaseBlurFilterPostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.KawaseBlurFilterPostFx', KawaseBlurFilterPostFxPipeline); + +export default KawaseBlurFilterPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline.d.ts new file mode 100644 index 000000000..f0b596c77 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline.d.ts @@ -0,0 +1,2 @@ +import KawaseBlurFilterPostFxPipeline from './shaders/kawaseblur/KawaseBlurFilterPostFxPipeline'; +export default KawaseBlurFilterPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline.js b/ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline.js new file mode 100644 index 000000000..63f1388c8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/kawaseblurpipeline.js @@ -0,0 +1,2 @@ +import KawaseBlurFilterPostFxPipeline from './shaders/kawaseblur/KawaseBlurFilterPostFxPipeline.js'; +export default KawaseBlurFilterPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/keyshub-plugin.js b/ui/src/phaser3-rex-plugins/plugins/keyshub-plugin.js new file mode 100644 index 000000000..6d9efd566 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/keyshub-plugin.js @@ -0,0 +1,20 @@ +import KeysHub from './keyshub.js'; + +class KeysHubPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new KeysHub(scene, config); + } + +} + +export default KeysHubPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/keyshub.js b/ui/src/phaser3-rex-plugins/plugins/keyshub.js new file mode 100644 index 000000000..1bd483305 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/keyshub.js @@ -0,0 +1,2 @@ +import KeysHub from './input/keyshub/KeysHub.js'; +export default KeysHub; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/layermanager-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/layermanager-plugin.d.ts new file mode 100644 index 000000000..e4bcfea78 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/layermanager-plugin.d.ts @@ -0,0 +1,9 @@ +import LayerManager from './layermanager.js'; + +export default class LayerManagerPlugin extends Phaser.Plugins.BasePlugin { + add( + scene: Phaser.Scene, + config?: LayerManager.IConfig + ): LayerManager; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/layermanager-plugin.js b/ui/src/phaser3-rex-plugins/plugins/layermanager-plugin.js new file mode 100644 index 000000000..11f3cb624 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/layermanager-plugin.js @@ -0,0 +1,18 @@ +import LayerManager from './layermanager.js'; + +class LayerManagerPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new LayerManager(scene, config); + } +} + +export default LayerManagerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/layermanager.d.ts b/ui/src/phaser3-rex-plugins/plugins/layermanager.d.ts new file mode 100644 index 000000000..5bfa0d86b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/layermanager.d.ts @@ -0,0 +1,2 @@ +import LayerManager from './gameobjects/layer/layermanager/LayerManager'; +export default LayerManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/layermanager.js b/ui/src/phaser3-rex-plugins/plugins/layermanager.js new file mode 100644 index 000000000..afb083c82 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/layermanager.js @@ -0,0 +1,2 @@ +import LayerManager from './gameobjects/layer/layermanager/LayerManager.js'; +export default LayerManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lifetime-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/lifetime-plugin.d.ts new file mode 100644 index 000000000..a926401da --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lifetime-plugin.d.ts @@ -0,0 +1,9 @@ +import LifeTime from './lifetime'; + +export default class LifeTimePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: LifeTime.IConfig + ): LifeTime; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lifetime-plugin.js b/ui/src/phaser3-rex-plugins/plugins/lifetime-plugin.js new file mode 100644 index 000000000..38c1f0b87 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lifetime-plugin.js @@ -0,0 +1,20 @@ +import LifeTime from './lifetime.js'; + +class LifeTimePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new LifeTime(gameObject, config); + } + +} + +export default LifeTimePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lifetime.d.ts b/ui/src/phaser3-rex-plugins/plugins/lifetime.d.ts new file mode 100644 index 000000000..aac02049e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lifetime.d.ts @@ -0,0 +1,2 @@ +import LifeTime from './time/lifetime/LifeTime'; +export default LifeTime; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lifetime.js b/ui/src/phaser3-rex-plugins/plugins/lifetime.js new file mode 100644 index 000000000..fd1b07d76 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lifetime.js @@ -0,0 +1,2 @@ +import LifeTime from './time/lifetime/LifeTime.js'; +export default LifeTime; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/line-plugin.js b/ui/src/phaser3-rex-plugins/plugins/line-plugin.js new file mode 100644 index 000000000..40dc463de --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/line-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/rendertexture/line/Factory.js'; +import Creator from './gameobjects/rendertexture/line/Creator.js'; +import Line from './gameobjects/rendertexture/line/Line.js'; +import SetValue from './utils/object/SetValue.js'; + +class LinePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexLine', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.Line', Line); + +export default LinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/line.d.ts b/ui/src/phaser3-rex-plugins/plugins/line.d.ts new file mode 100644 index 000000000..57e54ba53 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/line.d.ts @@ -0,0 +1,2 @@ +import Line from './gameobjects/rendertexture/line/Line.js'; +export default Line; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/line.js b/ui/src/phaser3-rex-plugins/plugins/line.js new file mode 100644 index 000000000..57e54ba53 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/line.js @@ -0,0 +1,2 @@ +import Line from './gameobjects/rendertexture/line/Line.js'; +export default Line; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lineprogress-plugin.js b/ui/src/phaser3-rex-plugins/plugins/lineprogress-plugin.js new file mode 100644 index 000000000..92c56643d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lineprogress-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/shape/lineprogress/Factory.js'; +import Creator from './gameobjects/shape/lineprogress/Creator.js'; +import LineProgress from './gameobjects/shape/lineprogress/LineProgress.js'; +import SetValue from './utils/object/SetValue.js'; + +class LineProgressPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexLineProgress', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.LineProgress', LineProgress); + +export default LineProgressPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lineprogress.d.ts b/ui/src/phaser3-rex-plugins/plugins/lineprogress.d.ts new file mode 100644 index 000000000..1115b8fdb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lineprogress.d.ts @@ -0,0 +1,2 @@ +import LineProgress from './gameobjects/shape/lineprogress/LineProgress'; +export default LineProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lineprogress.js b/ui/src/phaser3-rex-plugins/plugins/lineprogress.js new file mode 100644 index 000000000..9a21cac03 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lineprogress.js @@ -0,0 +1,2 @@ +import LineProgress from './gameobjects/shape/lineprogress/LineProgress.js'; +export default LineProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lineprogresscanvas-plugin.js b/ui/src/phaser3-rex-plugins/plugins/lineprogresscanvas-plugin.js new file mode 100644 index 000000000..c82f56dc9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lineprogresscanvas-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/canvas/lineprogress/Factory.js'; +import Creator from './gameobjects/canvas/lineprogress/Creator.js'; +import LineProgressCanvas from './gameobjects/canvas/lineprogress/LineProgress.js'; +import SetValue from './utils/object/SetValue.js'; + +class LineProgressCanvasPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexLineProgressCanvas', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.LineProgressCanvas', LineProgressCanvas); + +export default LineProgressCanvasPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lineprogresscanvas.d.ts b/ui/src/phaser3-rex-plugins/plugins/lineprogresscanvas.d.ts new file mode 100644 index 000000000..f6c18963a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lineprogresscanvas.d.ts @@ -0,0 +1,2 @@ +import LineProgressCanvas from './gameobjects/canvas/lineprogress/LineProgress'; +export default LineProgressCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lineprogresscanvas.js b/ui/src/phaser3-rex-plugins/plugins/lineprogresscanvas.js new file mode 100644 index 000000000..fd6258382 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lineprogresscanvas.js @@ -0,0 +1,2 @@ +import LineProgressCanvas from './gameobjects/canvas/lineprogress/LineProgress.js'; +export default LineProgressCanvas; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/live2d-plugin.js b/ui/src/phaser3-rex-plugins/plugins/live2d-plugin.js new file mode 100644 index 000000000..dbefe95ec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/live2d-plugin.js @@ -0,0 +1,41 @@ +import Factory from './gameobjects/live2d/gameobject/Factory.js'; +import Creator from './gameobjects/live2d/gameobject/Creator.js'; +import { + Live2dCoreScriptFileCallback, + Live2dFileCallback, + Live2dGameObject +} from './gameobjects/live2d/index.js'; +import SetValue from './utils/object/SetValue.js'; + +class Live2dPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + var game = pluginManager.game; + + var isWebGL = (game.config.renderType === 2); + + if (!isWebGL) { + console.error('Live2d can\'t run in CANVAS render mode.') + } + + // Register new file type to loader, to load live2d core script file (live2dcubismcore.min.js) + pluginManager.registerFileType('rexLive2dCoreScript', Live2dCoreScriptFileCallback); + + // Register new file type to loader, to load live2d model assets + pluginManager.registerFileType('rexLive2d', Live2dFileCallback); + + // Register our new Game Object type + pluginManager.registerGameObject('rexLive2d', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.Live2d', Live2dGameObject); + +export default Live2dPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/live2d.d.ts b/ui/src/phaser3-rex-plugins/plugins/live2d.d.ts new file mode 100644 index 000000000..a7643f510 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/live2d.d.ts @@ -0,0 +1,11 @@ +import { + Live2dCoreScriptFileCallback, + Live2dFileCallback, + Live2dGameObject +} from './gameobjects/live2d/index'; + +export { + Live2dCoreScriptFileCallback, + Live2dFileCallback, + Live2dGameObject +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/live2d.js b/ui/src/phaser3-rex-plugins/plugins/live2d.js new file mode 100644 index 000000000..944c5ea1d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/live2d.js @@ -0,0 +1,14 @@ +import { + Live2dCoreScriptFileCallback, + Live2dFileCallback, + Live2dGameObject +} from './gameobjects/live2d/index.js'; + +Phaser.Loader.FileTypesManager.register('rexLive2dCoreScript', Live2dCoreScriptFileCallback); +Phaser.Loader.FileTypesManager.register('rexLive2d', Live2dFileCallback); + +export { + Live2dCoreScriptFileCallback, + Live2dFileCallback, + Live2dGameObject +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loader/awaitloader/AwaitFile.js b/ui/src/phaser3-rex-plugins/plugins/loader/awaitloader/AwaitFile.js new file mode 100644 index 000000000..368929e0d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loader/awaitloader/AwaitFile.js @@ -0,0 +1,50 @@ +const FILE_POPULATED = Phaser.Loader.FILE_POPULATED; +const UUID = Phaser.Utils.String.UUID; + +class AwaitFile extends Phaser.Loader.File { + constructor(loader, fileConfig) { + if (!fileConfig.hasOwnProperty('type')) { + fileConfig.type = 'await'; + } + if (!fileConfig.hasOwnProperty('url')) { + fileConfig.url = ''; + } + if (!fileConfig.hasOwnProperty('key')) { + fileConfig.key = UUID(); + } + super(loader, fileConfig); + } + + load() { + if (this.state === FILE_POPULATED) { + // Can happen for example in a JSONFile if they've provided a JSON object instead of a URL + this.loader.nextFile(this, true); + } else { + // start loading task + var config = this.config; + var callback = config.callback; + var scope = config.scope; + var successCallback = this.onLoad.bind(this); + var failureCallback = this.onError.bind(this); + if (callback) { + if (scope) { + callback.call(scope, successCallback, failureCallback); + } else { + callback(successCallback, failureCallback); + } + } else { + this.onLoad(); + } + } + } + + onLoad() { + this.loader.nextFile(this, true); + } + + onError() { + this.loader.nextFile(this, false); + } +} + +export default AwaitFile; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loader/awaitloader/AwaitLoaderCallback.d.ts b/ui/src/phaser3-rex-plugins/plugins/loader/awaitloader/AwaitLoaderCallback.d.ts new file mode 100644 index 000000000..b3151a550 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loader/awaitloader/AwaitLoaderCallback.d.ts @@ -0,0 +1,23 @@ +export default AwaitLoaderCallback; + +declare namespace AwaitLoaderCallback { + type RunTaskCallbackType = ( + successCallback: Function, + failureCallback: Function + ) => Promise | void; +} + +declare function AwaitLoaderCallback( + this: Phaser.Loader.LoaderPlugin, + callback: AwaitLoaderCallback.RunTaskCallbackType, + scope?: object +): Phaser.Loader.LoaderPlugin; + +declare function AwaitLoaderCallback( + this: Phaser.Loader.LoaderPlugin, + key: string, + config: { + callback: AwaitLoaderCallback.RunTaskCallbackType, + scope?: object + } +): Phaser.Loader.LoaderPlugin; diff --git a/ui/src/phaser3-rex-plugins/plugins/loader/awaitloader/AwaitLoaderCallback.js b/ui/src/phaser3-rex-plugins/plugins/loader/awaitloader/AwaitLoaderCallback.js new file mode 100644 index 000000000..4b2100409 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loader/awaitloader/AwaitLoaderCallback.js @@ -0,0 +1,34 @@ +import AwaitFile from './AwaitFile.js'; +import IsFunction from '../../utils/object/IsFunction.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +const loaderCallback = function (key, config) { + if (IsFunction(key)) { + var callback = key; + var scope = config; + config = { + config: { + callback: callback, + scope: scope, + } + }; + } else if (IsPlainObject(key)) { + config = key; + if (!config.hasOwnProperty('config')) { + config = { + config: config + }; + } + } else { + config = { + key: key, + config: config + }; + } + this.addFile(new AwaitFile(this, config)); + + return this; +} + +export default loaderCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loader/imageuri/ImageURILoaderCallback.d.ts b/ui/src/phaser3-rex-plugins/plugins/loader/imageuri/ImageURILoaderCallback.d.ts new file mode 100644 index 000000000..50888aa05 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loader/imageuri/ImageURILoaderCallback.d.ts @@ -0,0 +1,8 @@ +export default ImageURILoaderCallback; + +declare function ImageURILoaderCallback( + this: Phaser.Loader.LoaderPlugin, + key: string, + uri: string, + frameConfig?: Phaser.Types.Loader.FileTypes.ImageFrameConfig +): Phaser.Loader.LoaderPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loader/imageuri/ImageURILoaderCallback.js b/ui/src/phaser3-rex-plugins/plugins/loader/imageuri/ImageURILoaderCallback.js new file mode 100644 index 000000000..551e8cda2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loader/imageuri/ImageURILoaderCallback.js @@ -0,0 +1,32 @@ +import AwaitFile from '../awaitloader/AwaitFile.js'; + + +const LoaderCallback = function (key, uri, frameConfig) { + this.addFile(CreateAwiatFile(this, key, uri, frameConfig)); + return this; +} + +var CreateAwiatFile = function (loader, key, uri, frameConfig) { + var callback = function (successCallback, failureCallback) { + var imageElement = new Image(); + imageElement.onload = function () { + if (frameConfig === undefined) { + loader.textureManager.addImage(key, imageElement); + } else { + loader.textureManager.addSpriteSheet(key, imageElement, frameConfig); + } + successCallback(); + } + imageElement.src = uri; + } + + return new AwaitFile(loader, { + type: 'imageuri', + config: { + key: key, + callback: callback + } + }); +} + +export default LoaderCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loader/scripttag/ScriptTagLoaderCallback.d.ts b/ui/src/phaser3-rex-plugins/plugins/loader/scripttag/ScriptTagLoaderCallback.d.ts new file mode 100644 index 000000000..1bd69121a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loader/scripttag/ScriptTagLoaderCallback.d.ts @@ -0,0 +1,15 @@ +export default ScriptTagLoaderCallback; + +declare function ScriptTagLoaderCallback( + this: Phaser.Loader.LoaderPlugin, + url: string, + availableTest?: () => boolean +): Phaser.Loader.LoaderPlugin; + +declare function ScriptTagLoaderCallback( + this: Phaser.Loader.LoaderPlugin, + config: { + url: string + availableTest?: () => boolean + } +): Phaser.Loader.LoaderPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loader/scripttag/ScriptTagLoaderCallback.js b/ui/src/phaser3-rex-plugins/plugins/loader/scripttag/ScriptTagLoaderCallback.js new file mode 100644 index 000000000..e26622b46 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loader/scripttag/ScriptTagLoaderCallback.js @@ -0,0 +1,57 @@ +import AwaitFile from '../awaitloader/AwaitFile.js'; +import LoadScriptPromise from '../../utils/loader/LoadScriptPromise.js'; +import Delay from '../../utils/promise/Delay.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetFastValue = Phaser.Utils.Objects.GetFastValue; + +const LoaderCallback = function (url) { + if (Array.isArray(url)) { + for (var i = 0, cnt = url.length; i < cnt; i++) { + this.addFile(CreateAwiatFile(this, url[i])); + } + } else { + this.addFile(CreateAwiatFile(this, url)); + } + return this; +} + +var CreateAwiatFile = function (loader, url, availableTest) { + if (IsPlainObject(url)) { + var config = url; + url = GetFastValue(config, 'url'); + availableTest = GetFastValue(config, 'availableTest'); + } + + var callback = function (successCallback, failureCallback) { + + LoadScriptPromise(url) + .then(function () { + if (!availableTest) { + return Promise.resolve(); + } + + var AvailableTestPromise = function () { + if (availableTest()) { + return Promise.resolve(); + } + + return Delay(10) + .then(function () { + return AvailableTestPromise(); + }); + } + return AvailableTestPromise(); + }) + .then(function () { + successCallback() + }) + } + + return new AwaitFile(loader, { + type: 'scriptTag', + config: { callback: callback } + }); +} + +export default LoaderCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/TestFont.js b/ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/TestFont.js new file mode 100644 index 000000000..a308a803d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/TestFont.js @@ -0,0 +1,44 @@ +const CanvasPool = Phaser.Display.Canvas.CanvasPool; + +var TestFont = function (familyName, testString) { + // Get canvas from pool + var canvas = CanvasPool.create(); + var context = canvas.getContext('2d', { willReadFrequently: true }); + + // Resize canvas + var font = `8px ${familyName}`; + context.font = font; + var width = Math.ceil(context.measureText(testString).width); + var baseline = width; + var height = 2 * baseline; + if ((width !== canvas.width) || (height !== canvas.height)) { + canvas.width = width; + canvas.height = height; + } + + // Clear canvas + context.fillStyle = '#000'; + context.fillRect(0, 0, width, height); + + // Draw text + context.textBaseline = 'alphabetic'; + context.fillStyle = '#fff'; + context.font = font; + context.fillText(testString, 0, baseline); + + // Check image data array + var imagedata = context.getImageData(0, 0, width, height).data; + var hasPixel = false; + for (var i = 0, cnt = imagedata.length; i < cnt; i += 4) { + if (imagedata[i] > 0) { + hasPixel = true; + break; + } + } + + // Recycle canvas + CanvasPool.remove(canvas); + return hasPixel; +} + +export default TestFont; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/WebFont.js b/ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/WebFont.js new file mode 100644 index 000000000..d82f806f6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/WebFont.js @@ -0,0 +1,88 @@ +import googleWebFontLoader from 'webfontloader'; +import TestFont from './TestFont.js'; + +const FILE_POPULATED = Phaser.Loader.FILE_POPULATED; +const GetValue = Phaser.Utils.Objects.GetValue; + +class WebFont extends Phaser.Loader.File { + // constructor(loader, fileConfig) { + // super(loader, fileConfig); + // } + + load() { + if (this.state === FILE_POPULATED) { + // Can happen for example in a JSONFile if they've provided a JSON object instead of a URL + this.loader.nextFile(this, true); + } else { + // start loading task + var config = this.config; + + this.testString = GetValue(config, 'testString', undefined); + if (this.testString !== undefined) { + this.testInterval = GetValue(config, 'testInterval', 20); + this.fontTests = {}; + delete config.testString; + delete config.testInterval; + } + + config.active = this.onLoad.bind(this); + config.inactive = this.onError.bind(this); + config.fontactive = this.onFontActive.bind(this); + config.fontinactive = this.onFontInactive.bind(this); + googleWebFontLoader.load(config); + } + } + + onLoad() { + if (this.testString === undefined) { + this.loader.nextFile(this, true); + } else { + var testFonts = (function () { + if (this.testFonts()) { + this.loader.nextFile(this, true); + } else { + setTimeout(testFonts, this.testInterval); + } + }).bind(this); + testFonts(); + } + } + + onError() { + this.loader.nextFile(this, false); + } + + onFontActive(familyName, fvd) { + if (this.testString !== undefined) { + var testString; + if (typeof (this.testString) === 'string') { + testString = this.testString; + } else { + testString = this.testString[familyName]; + } + if (testString !== undefined) { + this.fontTests[familyName] = testString; + } + } + this.loader.emit('webfontactive', this, familyName, fvd); + } + + onFontInactive(familyName, fvd) { + this.loader.emit('webfontinactive', this, familyName, fvd); + } + + testFonts() { + var allPass = true; + for (var familyName in this.fontTests) { + if (TestFont(familyName, this.fontTests[familyName])) { + delete this.fontTests[familyName]; + } else { + allPass = false; + } + } + + return allPass; + } +} + +export default WebFont; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/WebFontLoaderCallback.d.ts b/ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/WebFontLoaderCallback.d.ts new file mode 100644 index 000000000..e70e86ff9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/WebFontLoaderCallback.d.ts @@ -0,0 +1,8 @@ +import WebFont from 'webfontloader'; + +export default WebFontLoaderCallback; + +declare function WebFontLoaderCallback( + this: Phaser.Loader.LoaderPlugin, + config: WebFont.Config +): Phaser.Loader.LoaderPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/WebFontLoaderCallback.js b/ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/WebFontLoaderCallback.js new file mode 100644 index 000000000..b73373e65 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loader/webfontloader/WebFontLoaderCallback.js @@ -0,0 +1,32 @@ +import WebFont from './WebFont.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; + +const loaderCallback = function (key, config) { + if (IsPlainObject(key)) { + config = key; + if (config.hasOwnProperty('config')) { + config.type = 'webfont'; + config.url = ''; + } else { + config = { + key: 'webfont', + type: 'webfont', + url: '', + config: config + }; + } + } else { + config = { + type: 'webfont', + url: '', + key: key, + config: config + }; + } + this.addFile(new WebFont(this, config)); + + return this; +} + +export default loaderCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loadingprogress-plugin.js b/ui/src/phaser3-rex-plugins/plugins/loadingprogress-plugin.js new file mode 100644 index 000000000..9c4d05b15 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loadingprogress-plugin.js @@ -0,0 +1,14 @@ + +import LoadingProgress from './loadingprogress.js'; + +class LoadingProgressPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + add(gameObject, config) { + return new LoadingProgress(gameObject, config); + } +} + +export default LoadingProgressPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loadingprogress.js b/ui/src/phaser3-rex-plugins/plugins/loadingprogress.js new file mode 100644 index 000000000..64099950c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loadingprogress.js @@ -0,0 +1,2 @@ +import LoadingProgress from './behaviors/loadingprogress/LoadingProgress.js'; +export default LoadingProgress; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/localforage-files-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/localforage-files-plugin.d.ts new file mode 100644 index 000000000..5904e97db --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/localforage-files-plugin.d.ts @@ -0,0 +1,8 @@ +import Files from './localforage-files'; + +export default class LocalForageFilesPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: Files.IConfig + ): Files; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/localforage-files-plugin.js b/ui/src/phaser3-rex-plugins/plugins/localforage-files-plugin.js new file mode 100644 index 000000000..9aa07b739 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/localforage-files-plugin.js @@ -0,0 +1,18 @@ +import Files from './localforage-files.js' + +class FilesPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new Files(config); + } +} + +export default FilesPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/localforage-files.d.ts b/ui/src/phaser3-rex-plugins/plugins/localforage-files.d.ts new file mode 100644 index 000000000..4e91f5145 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/localforage-files.d.ts @@ -0,0 +1,2 @@ +import Files from './storage/localforage/files/Files'; +export default Files; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/localforage-files.js b/ui/src/phaser3-rex-plugins/plugins/localforage-files.js new file mode 100644 index 000000000..f2c44f4d6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/localforage-files.js @@ -0,0 +1,2 @@ +import Files from './storage/localforage/files/Files.js'; +export default Files; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/localstorage-data-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/localstorage-data-plugin.d.ts new file mode 100644 index 000000000..b0174fe58 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/localstorage-data-plugin.d.ts @@ -0,0 +1,34 @@ +import DataManager from './localstorage-data'; +import Extend from './storage/localstorage/data/Extend'; + +export default class DataManagerPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: DataManager.IConfig + ): DataManager; + + add( + parent: object, + config?: DataManager.IConfig + ): DataManager; + + add( + parent: object, + eventEmitter?: Phaser.Events.EventEmitter, + config?: DataManager.IConfig + ): DataManager; + + extend: typeof Extend; + + setItem( + dataKey: string, name: string, + value: any + ): this; + + getItem( + dataKey: string, name: string, + ): any; + + removeItem( + dataKey: string, name: string, + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/localstorage-data-plugin.js b/ui/src/phaser3-rex-plugins/plugins/localstorage-data-plugin.js new file mode 100644 index 000000000..bc55e1e52 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/localstorage-data-plugin.js @@ -0,0 +1,40 @@ +import DataManager from './localstorage-data.js'; +import Extend from './storage/localstorage/data/Extend.js'; +import { SetItem, GetItem, RemoveItem } from './storage/localstorage/utils/StorageMethods.js'; + + +class DataManagerPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(parent, eventEmitter, config) { + return new DataManager(parent, eventEmitter, config); + } + + extend(dataManager, config) { + return Extend(dataManager, config); + } + + setItem(dataKey, name, value) { + SetItem(dataKey, name, value); + return this; + } + + getItem(dataKey, name) { + return GetItem(dataKey, name); + } + + removeItem(dataKey, name) { + RemoveItem(dataKey, name); + return this; + } +} + +export default DataManagerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/localstorage-data.d.ts b/ui/src/phaser3-rex-plugins/plugins/localstorage-data.d.ts new file mode 100644 index 000000000..bd1540390 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/localstorage-data.d.ts @@ -0,0 +1,2 @@ +import DataManager from './storage/localstorage/data/DataManager'; +export default DataManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/localstorage-data.js b/ui/src/phaser3-rex-plugins/plugins/localstorage-data.js new file mode 100644 index 000000000..898f8f925 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/localstorage-data.js @@ -0,0 +1,2 @@ +import DataManager from './storage/localstorage/data/DataManager.js'; +export default DataManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/achievements/achievements/Achievements.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/achievements/achievements/Achievements.d.ts new file mode 100644 index 000000000..22fb3480d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/achievements/achievements/Achievements.d.ts @@ -0,0 +1,95 @@ +export default Achievements; + +declare namespace Achievements { + type ContextType = { + [name: string]: any + } + + type TestFunctionType = ( + context: ContextType + ) => boolean; + + type AchievementStateType = { + wasObtained: boolean, + justObtained: boolean + } + + type LevelStateType = { + [achievemenName: string]: AchievementStateType + } + + type AllLevelsStatesType = { + [levelName: string]: LevelStateType + } + + type ForEachObtainedStateCallback = + (levelName: string, achievementName: string, obtainedState: AchievementStateType) + => void; + +} +declare class Achievements { + + clear(): this; + + add( + levelName: string, + achievementName: string, + callback: Achievements.TestFunctionType + ): this; + + runTest( + levelName: string, + context: Achievements.ContextType + ): this; + + getTestResults( + levelName: string, + context: Achievements.ContextType + ): Achievements.LevelStateType; + + getObtainedState( + levelName: string, + achievementName: string + ): Achievements.AchievementStateType; + + getObtainedState( + levelName: string + ): Achievements.LevelStateType; + + getObtainedState( + ): Achievements.AllLevelsStatesType; + + forEachObtainedState( + levelName: string, + callback: Achievements.ForEachObtainedStateCallback, + scope?: object + ): this; + + getObtainedStates( + ): Achievements.AllLevelsStatesType; + + loadObtainedStates( + states: Achievements.AllLevelsStatesType + ): this; + + getLevelNames( + out?: string[] + ): string[]; + + getAchievementNames( + levelName: string, + out?: string[] + ): string[]; + + setObtainedState( + levelName: string, + achievementName: string, + value?: boolean + ): this; + + clearObtainedState( + levelName: string, + achievementName: string + ): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/achievements/achievements/Achievements.js b/ui/src/phaser3-rex-plugins/plugins/logic/achievements/achievements/Achievements.js new file mode 100644 index 000000000..a28941b28 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/achievements/achievements/Achievements.js @@ -0,0 +1,153 @@ +import Clear from '../../../utils/object/Clear.js'; + +class Achievements { + constructor() { + this.achievements = {}; + this.obtainedStates = {}; + } + + clear() { + Clear(this.achievements); + Clear(this.obtainedStates); + + return this; + } + + add(levelName, achievementName, callback) { + if (!this.achievements.hasOwnProperty(levelName)) { + this.achievements[levelName] = []; + } + + this.achievements[levelName].push({ + name: achievementName, + function: callback + }); + + return this; + } + + getAchievements(levelName) { + return this.achievements[levelName]; + } + + getObtainedState(levelName, achievementName) { + if (levelName === undefined) { + return this.obtainedStates; + } + + if (!this.obtainedStates.hasOwnProperty(levelName)) { + this.obtainedStates[levelName] = {}; + } + var obtainedStates = this.obtainedStates[levelName]; + + if (achievementName === undefined) { + return obtainedStates; + } + + if (!obtainedStates.hasOwnProperty(achievementName)) { + obtainedStates[achievementName] = { + wasObtained: false, + justObtained: false + }; + } + return obtainedStates[achievementName]; + } + + runTest(levelName, context) { + var achievements = this.getAchievements(levelName); + if (achievements === undefined) { + return this; + } + + var obtainedState; + for (var i = 0, cnt = achievements.length; i < cnt; i++) { + obtainedState = this.getObtainedState(levelName, achievements[i].name); + obtainedState.justObtained = false; + if (obtainedState.wasObtained) { + continue; + } + + if (!achievements[i].function(context)) { + continue; + } + + obtainedState.justObtained = true; + obtainedState.wasObtained = true; + } + + return this; + } + + getTestResults(levelName, context) { + this.runTest(levelName, context); + return this.getObtainedState(levelName); + } + + forEachObtainedState(levelName, callback, scope) { + var achievements = this.getAchievements(levelName); + if (achievements === undefined) { + return this; + } + var achievementName, obtainedState; + for (var i = 0, cnt = achievements.length; i < cnt; i++) { + achievementName = achievements[i].name; + obtainedState = this.getObtainedState(levelName, achievementName); + if (scope) { + callback.call(scope, levelName, achievementName, obtainedState); + } else { + callback(levelName, achievementName, obtainedState); + } + } + + return this; + } + + getLevelNames(out) { + if (out === undefined) { + out = []; + } + for (var levelName in this.achievements) { + out.push(levelName); + } + return out; + } + + getAchievementNames(levelName, out) { + if (out === undefined) { + out = []; + } + var achievements = this.getAchievements(levelName); + if (!achievements) { + return out; + } + for (var i = 0, cnt = achievements.length; i < cnt; i++) { + out.push(achievements[i].name); + } + return names; + } + + loadObtainedStates(states) { + this.obtainedStates = states; + return this; + } + + getObtainedStates() { + return this.obtainedStates; + } + + setObtainedState(levelName, achievementName, value) { + if (value === undefined) { + value = true; + } + var obtainedState = this.getObtainedState(levelName, achievementName); + obtainedState.wasObtained = value; + obtainedState.justObtained = value; + return this; + } + + clearObtainedState(levelName, achievementName) { + this.setObtainedState(levelName, achievementName, gfalse); + return this; + } +} +export default Achievements; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/achievements/csvachievements/Achievements.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/achievements/csvachievements/Achievements.d.ts new file mode 100644 index 000000000..afb42027f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/achievements/csvachievements/Achievements.d.ts @@ -0,0 +1,15 @@ +import Base from '../achievements/Achievements'; + +export default Achievements; + +declare namespace Achievements { + interface ILoadCSVConfig { + delimiter?: string + } +} +declare class Achievements extends Base { + loadCSV( + csvString: string, + config?: Achievements.ILoadCSVConfig + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/achievements/csvachievements/Achievements.js b/ui/src/phaser3-rex-plugins/plugins/logic/achievements/csvachievements/Achievements.js new file mode 100644 index 000000000..bade87c8e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/achievements/csvachievements/Achievements.js @@ -0,0 +1,26 @@ +import Base from '../achievements/Achievements.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import CSVParser from 'papaparse/papaparse.min.js'; +import CreateTestFunction from '../../conditionstable/csvconditiontable/CreateTestFunction.js'; + +class Achievements extends Base { + loadCSV(csvString, config) { + this.clear(); + + var delimiter = GetValue(config, 'delimiter', ','); + var table = CSVParser.parse(csvString, { + delimiter: delimiter + }).data; + + var keys = table[0]; + keys.splice(0, 2); + var levelName, achievementName, items; + for (var i = 1, cnt = table.length; i < cnt; i++) { + [levelName, achievementName, ...items] = table[i]; + this.add(levelName, achievementName, CreateTestFunction(keys, items)); + } + + return this; + } +} +export default Achievements; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/achievements/ymlachievements/Achievements.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/achievements/ymlachievements/Achievements.d.ts new file mode 100644 index 000000000..24fa97302 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/achievements/ymlachievements/Achievements.d.ts @@ -0,0 +1,12 @@ +import Base from '../achievements/Achievements'; + +export default Achievements; + +declare namespace Achievements { +} + +declare class Achievements extends Base { + loadYML( + ymlString: string + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/achievements/ymlachievements/Achievements.js b/ui/src/phaser3-rex-plugins/plugins/logic/achievements/ymlachievements/Achievements.js new file mode 100644 index 000000000..3efa6d669 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/achievements/ymlachievements/Achievements.js @@ -0,0 +1,24 @@ +import Base from '../achievements/Achievements.js'; +import ParseYaml from '../../../utils/yaml/ParseYaml.js'; +import CreateTestFunction from '../../../math/expressionparser/utils/Complile.js'; + +class Achievements extends Base { + loadYML(ymlString) { + this.clear(); + + var doc = ParseYaml(ymlString); + if (!doc) { + return this; + } + + for (var levelName in doc) { + var levelAchevements = doc[levelName]; + for (var achievementName in levelAchevements) { + this.add(levelName, achievementName, CreateTestFunction(levelAchevements[achievementName])); + } + } + + return this; + } +} +export default Achievements; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/Factory.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/Factory.js new file mode 100644 index 000000000..f998b9863 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/Factory.js @@ -0,0 +1,86 @@ +import { BehaviorTree } from './behaviortree/Factory.js'; +import { Blackboard } from './blackboard/Factory.js'; + +import { + BaseNode, + Action, + Composite, + Decorator, + + Succeeder, + Failer, + Runner, + Error, + Wait, + + Selector, + Sequence, + Parallel, + IfSelector, + SwitchSelector, + WeightSelector, + RandomSelector, + ShuffleSelector, + + Bypass, + ForceSuccess, + ForceFailure, + Invert, + TimeLimit, + Cooldown, + Repeat, + RepeatUntilFailure, + RepeatUntilSuccess, + Limiter, + If, + ContinueIf, + AbortIf, +} from './nodes/Factory.js'; + +import { LoadYaml } from './parsers/yaml/Factory.js' + +export { + // Core + BehaviorTree, + Blackboard, + + BaseNode, + Composite, + Decorator, + Action, + + // Composites + Selector, + Sequence, + Parallel, + IfSelector, + SwitchSelector, + WeightSelector, + RandomSelector, + ShuffleSelector, + + // Decorators + Bypass, + ForceSuccess, + ForceFailure, + Invert, + TimeLimit, + Cooldown, + Repeat, + RepeatUntilFailure, + RepeatUntilSuccess, + Limiter, + If, + ContinueIf, + AbortIf, + + // Actions + Succeeder, + Failer, + Runner, + Error, + Wait, + + // Parsers + LoadYaml, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/LICENSE b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/LICENSE new file mode 100644 index 000000000..13593cf5a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Renato de Pontes Pereira + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/ObjectFactory.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/ObjectFactory.js new file mode 100644 index 000000000..a2b445b80 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/ObjectFactory.js @@ -0,0 +1,6 @@ +class ObjectFactory { + static register(type, callback) { + ObjectFactory.prototype[type] = callback; + } +}; +export default ObjectFactory; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/README.md b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/README.md new file mode 100644 index 000000000..c68143fd4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/README.md @@ -0,0 +1,100 @@ +# BehaviorTree + +Reference: https://github.com/behavior3/behavior3js/ + +## Nodes + +- Composite Nodes: + - Selector : MemSelector + - Sequence : MemSequence + - Parallel : + - First child is main task + - Return status of main task + - IfSelector + - SwitchSelector + - WeightSelector : Random select a child with weight + - ShuffleSelector : Shuffle children of selector +- Decorators: + - If + - Bypass + - ForceSuccess + - ForceFailure + - TimeLimit + - Return FAILURE when timeout, else return child statue + - Cooldown : + - Start cooldown when child status is not RUNNING + - Return FAILURE during cooldown, else return child statue + - Repeat + - RepeaterUntilFailure + - RepeaterUntilSuccess + - Invert + - Limiter +- Actions: + - Succeeder + - Failer + - Runner + - Wait + - Error + - Abort + +## Logic mapping + +### If + +``` +if ConditionA + TaskA +else if ConditionB + TaskB +else + TaskC +``` + +Map to + +- Selector + - if-ConditionA + - TaskA + - if-ConditionB + - TaskB + - TaskC + +### While + +``` +while ConditionA + TaskA +``` + +Map to + +- RepeaterUntilFailure + - if-ConditionA + - TaskA + +### Tick + +#### State machine + +```javascript +node._execute(tick); +``` + +```mermaid +graph TD + +ENTER("ENTER
    ----
    node._enter()
    -> tick._enterNode(), node.enter()") +OPEN("OPEN
    ----
    node._open()
    -> tick._openNode(), node.open()") +TICK("TICK
    ----
    node._tick()
    -> tick._tickNode(), node.tick()") +CLOSE("CLOSE
    ----
    node._close()
    -> tick._closeNode(), node.close()") +EXIT("EXIT
    ----
    node._exit()
    -> tick._exitNode(), node.exit()") + +ENTER --> |NOT isOpen| OPEN +OPEN --> TICK +ENTER --> |isOpen| TICK +TICK --> |NOT isRunning| CLOSE +CLOSE --> EXIT +TICK --> |isRunning| EXIT +``` + +- When closing a node, also close children nodes. \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/BehaviorTree.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/BehaviorTree.js new file mode 100644 index 000000000..9f9f170f7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/BehaviorTree.js @@ -0,0 +1,184 @@ +import { TREE, TREE_STATE, IDLE } from '../constants.js' +import { CreateID, SetSerialNumber, SetSerialNumberPrefix, GetSerialNumber } from '../utils/CreateID.js'; +import Dump from './Dump.js'; +import Load from './Load.js'; +import Tick from '../tick/Tick.js'; +import { BreadthFirstSearch } from './Traversal.js'; + +class BehaviorTree { + + constructor( + { + id, + title, + description, + properties, + root = null + } = {} + ) { + + if (id === undefined) { + id = CreateID(); + } + + this.id = id; + + this.category = TREE; + + this.title = title || ''; + + this.description = description || ''; + + this.properties = properties || {}; + + this._root = root; + + this.ticker = new Tick(); + } + + setTitle(title) { + this.title = title; + return this; + } + + setName(name) { + this.name = name; + return this; + } + + setDescription(description) { + this.description = description; + return this; + } + + setRoot(node) { + this.root = node; + return this; + } + + getRoot() { + return this.root; + } + + get root() { + return this._root; + } + + set root(node) { + if (node) { + this._root = node; + node.setParent(this); + } else { + if (this._root) { + this._root.setParent(null); + } + this._root = null; + } + } + + forEachNode(callback, scope) { + BreadthFirstSearch(this.root, callback, scope); + return this; + } + + getAllNodes(out) { + if (out === undefined) { + out = []; + } + this.forEachNode(function (node) { + out.push(node) + }) + return out; + } + + getChildrenNodes(parent, out) { + if (parent === undefined) { + parent = this.root; + } + if (out === undefined) { + out = []; + } + + BreadthFirstSearch(parent, function (node) { + out.push(node) + }); + + return out; + } + + tick(blackboard, target) { + if (!blackboard) { + throw 'The blackboard parameter is obligatory and must be an instance of Blackboard'; + } + + var ticker = this.ticker; + ticker + .setBlackBoard(blackboard) + .setTree(this) + .setTarget(target) + .reset(); + + /* TICK NODE */ + var state = this.root._execute(ticker); + + /* POPULATE BLACKBOARD */ + // blackboard.set('$openNodes', ticker._openNodes.slice(0), this.id); + // blackboard.set('$nodeCount', ticker._nodeCount, this.id); + blackboard.set(TREE_STATE, state, this.id); + + return state; + } + + abort(blackboard, target) { + if (!blackboard) { + throw 'The blackboard parameter is obligatory and must be an instance of Blackboard'; + } + + var ticker = this.ticker; + ticker + .setBlackBoard(blackboard) + .setTree(this) + .setTarget(target) + .reset(); + + /* ABORT NODE */ + this.root.abortChildren(ticker); + + /* POPULATE BLACKBOARD */ + blackboard.set(TREE_STATE, IDLE, this.id); + + return IDLE; + } + + getState(blackboard) { + return blackboard.get(TREE_STATE, this.id); + } + + resetState(blackboard) { + blackboard.set(TREE_STATE, IDLE, this.id); + return this; + } + + static setStartIDValue(value) { + SetSerialNumber(value); + } + + static getSerialNumber() { + return GetSerialNumber(); + } + + static setSerialIDPrefix(prefix) { + SetSerialNumberPrefix(prefix); + } +}; + +var Methods = { + dump: Dump, + load: Load, +} +Object.assign( + BehaviorTree.prototype, + Methods +); + +export default BehaviorTree; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Dump.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Dump.js new file mode 100644 index 000000000..017872862 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Dump.js @@ -0,0 +1,68 @@ +import { BreadthFirstSearch } from './Traversal.js'; +import { ACTION, COMPOSITE, DECORATOR } from '../constants.js'; +import DeepClone from '../../../utils/object/DeepClone.js'; +import { GetSerialNumber } from '../utils/CreateID.js' + +var Dump = function () { + var data = { + sn: GetSerialNumber(), + id: this.id, + title: this.title, + description: this.description, + root: (this.root) ? this.root.id : null, + properties: DeepClone(this.properties), + nodes: [], + }; + + if (!this.root) { + return data; + } + + var nodes = []; + BreadthFirstSearch(this.root, function (child) { + nodes.push(child); + }) + + for (var i = 0, cnt = nodes.length; i < cnt; i++) { + var node = nodes[i]; + + var spec = { + id: node.id, + name: node.name, + title: node.title, + description: node.description, + properties: DeepClone(node.properties) + }; + + switch (node.category) { + case COMPOSITE: + spec.children = node.children.map((child) => child.id); + + if (node.services) { + spec.services = node.services.map((child) => child.id); + } + + break; + + case DECORATOR: + if (node.child) { + spec.child = node.child.id; + } + + break; + + case ACTION: + if (node.services) { + spec.services = node.services.map((child) => child.id); + } + + break; + } + + data.nodes.push(spec); + } + + return data; +} + +export default Dump; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Factory.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Factory.js new file mode 100644 index 000000000..6550bd3f8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Factory.js @@ -0,0 +1,13 @@ +import BehaviorTree from './BehaviorTree.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('behaviorTree', function (config) { + return new BehaviorTree(config); +}); + +SetValue(window, 'RexPlugins.BehaviorTree.BehaviorTree', BehaviorTree); + +export { + BehaviorTree, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Load.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Load.js new file mode 100644 index 000000000..56f0fdd5a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Load.js @@ -0,0 +1,65 @@ +import * as Nodes from '../nodes'; +import { GetSerialNumber, SetSerialNumber } from '../utils/CreateID.js' + +var Load = function (data, names) { + var sn = data.sn; + if (sn != null) { + SetSerialNumber(Math.max(GetSerialNumber(), sn)) + } + + names = names || {}; + + this.title = data.title || this.title; + this.description = data.description || this.description; + this.properties = data.properties || this.properties; + + var nodeData = data.nodes; + var nodes = {}; + for (var i = nodeData.length - 1; i >= 0; i--) { + // Create nodes from bottom to top + var spec = nodeData[i], + className = spec.name; + + var Cls; + if (className in names) { + // Look for the name in custom nodes + Cls = names[className]; + } else if (className in Nodes) { + // Look for the name in default nodes + Cls = Nodes[className]; + } else { + // Invalid node name + throw new EvalError(`BehaviorTree.load: Invalid node name "${className}".`); + } + + var config = {}; + if (spec.hasOwnProperty('children')) { + config.children = spec.children; + } + if (spec.hasOwnProperty('child')) { + config.child = spec.child; + } + if (spec.hasOwnProperty('services')) { + config.services = spec.services; + } + + config = Object.assign( + config, + spec.properties, + ) + + var node = new Cls(config, nodes); + node.id = spec.id || node.id; + node.title = spec.title || node.title; + node.description = spec.description || node.description; + node.properties = spec.properties || node.properties; + + nodes[node.id] = node; + } + + this.root = nodes[data.root]; + + return this; +} + +export default Load; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Traversal.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Traversal.js new file mode 100644 index 000000000..f9c4318ea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/behaviortree/Traversal.js @@ -0,0 +1,77 @@ +import { ACTION, COMPOSITE, DECORATOR } from '../constants.js'; + +var DepthFirstSearch = function (root, callback, scope) { + var skip = callback.call(scope, root); + + if (skip) { + return; + } + + switch (current.category) { + case COMPOSITE: + var children = root.children; + for (var i = 0, cnt = children.length; i < cnt; i++) { + DepthFirstSearch(children[i], callback, scope); + } + + var services = root.services; + if (services) { + for (var i = 0, cnt = services.length; i < cnt; i++) { + DepthFirstSearch(services[i], callback, scope); + } + } + break; + + case DECORATOR: + DepthFirstSearch(root.child, callback, scope); + break; + + case ACTION: + var services = root.services; + if (services) { + for (var i = 0, cnt = services.length; i < cnt; i++) { + DepthFirstSearch(services[i], callback); + } + } + break; + } +} + +var BreadthFirstSearch = function (root, callback, scope) { + var queue = [root]; + while (queue.length > 0) { + var current = queue.shift(); + var skip = callback.call(scope, current); + + if (skip) { + continue; + } + + switch (current.category) { + case COMPOSITE: + queue.push(...current.children); + + var services = current.services; + if (services) { + queue.push(...services); + } + break; + + case DECORATOR: + queue.push(current.child); + break; + + case ACTION: + var services = current.services; + if (services) { + queue.push(...services); + } + break; + } + } +} + +export { + DepthFirstSearch, + BreadthFirstSearch +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/blackboard/Base.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/blackboard/Base.js new file mode 100644 index 000000000..f5401c265 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/blackboard/Base.js @@ -0,0 +1,171 @@ +import DeepClone from '../../../utils/object/DeepClone.js'; + +class Blackboard { + + constructor() { + this._baseMemory = {}; + this._treeMemory = {}; + + // Global memory : this._baseMemory + // Tree memory : this._treeMemory[treeID] + // Node memory : this._treeMemory[treeID].nodeMemory[nodeID] + } + + _getTreeMemory(treeID) { + if (!this._treeMemory[treeID]) { + this._treeMemory[treeID] = { + 'nodeMemory': {}, + }; + } + return this._treeMemory[treeID]; + } + + _getNodeMemory(treeMemory, nodeID) { + var memory = treeMemory.nodeMemory; + if (!memory[nodeID]) { + memory[nodeID] = {}; + } + + return memory[nodeID]; + } + + _getMemory(treeID, nodeID) { + var memory; + + if (treeID !== undefined) { + memory = this._getTreeMemory(treeID); + + if (nodeID !== undefined) { + memory = this._getNodeMemory(memory, nodeID); + } + } else { + memory = this._baseMemory; + } + + return memory; + } + + set(key, value, treeID, nodeID) { + var memory = this._getMemory(treeID, nodeID); + memory[key] = value; + return this; + } + + setData(key, value, treeID, nodeID) { + return this.set(key, value, treeID, nodeID); + } + + get(key, treeID, nodeID) { + var memory = this._getMemory(treeID, nodeID); + return memory[key]; + } + + getData(key, treeID, nodeID) { + return this.get(key, treeID, nodeID); + } + + has(key, treeID, nodeID) { + var memory; + if (treeID !== undefined) { + memory = this._treeMemory[treeID]; + if (memory && (nodeID !== undefined)) { + memory = treeMemory.nodeMemory[nodeID]; + } + } else { + memory = this._baseMemory; + } + + if (memory) { + return memory.hasOwnProperty(key); + } else { + return false; + } + } + + hasData(key, treeID, nodeID) { + return this.has(key, treeID, nodeID); + } + + inc(key, inc, treeID, nodeID) { + var value; + if (this.has(key, treeID, nodeID)) { + value = 0; + } else { + value = this.get(key, treeID, nodeID); + } + value += inc; + this.set(key, value, treeID, nodeID); + return this; + } + + incData(key, inc, treeID, nodeID) { + return this.inc(key, inc, treeID, nodeID); + } + + toggle(key, treeID, nodeID) { + var value; + if (this.has(key, treeID, nodeID)) { + value = false; + } else { + value = this.get(key, treeID, nodeID); + } + value = !value; + this.set(key, value, treeID, nodeID); + return this; + } + + toggleData(key, treeID, nodeID) { + return this.toggle(key, treeID, nodeID); + } + + removeTree(treeID) { + if (this._treeMemory[treeID]) { + delete this._treeMemory[treeID]; + } + return this; + } + + removeTreeData(treeID) { + return this.removeTree(treeID); + } + + removeNode(treeID, nodeID) { + var treeMemory = this._treeMemory[treeID]; + + if (treeMemory && treeMemory.nodeMemory[nodeID]) { + delete treeMemory.nodeMemory[nodeID]; + } + return this; + } + + removeNodeData(treeID, nodeID) { + return this.removeNode(treeID, nodeID); + } + + getGlobalMemory() { + return this._baseMemory; + } + + getTreeMemory(treeID) { + return this._getTreeMemory(treeID); + } + + getNodeMemory(treeID, nodeID) { + return this._getNodeMemory(this._getTreeMemory(treeID), nodeID); + } + + dump() { + return { + base: DeepClone(this._baseMemory), + tree: DeepClone(this._treeMemory), + } + } + + load(data) { + this._baseMemory = DeepClone(data.base); + this._treeMemory = DeepClone(data.tree); + return this; + } +}; + +export default Blackboard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/blackboard/Blackboard.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/blackboard/Blackboard.js new file mode 100644 index 000000000..72149af7f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/blackboard/Blackboard.js @@ -0,0 +1,20 @@ +import Base from './Base.js'; +import { TREE_STATE, CURRENT_TIME } from '../constants.js'; + +class Blackboard extends Base { + getTreeState(treeID) { + return this.get(TREE_STATE, treeID); + } + + setTreeState(treeID, state) { + this.set(TREE_STATE, state, treeID); + return this; + } + + setCurrentTime(time) { + this.set(CURRENT_TIME, time); + } + +}; + +export default Blackboard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/blackboard/Factory.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/blackboard/Factory.js new file mode 100644 index 000000000..0d3098e42 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/blackboard/Factory.js @@ -0,0 +1,13 @@ +import Blackboard from './Blackboard.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +ObjectFactory.register('blackboard', function (config) { + return new Blackboard(config); +}); + +SetValue(window, 'RexPlugins.BehaviorTree.Blackboard', Blackboard); + +export { + Blackboard, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/constants.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/constants.js new file mode 100644 index 000000000..ba2381d0b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/constants.js @@ -0,0 +1,16 @@ +export const IDLE = 0; +export const SUCCESS = 1; +export const FAILURE = 2; +export const RUNNING = 3; +export const PENDING = 4; +export const ABORT = 5; +export const ERROR = 9; + +export const TREE = 'tree'; +export const COMPOSITE = 'composite'; +export const DECORATOR = 'decorator'; +export const ACTION = 'action'; +export const SERVICE = 'service'; + +export const TREE_STATE = '$state'; +export const CURRENT_TIME = '$currentTime'; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/index.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/index.js new file mode 100644 index 000000000..8a1e203c7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/index.js @@ -0,0 +1,106 @@ +import { + IDLE, SUCCESS, FAILURE, RUNNING, PENDING, ABORT, ERROR, + COMPOSITE, DECORATOR, ACTION, SERVICE +} from './constants'; + +import { CreateID, SetSerialNumber, SetSerialNumberPrefix, GetSerialNumber } from './utils/CreateID.js' + +import BehaviorTree from './behaviortree/BehaviorTree.js'; +import Blackboard from './blackboard/Blackboard.js'; +import Tick from './tick/Tick.js'; + +import { + BaseNode, + Action, + Composite, + Decorator, + + Succeeder, + Failer, + Runner, + Error, + Wait, + Abort, + + Selector, + Sequence, + Parallel, + IfSelector, + SwitchSelector, + WeightSelector, + RandomSelector, + ShuffleSelector, + + Bypass, + ForceSuccess, + ForceFailure, + Invert, + TimeLimit, + Cooldown, + Repeat, + RepeatUntilFailure, + RepeatUntilSuccess, + Limiter, + If, + ContinueIf, + AbortIf, +} from './nodes'; + +export { + IDLE, + SUCCESS, + FAILURE, + RUNNING, + PENDING, + ABORT, + ERROR, + + COMPOSITE, + DECORATOR, + ACTION, + SERVICE, + + CreateID, + SetSerialNumber, + SetSerialNumberPrefix, + GetSerialNumber, + + BehaviorTree, + Blackboard, + Tick, + + BaseNode, + Action, + Composite, + Decorator, + + Succeeder, + Failer, + Runner, + Error, + Wait, + Abort, + + Selector, + Sequence, + Parallel, + IfSelector, + SwitchSelector, + WeightSelector, + RandomSelector, + ShuffleSelector, + + Bypass, + ForceSuccess, + ForceFailure, + Invert, + TimeLimit, + Cooldown, + Repeat, + RepeatUntilFailure, + RepeatUntilSuccess, + Limiter, + If, + ContinueIf, + AbortIf, +}; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Action.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Action.js new file mode 100644 index 000000000..2e60ee3a0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Action.js @@ -0,0 +1,61 @@ +import BaseNode from './BaseNode.js'; +import { ACTION } from '../constants.js'; + +class Action extends BaseNode { + + constructor( + { + name = 'Action', + title, + properties, + services, + } = {}, + nodePool + ) { + + super({ + category: ACTION, + name, + title, + properties, + }); + + if (services) { + for (var i = 0, cnt = services.length; i < cnt; i++) { + this.addService(services[i], nodePool); + } + } + } + + addService(node, nodePool) { + if (typeof (node) === 'string') { // Node ID + node = nodePool[node]; + } + + if (this.services === undefined) { + this.services = []; + } + + if (this.services.indexOf(node) === -1) { + this.services.push(node); + node.setParent(this); + } + + return this; + } + + _tick(tick) { + tick._tickNode(this); + + if (this.services) { + for (var i = 0, cnt = this.services.length; i < cnt; i++) { + this.services[i]._tick(tick); + } + } + + return this.tick(tick); + } + +}; + +export default Action; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/BaseNode.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/BaseNode.js new file mode 100644 index 000000000..acfc6dbe0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/BaseNode.js @@ -0,0 +1,198 @@ +import { CreateID } from '../utils/CreateID.js'; +import { Expression, BooleanExpression, StringTemplateExpression } from './expressions'; +import { TREE, SUCCESS, FAILURE, RUNNING, PENDING, ABORT, ERROR } from '../constants.js'; + +export default class BaseNode { + + constructor( + { + id, + category, + name, + title, + description, + properties + } = {} + ) { + + if (id === undefined) { + id = CreateID(); + } + + this.parent = null; + + this.id = id; + + this.category = category || ''; + + this.name = name || ''; + + this.title = title || this.name; + + this.description = description || ''; + + this.properties = properties || {}; + } + + setTitle(title) { + this.title = title; + return this; + } + + setName(name) { + this.name = name; + return this; + } + + setDescription(description) { + this.description = description; + return this; + } + + setParent(parent) { + this.parent = parent; + return this; + } + + getParent() { + return this.parent; + } + + getTree(tick) { + if (tick) { + return tick.tree; + } else { + var parent = this.parent; + while (parent) { + if (parent.category === TREE) { + return parent; + } + parent = parent.parent; + } + return null; + } + } + + addExpression(expression) { + return new Expression(expression); + } + + addBooleanExpression(expression) { + return new BooleanExpression(expression); + } + + addStringTemplateExpression(expression) { + // TODO: Use mustache or handlebars ? + return new StringTemplateExpression(expression); + } + + _execute(tick) { + // ENTER + this._enter(tick); + + // OPEN + if (!this.getOpenState(tick)) { + this._open(tick); + } + + // TICK + var status = this._tick(tick); + + // CLOSE + if ((status === SUCCESS) || (status === FAILURE) || + (status === ABORT) || (status === ERROR)) { + this._close(tick); + } + + // EXIT + this._exit(tick); + + return status; + } + + _enter(tick) { + tick._enterNode(this); + this.enter(tick); + } + + _open(tick) { + tick._openNode(this); + this.setOpenState(tick, true); + this.open(tick); + } + + _tick(tick) { + tick._tickNode(this); + return this.tick(tick); + } + + _close(tick) { + tick._closeNode(this); + this.setOpenState(tick, false); + this.close(tick); + // Children will be closed before parent, otherwise abort children + this.abortChildren(tick); + } + + _exit(tick) { + tick._exitNode(this); + this.exit(tick); + } + + _abort(tick) { + this._close(tick); + this.abort(tick); + } + + enter(tick) { } + + open(tick) { } + + tick(tick) { } + + close(tick) { } + + exit(tick) { } + + abortChildren(tick) { } + + abort(tick) { } + + // open state of this node + getNodeMemory(tick) { + return tick.getNodeMemory(this.id); + } + + getOpenState(tick) { + return this.getNodeMemory(tick).$isOpen; + } + + setOpenState(tick, state) { + if (state === undefined) { + state = true; + } + this.getNodeMemory(tick).$isOpen = state; + return this; + } + + // Return state value + get SUCCESS() { + return SUCCESS; + } + + get FAILURE() { + return FAILURE; + } + + get RUNNING() { + return RUNNING; + } + + get PENDING() { + return PENDING; + } + + get ERROR() { + return ERROR; + } +}; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Composite.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Composite.js new file mode 100644 index 000000000..e247d365e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Composite.js @@ -0,0 +1,102 @@ +import BaseNode from './BaseNode.js'; +import { COMPOSITE } from '../constants.js'; + +class Composite extends BaseNode { + + constructor( + { + children = [], + name = 'Composite', + title, + properties, + services, + } = {}, + nodePool + ) { + + super({ + category: COMPOSITE, + name, + title, + properties, + }); + + this.children = []; + for (var i = 0, cnt = children.length; i < cnt; i++) { + this.addChild(children[i], nodePool); + } + + if (services) { + for (var i = 0, cnt = services.length; i < cnt; i++) { + this.addService(services[i], nodePool); + } + } + } + + insertChild(node, nodePool, index) { + if (typeof (node) === 'string') { // Node ID + node = nodePool[node]; + } + + if (this.children.indexOf(node) === -1) { + if (index < 0) { + index = this.children.length + index; + } + if ((index === undefined) || (index >= this.children.length)) { + this.children.push(node); + } else { + this.children.splice(index, 0, node); + } + + node.setParent(this); + } + + return this; + } + + addChild(node, nodePool,) { + this.insertChild(node, nodePool); + return this; + } + + addService(node, nodePool) { + if (typeof (node) === 'string') { // Node ID + node = nodePool[node]; + } + + if (this.services === undefined) { + this.services = []; + } + + if (this.services.indexOf(node) === -1) { + this.services.push(node); + node.setParent(this); + } + + return this; + } + + _tick(tick) { + tick._tickNode(this); + + if (this.services) { + for (var i = 0, cnt = this.services.length; i < cnt; i++) { + this.services[i]._tick(tick); + } + } + + return this.tick(tick); + } + + abortChildren(tick) { + for (var i = 0, cnt = this.children.length; i < cnt; i++) { + var childNode = this.children[i]; + if (childNode.getOpenState(tick)) { + childNode._abort(tick); + } + } + } + +}; + +export default Composite; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Decorator.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Decorator.js new file mode 100644 index 000000000..ac0f2d642 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Decorator.js @@ -0,0 +1,55 @@ +import BaseNode from './BaseNode.js'; +import { DECORATOR } from '../constants.js'; + +class Decorator extends BaseNode { + + constructor( + { + child = null, + name = 'Decorator', + title, + properties + } = {}, + nodePool + ) { + + super({ + category: DECORATOR, + name, + title, + properties, + }); + + this.child = null; + if (child) { + this.addChild(child, nodePool); + } + } + + addChild(node, nodePool) { + if (typeof (node) === 'string') { // Node ID + node = nodePool[node]; + } + + this.child = node; + node.setParent(this); + return this; + } + + isChildRunning(tick) { + return this.child.getOpenState(tick); + } + + abortChildren(tick) { + if (this.isChildRunning(tick)) { + this.child._abort(tick); + } + } + + openChild(tick) { + this.child.setOpenState(tick, true); + return this; + } +}; + +export default Decorator; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Factory.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Factory.js new file mode 100644 index 000000000..a2dae31e6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Factory.js @@ -0,0 +1,192 @@ +import BaseNode from './BaseNode.js'; +import Action from './Action.js'; +import Composite from './Composite.js'; +import Decorator from './Decorator'; +import Service from './Service.js'; + +import Succeeder from './actions/Succeeder.js'; +import Failer from './actions/Failer.js'; +import Runner from './actions/Runner.js'; +import Error from './actions/Error.js'; +import Wait from './actions/Wait.js'; + +import Selector from './composites/Selector.js'; +import Sequence from './composites/Sequence.js'; +import Parallel from './composites/Parallel.js'; +import IfSelector from './composites/IfSelector.js'; +import SwitchSelector from './composites/SwitchSelector.js'; +import WeightSelector from './composites/WeightSelector.js'; +import RandomSelector from './composites/RandomSelector.js'; +import ShuffleSelector from './composites/ShuffleSelector.js'; + +import Bypass from './decorators/Bypass.js'; +import ForceSuccess from './decorators/ForceSuccess.js'; +import ForceFailure from './decorators/ForceFailure.js'; +import Invert from './decorators/Invert.js'; +import TimeLimit from './decorators/TimeLimit.js'; +import Cooldown from './decorators/Cooldown.js'; +import Repeat from './decorators/Repeat.js'; +import RepeatUntilFailure from './decorators/RepeatUntilFailure.js'; +import RepeatUntilSuccess from './decorators/RepeatUntilSuccess.js'; +import Limiter from './decorators/Limiter.js'; +import If from './decorators/If.js'; +import ContinueIf from './decorators/ContinueIf.js'; +import AbortIf from './decorators/AbortIf.js'; + +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../../utils/object/SetValue.js'; + +// Actions +ObjectFactory.register('successAction', function (config) { + return new Succeeder(config); +}); +ObjectFactory.register('failureAction', function (config) { + return new Failer(config); +}); +ObjectFactory.register('runningAction', function (config) { + return new Runner(config); +}); +ObjectFactory.register('errorAction', function (config) { + return new Error(config); +}); +ObjectFactory.register('wait', function (config) { + return new Wait(config); +}); + +// Composites +ObjectFactory.register('selector', function (config) { + return new Selector(config); +}); +ObjectFactory.register('sequence', function (config) { + return new Sequence(config); +}); +ObjectFactory.register('parallel', function (config) { + return new Parallel(config); +}); +ObjectFactory.register('ifSelector', function (config) { + return new IfSelector(config); +}); +ObjectFactory.register('switchSelector', function (config) { + return new SwitchSelector(config); +}); +ObjectFactory.register('weightSelector', function (config) { + return new WeightSelector(config); +}); +ObjectFactory.register('randomSelector', function (config) { + return new RandomSelector(config); +}); +ObjectFactory.register('shuffleSelector', function (config) { + return new ShuffleSelector(config); +}); + +// Decorators +ObjectFactory.register('bypass', function (config) { + return new Bypass(config); +}); +ObjectFactory.register('forceSuccess', function (config) { + return new ForceSuccess(config); +}); +ObjectFactory.register('forceFailure', function (config) { + return new ForceFailure(config); +}); +ObjectFactory.register('invert', function (config) { + return new Invert(config); +}); +ObjectFactory.register('timeLimit', function (config) { + return new TimeLimit(config); +}); +ObjectFactory.register('cooldown', function (config) { + return new Cooldown(config); +}); +ObjectFactory.register('repeat', function (config) { + return new Repeat(config); +}); +ObjectFactory.register('repeatUntilFailure', function (config) { + return new RepeatUntilFailure(config); +}); +ObjectFactory.register('repeatUntilSuccess', function (config) { + return new RepeatUntilSuccess(config); +}); +ObjectFactory.register('limiter', function (config) { + return new Limiter(config); +}); +ObjectFactory.register('if', function (config) { + return new If(config); +}); +ObjectFactory.register('continueIf', function (config) { + return new ContinueIf(config); +}); +ObjectFactory.register('abortIf', function (config) { + return new AbortIf(config); +}); + + +SetValue(window, 'RexPlugins.BehaviorTree.Action', Action); +SetValue(window, 'RexPlugins.BehaviorTree.Composite', Composite); +SetValue(window, 'RexPlugins.BehaviorTree.Decorator', Decorator); +SetValue(window, 'RexPlugins.BehaviorTree.Service', Service); + +SetValue(window, 'RexPlugins.BehaviorTree.Succeeder', Succeeder); +SetValue(window, 'RexPlugins.BehaviorTree.Failer', Failer); +SetValue(window, 'RexPlugins.BehaviorTree.Runner', Runner); +SetValue(window, 'RexPlugins.BehaviorTree.Error', Error); +SetValue(window, 'RexPlugins.BehaviorTree.Wait', Wait); + +SetValue(window, 'RexPlugins.BehaviorTree.Selector', Selector); +SetValue(window, 'RexPlugins.BehaviorTree.Sequence', Sequence); +SetValue(window, 'RexPlugins.BehaviorTree.Parallel', Parallel); +SetValue(window, 'RexPlugins.BehaviorTree.IfSelector', IfSelector); +SetValue(window, 'RexPlugins.BehaviorTree.SwitchSelector', SwitchSelector); +SetValue(window, 'RexPlugins.BehaviorTree.WeightSelector', WeightSelector); +SetValue(window, 'RexPlugins.BehaviorTree.RandomSelector', RandomSelector); +SetValue(window, 'RexPlugins.BehaviorTree.ShuffleSelector', ShuffleSelector); + +SetValue(window, 'RexPlugins.BehaviorTree.Bypass', Bypass); +SetValue(window, 'RexPlugins.BehaviorTree.ForceSuccess', ForceSuccess); +SetValue(window, 'RexPlugins.BehaviorTree.ForceFailure', ForceFailure); +SetValue(window, 'RexPlugins.BehaviorTree.Invert', Invert); +SetValue(window, 'RexPlugins.BehaviorTree.TimeLimit', TimeLimit); +SetValue(window, 'RexPlugins.BehaviorTree.Cooldown', Cooldown); +SetValue(window, 'RexPlugins.BehaviorTree.Repeat', Repeat); +SetValue(window, 'RexPlugins.BehaviorTree.RepeatUntilFailure', RepeatUntilFailure); +SetValue(window, 'RexPlugins.BehaviorTree.RepeatUntilSuccess', RepeatUntilSuccess); +SetValue(window, 'RexPlugins.BehaviorTree.Limiter', Limiter); +SetValue(window, 'RexPlugins.BehaviorTree.If', If); +SetValue(window, 'RexPlugins.BehaviorTree.ContinueIf', ContinueIf); +SetValue(window, 'RexPlugins.BehaviorTree.AbortIf', AbortIf); + +export { + BaseNode, + Composite, + Decorator, + Action, + + Succeeder, + Failer, + Runner, + Error, + Wait, + + Selector, + Sequence, + Parallel, + IfSelector, + SwitchSelector, + WeightSelector, + RandomSelector, + ShuffleSelector, + + Bypass, + ForceSuccess, + ForceFailure, + Invert, + TimeLimit, + Cooldown, + Repeat, + RepeatUntilFailure, + RepeatUntilSuccess, + Limiter, + If, + ContinueIf, + AbortIf, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Service.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Service.js new file mode 100644 index 000000000..fd9f8af6f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/Service.js @@ -0,0 +1,66 @@ +import BaseNode from './BaseNode.js'; +import { SERVICE } from '../constants.js'; + +class Service extends BaseNode { + + constructor( + { + interval = 0, + randomDeviation = 0, + name = 'Service', + title, + properties + } = {} + ) { + + if (properties === undefined) { + properties = {}; + } + + properties.interval = interval; + properties.randomDeviation = randomDeviation; + + super({ + category: SERVICE, + name, + title, + properties, + }); + + this.intervalExpression = this.addExpression(interval); + this.randomDeviationExpression = this.addExpression(randomDeviation); + + } + + _tick(tick) { + if (this.canTick(tick)) { + this.tick(tick); + } + } + + canTick(tick) { + var nodeMemory = this.getNodeMemory(tick); + var currTime = tick.currentTime; + var lastEndTime = nodeMemory.$lastEndTime; + var interval = nodeMemory.$interval; + + var canTick = (lastEndTime === undefined) || + ((currTime - lastEndTime) >= interval); + + if (canTick) { + nodeMemory.$lastEndTime = currTime; + + var interval = tick.evalExpression(this.intervalExpression); + var randomDeviation = tick.evalExpression(this.randomDeviationExpression); + if (randomDeviation > 0) { + interval += (0.5 - Math.random()) * randomDeviation; + } + nodeMemory.$interval = interval; + } + + return canTick; + } + +}; + +export default Service; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Abort.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Abort.js new file mode 100644 index 000000000..ffd2ff90b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Abort.js @@ -0,0 +1,24 @@ +import Action from '../Action.js'; +import { ABORT } from '../../constants.js'; + +class Abort extends Action { + + constructor({ + services, + title, + name = 'Abort', + } = {}) { + + super({ + services, + title, + name, + }); + } + + tick(tick) { + return ABORT; + } +}; + +export default Abort; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Error.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Error.js new file mode 100644 index 000000000..15f22d7bd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Error.js @@ -0,0 +1,24 @@ +import Action from '../Action.js'; +import { ERROR } from '../../constants.js'; + +class Error extends Action { + + constructor({ + services, + title, + name = 'Error', + } = {}) { + + super({ + services, + title, + name, + }); + } + + tick(tick) { + return ERROR; + } +}; + +export default Error; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Failer.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Failer.js new file mode 100644 index 000000000..ccb871bd3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Failer.js @@ -0,0 +1,24 @@ +import Action from '../Action.js'; +import { FAILURE } from '../../constants.js'; + +class Failer extends Action { + + constructor({ + services, + title, + name = 'Failer' + } = {}) { + + super({ + services, + title, + name, + }); + } + + tick(tick) { + return FAILURE; + } +}; + +export default Failer; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Runner.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Runner.js new file mode 100644 index 000000000..89aa81356 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Runner.js @@ -0,0 +1,24 @@ +import Action from '../Action.js'; +import { RUNNING } from '../../constants.js'; + +class Runner extends Action { + + constructor({ + services, + title, + name = 'Runner' + } = {}) { + + super({ + services, + title, + name, + }); + } + + tick(tick) { + return RUNNING; + } +}; + +export default Runner; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Succeeder.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Succeeder.js new file mode 100644 index 000000000..18ff80147 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Succeeder.js @@ -0,0 +1,24 @@ +import Action from '../Action.js'; +import { SUCCESS } from '../../constants.js'; + +class Succeeder extends Action { + + constructor({ + services, + title, + name = 'Succeeder' + } = {}) { + + super({ + services, + title, + name, + }); + } + + tick(tick) { + return SUCCESS; + } +}; + +export default Succeeder; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Wait.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Wait.js new file mode 100644 index 000000000..a7318a7ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/actions/Wait.js @@ -0,0 +1,46 @@ +import Action from '../Action.js'; +import { SUCCESS, RUNNING } from '../../constants.js'; + +class Wait extends Action { + + constructor({ + duration = 0, + services, + title, + name = 'Wait' + } = {}) { + + super({ + title, + name, + properties: { + duration + }, + services, + }); + + this.durationExpression = this.addExpression(duration); + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + + nodeMemory.$startTime = tick.currentTime; + nodeMemory.$duration = tick.evalExpression(this.durationExpression); + } + + tick(tick) { + var nodeMemory = this.getNodeMemory(tick); + var currTime = tick.currentTime; + var startTime = nodeMemory.$startTime; + var duration = nodeMemory.$duration; + + if ((currTime - startTime) < duration) { + return RUNNING; + } + + return SUCCESS; + } +}; + +export default Wait; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/IfSelector.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/IfSelector.js new file mode 100644 index 000000000..114a9af2a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/IfSelector.js @@ -0,0 +1,72 @@ +import Composite from '../Composite.js'; +import { SUCCESS, FAILURE, RUNNING, ERROR, PENDING } from '../../constants.js'; + +class IfSelector extends Composite { + constructor( + { + expression = 'true', + returnPending = false, + children = [], + services, + title, + name = 'IfSelector' + } = {}, + nodePool + ) { + + super( + { + children: children, + services, + title, + name, + properties: { + expression, + returnPending, + }, + }, + nodePool + ); + + this.expression = this.addBooleanExpression(expression); + this.returnPending = returnPending; + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$runningChild = -1; // No running child + } + + tick(tick) { + if (this.children.length === 0) { + return ERROR; + } + + var nodeMemory = this.getNodeMemory(tick); + var childIndex = nodeMemory.$runningChild; + if (childIndex < 0) { + childIndex = tick.evalExpression(this.expression) ? 0 : 1; + if (this.returnPending) { + nodeMemory.$runningChild = childIndex; + return PENDING; + } + } + + var child = this.children[childIndex]; + var status = child._execute(tick); + nodeMemory.$runningChild = (status === RUNNING) ? childIndex : -1; + + return status; + } + + abortChildren(tick) { + var nodeMemory = this.getNodeMemory(tick); + var child = this.children[nodeMemory.$runningChild]; + if (child) { + child._abort(tick); + nodeMemory.$runningChild = -1; + } + } +}; + +export default IfSelector; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/Parallel.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/Parallel.js new file mode 100644 index 000000000..43c9030a7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/Parallel.js @@ -0,0 +1,127 @@ +import Composite from '../Composite.js'; +import { SUCCESS, FAILURE, RUNNING, PENDING, ABORT, ERROR } from '../../constants.js'; +import RemoveItem from '../../../../utils/array/Remove.js'; + +class Parallel extends Composite { + constructor( + { + finishMode = 0, + returnSuccess = true, + children = [], + services, + title, + name = 'Parallel' + } = {}, + nodePool + ) { + + super( + { + children, + services, + title, + name, + properties: { + finishMode, + returnSuccess + }, + }, + nodePool + ); + + this.finishMode = finishMode; + this.returnSuccess = returnSuccess; + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$runningChildren = this.children.map((child, index) => index); + } + + tick(tick) { + if (this.children.length === 0) { + return ERROR; + } + + var nodeMemory = this.getNodeMemory(tick); + var childIndexes = nodeMemory.$runningChildren; + var statusMap = {}; + var hasAnyFinishStatus = false; + var hasAnyPendingStatus = false; + var hasAnyRunningStatus = false; + var hasAnyAbortStatus = false; + var hasAnyErrorStatus = false; + for (var i = 0, cnt = childIndexes.length; i < cnt; i++) { + var childIndex = childIndexes[i]; + var status = this.children[childIndex]._execute(tick); + statusMap[childIndex] = status; + + if (childIndex === 0) { + nodeMemory.$mainTaskStatus = status; + } + + switch (status) { + case SUCCESS: + case FAILURE: + hasAnyFinishStatus = true; + break; + + case RUNNING: + hasAnyRunningStatus = true; + break; + + case PENDING: + hasAnyPendingStatus = true; + break; + + case ABORT: + hasAnyAbortStatus = true; + break; + + case ERROR: + hasAnyErrorStatus = true; + break; + } + } + + // Clear none-running child + if (hasAnyFinishStatus) { + for (var childIndex in statusMap) { + var status = statusMap[childIndex]; + if ((status === SUCCESS) || (status === FAILURE)) { + RemoveItem(childIndexes, parseInt(childIndex)); + } + } + } + + if (this.finishMode === 0) { + return nodeMemory.$mainTaskStatus; + } else { + if (hasAnyErrorStatus) { + return ERROR; + } else if (hasAnyAbortStatus) { + return ABORT; + } else if (hasAnyPendingStatus) { + return PENDING; + } else if (hasAnyRunningStatus) { + return RUNNING; + } else if (this.returnSuccess) { + return SUCCESS; + } else { + return nodeMemory.$mainTaskStatus; + } + } + } + + abortChildren(tick) { + var nodeMemory = this.getNodeMemory(tick); + var childIndexes = nodeMemory.$runningChildren; + for (var i = 0, cnt = childIndexes.length; i < cnt; i++) { + var childIndex = childIndexes[i]; + this.children[childIndex]._abort(tick); + } + nodeMemory.$runningChildren.length = 0; + } +}; + +export default Parallel; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/RandomSelector.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/RandomSelector.js new file mode 100644 index 000000000..22ff2a0a8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/RandomSelector.js @@ -0,0 +1,58 @@ +import Composite from '../Composite.js'; + +class RandomSelector extends Composite { + constructor( + { + children = [], + services, + title, + name = 'RandomSelector' + } = {}, + nodePool + ) { + + super( + { + children, + services, + title, + name, + }, + nodePool + ); + + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$runningChild = -1; // No running child + } + + tick(tick) { + if (this.children.length === 0) { + return ERROR; + } + + var nodeMemory = this.getNodeMemory(tick); + var childIndex = nodeMemory.$runningChild; + if (childIndex < 0) { + childIndex = Math.floor(Math.random() * this.children.length); + } + + var child = this.children[childIndex]; + var status = child._execute(tick); + nodeMemory.$runningChild = (status === RUNNING) ? childIndex : -1; + return status; + } + + abortChildren(tick) { + var nodeMemory = this.getNodeMemory(tick); + var child = this.children[nodeMemory.$runningChild]; + if (child) { + child._abort(tick); + nodeMemory.$runningChild = -1; + } + } +}; + +export default RandomSelector; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/Selector.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/Selector.js new file mode 100644 index 000000000..0c26f7fc3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/Selector.js @@ -0,0 +1,68 @@ +import Composite from '../Composite.js'; +import { SUCCESS, FAILURE, RUNNING, ABORT, ERROR } from '../../constants.js'; + +class Selector extends Composite { + constructor( + { + children = [], + services, + title, + name = 'Selector' + } = {}, + nodePool + ) { + + super( + { + children, + services, + title, + name, + }, + nodePool + ); + + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$runningChild = -1; // No running child + } + + tick(tick) { + if (this.children.length === 0) { + return ERROR; + } + + var nodeMemory = this.getNodeMemory(tick); + var childIndex = nodeMemory.$runningChild; + var status; + if (childIndex < 0) { + for (var i = 0, cnt = this.children.length; i < cnt; i++) { + status = this.children[i]._execute(tick); + + if ((status === RUNNING) || (status === SUCCESS) || (status === ABORT)) { + childIndex = i; + break; + } + } + } else { + var child = this.children[childIndex]; + status = child._execute(tick); + } + + nodeMemory.$runningChild = (status === RUNNING) ? childIndex : -1; + return status; + } + + abortChildren(tick) { + var nodeMemory = this.getNodeMemory(tick); + var child = this.children[nodeMemory.$runningChild]; + if (child) { + child._abort(tick); + nodeMemory.$runningChild = -1; + } + } +}; + +export default Selector; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/Sequence.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/Sequence.js new file mode 100644 index 000000000..da9dbb064 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/Sequence.js @@ -0,0 +1,63 @@ +import Composite from '../Composite.js'; +import { SUCCESS, FAILURE, RUNNING, ABORT, ERROR } from '../../constants.js'; + +class Sequence extends Composite { + constructor( + { + children = [], + services, + title, + name = 'Sequence' + } = {}, + nodePool + ) { + + super( + { + children, + services, + title, + name, + }, + nodePool + ); + + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$runningChild = 0; + } + + tick(tick) { + if (this.children.length === 0) { + return ERROR; + } + + var nodeMemory = this.getNodeMemory(tick); + + var childIndex = nodeMemory.$runningChild; + var status; + for (var i = childIndex, cnt = this.children.length; i < cnt; i++) { + status = this.children[i]._execute(tick); + + if ((status === RUNNING) || (status === FAILURE) || (status === ABORT)) { + break; + } + } + + nodeMemory.$runningChild = (status === RUNNING) ? i : -1; + return status; + } + + abortChildren(tick) { + var nodeMemory = this.getNodeMemory(tick); + var child = this.children[nodeMemory.$runningChild]; + if (child) { + child._abort(tick); + nodeMemory.$runningChild = -1; + } + } +}; + +export default Sequence; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/ShuffleSelector.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/ShuffleSelector.js new file mode 100644 index 000000000..ce8fb46ef --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/ShuffleSelector.js @@ -0,0 +1,70 @@ +import Composite from '../Composite.js'; +import Shuffle from '../../../../utils/array/Shuffle.js'; +import { SUCCESS, FAILURE, RUNNING, ABORT, ERROR } from '../../constants.js'; + +class ShuffleSelector extends Composite { + constructor( + { + children = [], + services, + title, + name = 'ShuffleSelector' + } = {}, + nodePool + ) { + + super( + { + children, + services, + title, + name, + }, + nodePool + ); + + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$runningChild = 0; + + if (!nodeMemory.$children) { + nodeMemory.$children = this.children.map((child, index) => index); + } + Shuffle(nodeMemory.$children); + } + + tick(tick) { + if (this.children.length === 0) { + return ERROR; + } + + var nodeMemory = this.getNodeMemory(tick); + + var childIndex = nodeMemory.$runningChild; + var children = nodeMemory.$children; + var status; + for (var i = childIndex, cnt = children.length; i < cnt; i++) { + status = this.children[children[i]]._execute(tick); + + if ((status === RUNNING) || (status === SUCCESS) || (status === ABORT)) { + break; + } + } + + nodeMemory.$runningChild = (status === RUNNING) ? i : -1; + return status; + } + + abortChildren(tick) { + var nodeMemory = this.getNodeMemory(tick); + var child = this.children[nodeMemory.$runningChild]; + if (child) { + child._abort(tick); + nodeMemory.$runningChild = -1; + } + } +}; + +export default ShuffleSelector; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/SwitchSelector.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/SwitchSelector.js new file mode 100644 index 000000000..0695c9b26 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/SwitchSelector.js @@ -0,0 +1,80 @@ +import Composite from '../Composite.js'; +import { SUCCESS, FAILURE, RUNNING, ERROR } from '../../constants.js'; + +class SwitchSelector extends Composite { + constructor( + { + expression = null, + keys = undefined, // Or [key, ...] + children = {}, // Or [child, ...] + services, + title, + name = 'SwitchSelector' + } = {}, + nodePool + ) { + + if (keys === undefined) { + keys = Object.keys(children); + children = Object.values(children); + } + + super( + { + children: children, + services, + title, + name, + properties: { + expression, + keys + }, + }, + nodePool + ); + + this.expression = this.addExpression(expression); + + this.keys = keys; // Index of children + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$runningChild = -1; // No running child + } + + tick(tick) { + if (this.children.length === 0) { + return ERROR; + } + + var nodeMemory = this.getNodeMemory(tick); + var childIndex = nodeMemory.$runningChild; + if (childIndex < 0) { + var key = tick.evalExpression(this.expression); + childIndex = this.keys.indexOf(key); + if (childIndex === -1) { + childIndex = this.keys.indexOf('default'); + } + if (childIndex === -1) { + return ERROR; + } + } + + var child = this.children[childIndex]; + var status = child._execute(tick); + nodeMemory.$runningChild = (status === RUNNING) ? childIndex : -1; + return status; + } + + abortChildren(tick) { + var nodeMemory = this.getNodeMemory(tick); + var child = this.children[nodeMemory.$runningChild]; + if (child) { + child._abort(tick); + nodeMemory.$runningChild = -1; + } + } +}; + +export default SwitchSelector; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/WeightSelector.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/WeightSelector.js new file mode 100644 index 000000000..e2fb7c0f0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/composites/WeightSelector.js @@ -0,0 +1,98 @@ +import Composite from '../Composite.js'; +import { SUCCESS, FAILURE, RUNNING, ERROR } from '../../constants.js'; +import BaseNode from '../BaseNode.js'; + +class WeightSelector extends Composite { + constructor( + { + expression = null, + weights = undefined, // Or [weight, ...] + children = [], // [node, ...], or [{weight, node}, ...] + services, + title, + name = 'WeightSelector' + } = {}, + nodePool + ) { + + if (weights === undefined) { + weights = []; + + var totalWeight = 0; + for (var i = 0, cnt = children.length; i < cnt; i++) { + var child = children[i]; + var weight; + if ((child instanceof BaseNode) || (typeof (child) === 'string')) { + weight = 1; + } else { + weight = child.weight; + children[i] = child.node; + } + weights.push(weight); + totalWeight += weight; + } + for (var i = 0, cnt = weights.length; i < cnt; i++) { + weights[i] /= totalWeight; + } + } + + super( + { + children: children, + services, + title, + name, + properties: { + expression, + weights + }, + }, + nodePool + ); + + this.expression = (expression) ? this.addExpression(expression) : null; + + this.weights = weights; + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$runningChild = -1; // No running child + } + + tick(tick) { + if (this.children.length === 0) { + return ERROR; + } + + var nodeMemory = this.getNodeMemory(tick); + var childIndex = nodeMemory.$runningChild; + if (childIndex < 0) { + var value = (this.expression) ? tick.evalExpression(this.expression) : Math.random(); + // console.log(value); + for (var i = 0, cnt = this.weights.length; i < cnt; i++) { + value -= this.weights[i]; + if (value < 0) { + childIndex = i; + break; + } + } + } + + var child = this.children[childIndex]; + var status = child._execute(tick); + nodeMemory.$runningChild = (status === RUNNING) ? childIndex : -1; + return status; + } + + abortChildren(tick) { + var nodeMemory = this.getNodeMemory(tick); + var child = this.children[nodeMemory.$runningChild]; + if (child) { + child._abort(tick); + nodeMemory.$runningChild = -1; + } + } +}; + +export default WeightSelector; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/AbortIf.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/AbortIf.js new file mode 100644 index 000000000..471e8fcee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/AbortIf.js @@ -0,0 +1,54 @@ +import Decorator from '../Decorator.js'; +import { FAILURE, SUCCESS, ERROR } from '../../constants.js'; + + +class AbortIf extends Decorator { + + constructor( + { + expression = 'true', + returnSuccess = true, + child = null, + title, + name = 'AbortIf' + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + properties: { + expression, + returnSuccess, + }, + }, + nodePool + ); + + this.expression = this.addBooleanExpression(expression); + this.returnSuccess = returnSuccess; + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + // child is running + if (this.isChildRunning(tick)) { + // Abort child if eval result is true + if (tick.evalExpression(this.expression)) { + return (this.returnSuccess) ? SUCCESS : FAILURE; + } + } + + var status = this.child._execute(tick); + + return status; + } +}; + +export default AbortIf; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Bypass.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Bypass.js new file mode 100644 index 000000000..7e583a287 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Bypass.js @@ -0,0 +1,38 @@ +import Decorator from '../Decorator.js'; +import { FAILURE, SUCCESS, ERROR } from '../../constants.js'; + + +class Bypass extends Decorator { + + constructor( + { + child = null, + title, + name = 'Bypass' + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + }, + nodePool + ); + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + // Won't abort child + var status = this.child._execute(tick); + + return status; + } +}; + +export default Bypass; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/ContinueIf.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/ContinueIf.js new file mode 100644 index 000000000..059fa77f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/ContinueIf.js @@ -0,0 +1,54 @@ +import Decorator from '../Decorator.js'; +import { FAILURE, SUCCESS, ERROR } from '../../constants.js'; + + +class ContinueIf extends Decorator { + + constructor( + { + expression = 'true', + returnSuccess = true, + child = null, + title, + name = 'ContinueIf' + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + properties: { + expression, + returnSuccess, + }, + }, + nodePool + ); + + this.expression = this.addBooleanExpression(expression); + this.returnSuccess = returnSuccess; + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + // child is running + if (this.isChildRunning(tick)) { + // Abort child if eval result is false + if (!tick.evalExpression(this.expression)) { + return (this.returnSuccess) ? SUCCESS : FAILURE; + } + } + + var status = this.child._execute(tick); + + return status; + } +}; + +export default ContinueIf; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Cooldown.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Cooldown.js new file mode 100644 index 000000000..23b585d6a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Cooldown.js @@ -0,0 +1,64 @@ +import Decorator from '../Decorator.js'; +import { SUCCESS, FAILURE, RUNNING, ABORT, ERROR } from '../../constants.js'; + +class Cooldown extends Decorator { + constructor( + { + duration = 0, + child = null, + title, + name = 'Cooldown' + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + properties: { + duration + }, + }, + nodePool + ); + + this.durationExpression = this.addExpression(duration); + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$cooldownTime = tick.evalExpression(this.durationExpression); + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + // Won't abort child + var nodeMemory = this.getNodeMemory(tick); + var currTime = tick.currentTime; + var lastEndTime = nodeMemory.$lastEndTime; + var cooldownTime = nodeMemory.$cooldownTime; + + // Open child after cooldown timeout + if ( + (lastEndTime !== undefined) && + ((currTime - lastEndTime) < cooldownTime) + ) { + return FAILURE; + } + + var status = this.child._execute(tick); + + if ((status === SUCCESS) || (status === FAILURE) || (status === ABORT)) { + nodeMemory.$lastEndTime = currTime; + } + + return status; + } +}; + +export default Cooldown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/ForceFailure.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/ForceFailure.js new file mode 100644 index 000000000..30402a8df --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/ForceFailure.js @@ -0,0 +1,44 @@ +import Decorator from '../Decorator.js'; +import { FAILURE, SUCCESS, RUNNING, ABORT, ERROR } from '../../constants.js'; + + +class ForceFailure extends Decorator { + + constructor( + { + child = null, + title, + name = 'ForceFailure' + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + properties: { + }, + }, + nodePool + ); + + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + var status = this.child._execute(tick); + + if (status === SUCCESS) { + return FAILURE; + } + + return status; + } +}; + +export default ForceFailure; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/ForceSuccess.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/ForceSuccess.js new file mode 100644 index 000000000..97cb6ccd5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/ForceSuccess.js @@ -0,0 +1,43 @@ +import Decorator from '../Decorator.js'; +import { RUNNING, FAILURE, SUCCESS, ABORT, ERROR } from '../../constants.js'; + + +class ForceSuccess extends Decorator { + + constructor( + { + child = null, + title, + name = 'ForceSuccess' + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + properties: { + }, + }, + nodePool + ); + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + var status = this.child._execute(tick); + + if (status === FAILURE) { + return SUCCESS; + } + + return status; + } +}; + +export default ForceSuccess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/If.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/If.js new file mode 100644 index 000000000..e8f3a8403 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/If.js @@ -0,0 +1,57 @@ +import Decorator from '../Decorator.js'; +import { FAILURE, SUCCESS, ERROR, PENDING } from '../../constants.js'; + + +class If extends Decorator { + + constructor( + { + expression = 'true', + returnPending = false, + child = null, + title, + name = 'If' + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + properties: { + expression, + returnPending + }, + }, + nodePool + ); + + this.expression = this.addBooleanExpression(expression); + this.returnPending = returnPending; + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + // child is not running + if (!this.isChildRunning(tick)) { + // Return FAILURE to run next node + if (!tick.evalExpression(this.expression)) { + return FAILURE; + } else if (this.returnPending) { + this.openChild(); // Open child but not run it now + return PENDING; + } + } + + var status = this.child._execute(tick); + + return status; + } +}; + +export default If; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Invert.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Invert.js new file mode 100644 index 000000000..d17061a45 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Invert.js @@ -0,0 +1,42 @@ +import Decorator from '../Decorator.js'; +import { FAILURE, SUCCESS, ERROR } from '../../constants.js'; + + +class Invert extends Decorator { + constructor( + { + child = null, + title, + name = 'Invert' + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + }, + nodePool + ); + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + var status = this.child._execute(tick); + + if (status === SUCCESS) { + status = FAILURE; + } else if (status === FAILURE) { + status = SUCCESS; + } + + return status; + } +}; + +export default Invert; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Limiter.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Limiter.js new file mode 100644 index 000000000..221ae3413 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Limiter.js @@ -0,0 +1,62 @@ +import Decorator from '../Decorator.js'; +import { FAILURE, SUCCESS, ERROR } from '../../constants.js'; + +class Limiter extends Decorator { + + constructor( + { + maxLoop = 1, + child = null, + title, + name = 'Limiter' + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + properties: { + maxLoop + }, + }, + nodePool + ); + + this.maxLoopExpression = this.addExpression(maxLoop); + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$maxLoop = tick.evalExpression(this.maxLoopExpression); + nodeMemory.$i = 0; + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + // Won't abort child + var nodeMemory = this.getNodeMemory(tick); + var maxLoop = nodeMemory.$maxLoop; + var i = nodeMemory.$i; + + // Open child before exceed maxLoop + // Execute child 1 time in a tick + if (i >= maxLoop) { + return FAILURE; + } + + var status = this.child._execute(tick); + if ((status === SUCCESS) || (status === FAILURE)) { + nodeMemory.$i = i + 1; + } + + return status; + } +}; + +export default Limiter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Repeat.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Repeat.js new file mode 100644 index 000000000..7c19c2456 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/Repeat.js @@ -0,0 +1,64 @@ +import Decorator from '../Decorator.js'; +import { SUCCESS, ERROR, FAILURE, RUNNING } from '../../constants.js'; + +class Repeat extends Decorator { + + constructor( + { + maxLoop = -1, + child = null, + title, + name = 'Repeat' + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + properties: { + maxLoop + }, + }, + nodePool + ); + + this.maxLoopExpression = this.addExpression(maxLoop); + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$maxLoop = tick.evalExpression(this.maxLoopExpression); + nodeMemory.$i = 0; + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + var nodeMemory = this.getNodeMemory(tick); + var maxLoop = nodeMemory.$maxLoop; + var i = nodeMemory.$i; + var status = SUCCESS; + + // Open child before exceed maxLoop + // Execute child many times in a tick + while (maxLoop < 0 || i < maxLoop) { + status = this.child._execute(tick); + + if ((status === SUCCESS) || (status === FAILURE)) { + i++; + } else { + break; + } + } + + nodeMemory.$i = i; + return status; + } +}; + +export default Repeat; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/RepeatUntilFailure.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/RepeatUntilFailure.js new file mode 100644 index 000000000..f06ac1605 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/RepeatUntilFailure.js @@ -0,0 +1,73 @@ +import Decorator from '../Decorator.js'; +import { SUCCESS, FAILURE, ERROR } from '../../constants.js'; + +class RepeatUntilFailure extends Decorator { + + constructor( + { + maxLoop = -1, + returnSuccess = false, + child = null, + title, + name = 'RepeatUntilFailure', + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + properties: { + returnSuccess, + maxLoop + }, + }, + nodePool + ); + + this.maxLoopExpression = this.addExpression(maxLoop); + this.returnSuccess = returnSuccess; + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$maxLoop = tick.evalExpression(this.maxLoopExpression); + nodeMemory.$i = 0; + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + // Won't abort child + var nodeMemory = this.getNodeMemory(tick); + var maxLoop = nodeMemory.$maxLoop; + var i = nodeMemory.$i; + var status = ERROR; + + // Open child before exceed maxLoop + // Execute child many times in a tick + while ((maxLoop < 0) || (i < maxLoop)) { + status = this.child._execute(tick); + + if (status === SUCCESS) { + i++; + } else { + break; + } + } + + nodeMemory.$i = i; + + if ((status === this.FAILURE) && this.returnSuccess) { + status = SUCCESS; + } + + return status; + } +}; + +export default RepeatUntilFailure; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/RepeatUntilSuccess.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/RepeatUntilSuccess.js new file mode 100644 index 000000000..e7aebadbf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/RepeatUntilSuccess.js @@ -0,0 +1,65 @@ +import Decorator from '../Decorator.js'; +import { SUCCESS, ERROR, FAILURE } from '../../constants.js'; + +class RepeatUntilSuccess extends Decorator { + + constructor( + { + maxLoop = -1, + child = null, + title, + name = 'RepeatUntilSuccess' + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + properties: { + maxLoop + }, + }, + nodePool + ); + + this.maxLoopExpression = this.addExpression(maxLoop); + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$maxLoop = tick.evalExpression(this.maxLoopExpression); + nodeMemory.$i = 0; + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + // Won't abort child + var nodeMemory = this.getNodeMemory(tick); + var maxLoop = nodeMemory.$maxLoop; + var i = nodeMemory.$i; + var status = ERROR; + + // Open child before exceed maxLoop + // Execute child many times in a tick + while (maxLoop < 0 || i < maxLoop) { + status = this.child._execute(tick); + + if (status === FAILURE) { + i++; + } else { + break; + } + } + + nodeMemory.$i = i; + return status; + } +}; + +export default RepeatUntilSuccess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/TimeLimit.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/TimeLimit.js new file mode 100644 index 000000000..87afa2c02 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/decorators/TimeLimit.js @@ -0,0 +1,60 @@ +import Decorator from '../Decorator.js'; +import { FAILURE, ERROR } from '../../constants.js'; + +class TimeLimit extends Decorator { + constructor( + { + duration = 0, + returnSuccess = true, + child = null, + title, + name = 'TimeLimit' + } = {}, + nodePool + ) { + + super( + { + child, + title, + name, + properties: { + duration, + returnSuccess + }, + }, + nodePool + ); + + this.durationExpression = this.addExpression(duration); + this.returnSuccess = returnSuccess; + } + + open(tick) { + var nodeMemory = this.getNodeMemory(tick); + nodeMemory.$startTime = tick.currentTime; + nodeMemory.$duration = tick.evalExpression(this.durationExpression); + } + + tick(tick) { + if (!this.child) { + return ERROR; + } + + // Abort child when timeout + var nodeMemory = this.getNodeMemory(tick); + var currTime = tick.currentTime; + var startTime = nodeMemory.$startTime; + var duration = nodeMemory.$duration; + + if ((currTime - startTime) >= duration) { + return (this.returnSuccess) ? SUCCESS : FAILURE; + } + + var status = this.child._execute(tick); + + return status; + } +}; + +export default TimeLimit; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/BaseExpression.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/BaseExpression.js new file mode 100644 index 000000000..1420a6cdc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/BaseExpression.js @@ -0,0 +1,12 @@ +class BaseExpression { + setExpressionHandler(callback) { + this.expressionHandler = callback; + return this; + } + + eval(context) { + return this.expressionHandler(context); + } +} + +export default BaseExpression; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/BooleanExpression.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/BooleanExpression.js new file mode 100644 index 000000000..49802aff0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/BooleanExpression.js @@ -0,0 +1,9 @@ +import Expression from './Expression.js'; + +class BooleanExpression extends Expression { + eval(context) { + return !!super.eval(context); + } +} + +export default BooleanExpression; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/Expression.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/Expression.js new file mode 100644 index 000000000..b9246ff7e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/Expression.js @@ -0,0 +1,21 @@ +import BaseExpression from './BaseExpression.js'; +import Compile from '../../../../math/expressionparser/utils/Complile.js'; + +class Expression extends BaseExpression { + constructor(expression) { + super(); + + var callback; + if (typeof (expression) === 'number') { + callback = function () { + return expression; + } + } else { + callback = Compile(expression); + } + + this.setExpressionHandler(callback); + } +} + +export default Expression; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/StringTemplateExpression.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/StringTemplateExpression.js new file mode 100644 index 000000000..f1c126fcf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/StringTemplateExpression.js @@ -0,0 +1,13 @@ +import BaseExpression from './BaseExpression.js'; +import Compile from '../../../../string/stringtemplate/utils/Complile.js'; + +class StringTemplateExpression extends BaseExpression { + constructor(expression) { + super(); + + var callback = Compile(expression); + this.setExpressionHandler(callback); + } +} + +export default StringTemplateExpression; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/index.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/index.js new file mode 100644 index 000000000..95ed5227f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/expressions/index.js @@ -0,0 +1,11 @@ +import BaseExpression from './BaseExpression.js'; +import Expression from './Expression.js'; +import BooleanExpression from './BooleanExpression.js'; +import StringTemplateExpression from './StringTemplateExpression.js'; + +export { + BaseExpression, + Expression, + BooleanExpression, + StringTemplateExpression +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/index.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/index.js new file mode 100644 index 000000000..411ffd31d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/nodes/index.js @@ -0,0 +1,73 @@ +import BaseNode from './BaseNode.js'; +import Action from './Action.js'; +import Composite from './Composite.js'; +import Decorator from './Decorator.js'; +import Service from './Service.js'; + +import Succeeder from './actions/Succeeder.js'; +import Failer from './actions/Failer.js'; +import Runner from './actions/Runner.js'; +import Error from './actions/Error.js'; +import Wait from './actions/Wait.js'; +import Abort from './actions/Abort.js'; + +import Selector from './composites/Selector.js'; +import Sequence from './composites/Sequence.js'; +import Parallel from './composites/Parallel.js'; +import IfSelector from './composites/IfSelector.js'; +import SwitchSelector from './composites/SwitchSelector.js'; +import WeightSelector from './composites/WeightSelector.js'; +import RandomSelector from './composites/RandomSelector.js'; +import ShuffleSelector from './composites/ShuffleSelector.js'; + +import Bypass from './decorators/Bypass.js'; +import ForceSuccess from './decorators/ForceSuccess.js'; +import ForceFailure from './decorators/ForceFailure.js'; +import Invert from './decorators/Invert.js'; +import TimeLimit from './decorators/TimeLimit.js'; +import Cooldown from './decorators/Cooldown.js'; +import Repeat from './decorators/Repeat.js'; +import RepeatUntilFailure from './decorators/RepeatUntilFailure.js'; +import RepeatUntilSuccess from './decorators/RepeatUntilSuccess.js'; +import Limiter from './decorators/Limiter.js'; +import If from './decorators/If.js'; +import ContinueIf from './decorators/ContinueIf.js'; +import AbortIf from './decorators/AbortIf.js'; + +export { + BaseNode, + Action, + Composite, + Decorator, + Service, + + Succeeder, + Failer, + Runner, + Error, + Wait, + Abort, + + Selector, + Sequence, + Parallel, + IfSelector, + SwitchSelector, + WeightSelector, + RandomSelector, + ShuffleSelector, + + Bypass, + ForceSuccess, + ForceFailure, + Invert, + TimeLimit, + Cooldown, + Repeat, + RepeatUntilFailure, + RepeatUntilSuccess, + Limiter, + If, + ContinueIf, + AbortIf, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/CreateNode.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/CreateNode.js new file mode 100644 index 000000000..d2fb73b21 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/CreateNode.js @@ -0,0 +1,117 @@ +import IsPlainObject from '../../../../utils/object/IsPlainObject.js'; +import { + CreateCompositeHandlers, + CreateActionHandlers, + CreateDecoratorHandles +} from './Handlers.js'; + + +var CreateNode = function (data, customNodeHandlers) { + // SingleValue : data is not an object + var handlerName = data.__handlerName__, + isSingleValue = data.__isSingleValue__; + var children, conditions, services; + if (isSingleValue) { + // Get origin data + data = data[handlerName]; + } else { + children = data.children; + conditions = data.conditions; + services = data.services; + + delete data.conditions; + } + + // 1. Replace node data of child to node instance of child + if (children) { + if (Array.isArray(children)) { + // Children is an array + for (var i = 0, cnt = children.length; i < cnt; i++) { + var childObj = children[i]; + if (childObj.node) { + // childObj.node is a node data + childObj.node = CreateChildNode(childObj.node, customNodeHandlers); + } else { + // childObj is a node data + children[i] = CreateChildNode(childObj, customNodeHandlers); + } + + } + + } else { + // Children is a dictionary + for (var key in children) { + var childObj = children[key]; + if (childObj.node) { + // childObj.node is a node data + childObj.node = CreateChildNode(childObj.node, customNodeHandlers); + } else { + // childObj is a node data + children[key] = CreateChildNode(childObj, customNodeHandlers); + } + + } + + } + } + + // 2. Replace node data of service to node instance of services + if (services) { + // services is an array + for (var i = 0, cnt = services.length; i < cnt; i++) { + services[i] = CreateChildNode(services[i], customNodeHandlers); + } + } + + // 3. Create (composite/action) node instance + var handler; + if (handlerName in CreateCompositeHandlers) { + handler = CreateCompositeHandlers[handlerName]; + } else if (handlerName in CreateActionHandlers) { + handler = CreateActionHandlers[handlerName]; + } else if (handlerName in customNodeHandlers) { + handler = customNodeHandlers[handlerName]; + } else { + throw `Can't create '${handlerName}' composite/action/service node` + } + var retNode = handler(data); + + // 4. Create decorator instance + if (conditions) { + var handlerNames = Object.keys(conditions); + // Create conditions from last to first + for (var i = handlerNames.length - 1; i >= 0; i--) { + var handlerName = handlerNames[i]; + var handler; + if (handlerName in CreateDecoratorHandles) { + handler = CreateDecoratorHandles[handlerName]; + } else if (handlerName in customNodeHandlers) { + handler = customNodeHandlers[handlerName]; + } else { + throw `Can't create '${handlerName}' decorator node` + } + retNode = handler(conditions[handlerName], retNode); + } + } + + return retNode; +} + +var CreateChildNode = function (childObj, customNodeHandlers) { + var childData, key; + // childData is at first key of childObj + for (key in childObj) { + childData = childObj[key]; + break; + } + if (!IsPlainObject(childData)) { + // childData is a single value, wrap to an object + childData = childObj; + childData.__isSingleValue__ = true; + } + + childData.__handlerName__ = key; + return CreateNode(childData, customNodeHandlers); +} + +export default CreateNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/Factory.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/Factory.js new file mode 100644 index 000000000..b72071b79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/Factory.js @@ -0,0 +1,14 @@ +import LoadYaml from './LoadYaml.js'; + +import ObjectFactory from '../../ObjectFactory.js'; +import SetValue from '../../../../utils/object/SetValue.js'; + +ObjectFactory.register('yaml', function (yamlString, customNodeHandlers) { + return LoadYaml(yamlString, customNodeHandlers); +}); + +SetValue(window, 'RexPlugins.BehaviorTree.LoadYaml', LoadYaml); + +export { + LoadYaml, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/Handlers.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/Handlers.js new file mode 100644 index 000000000..f3f7d3810 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/Handlers.js @@ -0,0 +1,58 @@ +// Composites +import CreateSelectorNode from './composites/Selector.js'; +import CreateSequenceNode from './composites/Sequence.js'; +import CreateParallelNode from './composites/Parallel.js'; +import CreateSwitchSelectorNode from './composites/SwitchSelector.js'; +import CreateIfSelectorNode from './composites/IfSelector.js'; +import CreateRandomSelectorNode from './composites/RandomSelector.js'; +import CreateShuffleNode from './composites/ShuffleSelector.js'; +import CreateWeightSelectorNode from './composites/WeightSelector.js'; +// Actions +import CreateWaitNode from './actions/Wait.js'; +// Decorators +import CreateRepeatNode from './decorators/Repeat.js'; +import CreateRepeatUntilFailureNode from './decorators/RepeatUntilFailure.js'; +import CreateRepeatUntilSuccessNode from './decorators/RepeatUntilSuccess.js'; +import CreateIfNode from './decorators/If.js'; +import CreateContinueIfNode from './decorators/ContinueIf.js'; +import CreateAbortIfNode from './decorators/AbortIf.js'; +import CreateCooldownNode from './decorators/Cooldown.js'; +import CreateTimeLimitNode from './decorators/TimeLimit.js'; +import CreateInvertNode from './decorators/Invert.js'; +import CreateForceSuccessNode from './decorators/ForceSuccess.js'; +import CreateForceFailureNode from './decorators/ForceFailure.js'; + +const CreateCompositeHandlers = { + 'selector': CreateSelectorNode, + 'sequence': CreateSequenceNode, + 'parallel': CreateParallelNode, + 'switch-selector': CreateSwitchSelectorNode, + 'if-selector': CreateIfSelectorNode, + 'random-selector': CreateRandomSelectorNode, + 'shuffle-selector': CreateShuffleNode, + 'weight-selector': CreateWeightSelectorNode, +} + +const CreateActionHandlers = { + 'wait': CreateWaitNode +} + +const CreateDecoratorHandles = { + 'repeat': CreateRepeatNode, + 'rpeeat-until-false': CreateRepeatUntilFailureNode, + 'repeat-until-true': CreateRepeatUntilSuccessNode, + 'if': CreateIfNode, + 'continue-if': CreateContinueIfNode, + 'abort-if': CreateAbortIfNode, + 'cooldown': CreateCooldownNode, + 'time-limit': CreateTimeLimitNode, + 'invert': CreateInvertNode, + 'force-true': CreateForceSuccessNode, + 'force-false': CreateForceFailureNode, +}; + +export { + CreateCompositeHandlers, + CreateActionHandlers, + CreateDecoratorHandles +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/LoadYaml.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/LoadYaml.js new file mode 100644 index 000000000..be72039b8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/LoadYaml.js @@ -0,0 +1,23 @@ +import yaml from 'js-yaml'; +import CreateNode from './CreateNode.js'; + +var LoadYaml = function (yamlString, customNodeHandlers) { + if (customNodeHandlers === undefined) { + customNodeHandlers = {}; + } + + var obj; + try { + obj = yaml.load(yamlString); + } catch (e) { + console.log(e); + } + + for (var key in obj) { + var data = obj[key]; + data.__handlerName__ = key; + return CreateNode(data, customNodeHandlers); + } +} + +export default LoadYaml; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/actions/Wait.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/actions/Wait.js new file mode 100644 index 000000000..fc86e6d9c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/actions/Wait.js @@ -0,0 +1,16 @@ +import { Wait } from '../../../nodes'; +import IsPlainObject from '../../../../../utils/object/IsPlainObject.js'; + +/* +sequence: + - wait: 1000 + # - wait: {duration:1000} +*/ + +var CreateWaitNode = function (data) { + return new Wait({ + duration: (IsPlainObject(data)) ? data.duration : data, + }) +} + +export default CreateWaitNode \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/IfSelector.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/IfSelector.js new file mode 100644 index 000000000..f6c2cc224 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/IfSelector.js @@ -0,0 +1,17 @@ +import { IfSelector } from '../../../nodes'; + +/* +```yaml +if-selector: + expression: A > 10 + children: + - seqence + - seqence +``` +*/ + +var CreateIfSelectorNode = function (data) { + return new IfSelector(data); +} + +export default CreateIfSelectorNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/Parallel.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/Parallel.js new file mode 100644 index 000000000..d682ab356 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/Parallel.js @@ -0,0 +1,17 @@ +import { Parallel } from '../../../nodes'; + +/* +```yaml +parallel: + finishMode: 0 + children: + - sequence + - sequence +``` +*/ + +var CreateParallelNode = function (data) { + return new Parallel(data); +} + +export default CreateParallelNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/RandomSelector.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/RandomSelector.js new file mode 100644 index 000000000..b65768487 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/RandomSelector.js @@ -0,0 +1,16 @@ +import { RandomSelector } from '../../../nodes'; + +/* +```yaml +random-selector: + children: + - sequence + - sequence +``` +*/ + +var CreateRandomSelectorNode = function (data) { + return new RandomSelector(data); +} + +export default CreateRandomSelectorNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/Selector.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/Selector.js new file mode 100644 index 000000000..44045cd90 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/Selector.js @@ -0,0 +1,16 @@ +import { Selector } from '../../../nodes'; + +/* +```yaml +selector: + children: + - sequence + - sequence +``` +*/ + +var CreateSelectorNode = function (data) { + return new Selector(data); +} + +export default CreateSelectorNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/Sequence.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/Sequence.js new file mode 100644 index 000000000..075abf5ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/Sequence.js @@ -0,0 +1,16 @@ +import { Sequence } from '../../../nodes'; + +/* +```yaml +sequence: + children: + - sequence + - sequence +``` +*/ + +var CreateSequenceNode = function (data) { + return new Sequence(data); +} + +export default CreateSequenceNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/ShuffleSelector.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/ShuffleSelector.js new file mode 100644 index 000000000..55019d883 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/ShuffleSelector.js @@ -0,0 +1,16 @@ +import { ShuffleSelector } from '../../../nodes'; + +/* +```yaml +shuffle-selector: + children: + - sequence + - sequence +``` +*/ + +var CreateShuffleSelectorNode = function (data) { + return new ShuffleSelector(data); +} + +export default CreateShuffleSelectorNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/SwitchSelector.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/SwitchSelector.js new file mode 100644 index 000000000..e5c0be4ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/SwitchSelector.js @@ -0,0 +1,17 @@ +import { SwitchSelector } from '../../../nodes'; + +/* +```yaml +switch-selector: + expression: key + children: + A: seqence + B: seqence +``` +*/ + +var CreateSwitchSelectorNode = function (data) { + return new SwitchSelector(data); +} + +export default CreateSwitchSelectorNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/WeightSelector.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/WeightSelector.js new file mode 100644 index 000000000..79a056e29 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/composites/WeightSelector.js @@ -0,0 +1,19 @@ +import { WeightSelector } from '../../../nodes'; + +/* +```yaml +weight-selector: + children: + - + weight: 2 + node: + sequence + - seqence +``` +*/ + +var CreateWeightSelectorNode = function (data) { + return new WeightSelector(data); +} + +export default CreateWeightSelectorNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/AbortIf.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/AbortIf.js new file mode 100644 index 000000000..417776406 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/AbortIf.js @@ -0,0 +1,24 @@ +import { AbortIf } from '../../../nodes'; +import IsPlainObject from '../../../../../utils/object/IsPlainObject.js'; + +/* +```yaml +conditions: + abort-if: A > 10 + # abort-if: {expression:'A > 10', returnSuccess:true} +``` +*/ + +var CreateAbortIfNode = function (data, child) { + if (IsPlainObject(data)) { + data.child = child; + } else { + data = { + expression: data, + child: child + } + } + return new AbortIf(data); +} + +export default CreateAbortIfNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/ContinueIf.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/ContinueIf.js new file mode 100644 index 000000000..ad729444c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/ContinueIf.js @@ -0,0 +1,24 @@ +import { ContinueIf } from '../../../nodes'; +import IsPlainObject from '../../../../../utils/object/IsPlainObject.js'; + +/* +```yaml +conditions: + continue-if: A > 10 + # abort-if: {expression:'A > 10', returnSuccess:true} +``` +*/ + +var CreateContinueIfNode = function (data, child) { + if (IsPlainObject(data)) { + data.child = child; + } else { + data = { + expression: data, + child: child + } + } + return new ContinueIf(data); +} + +export default CreateContinueIfNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/Cooldown.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/Cooldown.js new file mode 100644 index 000000000..edc2addd8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/Cooldown.js @@ -0,0 +1,24 @@ +import { Cooldown } from '../../../nodes'; +import IsPlainObject from '../../../../../utils/object/IsPlainObject.js'; + +/* +```yaml +conditions: + cooldown: 1000 + # cooldown: {duration:1000} +``` +*/ + +var CreateCooldownNode = function (data, child) { + if (IsPlainObject(data)) { + data.child = child; + } else { + data = { + duration: data, + child: child + } + } + return new Cooldown(data); +} + +export default CreateCooldownNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/ForceFailure.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/ForceFailure.js new file mode 100644 index 000000000..d243fe73d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/ForceFailure.js @@ -0,0 +1,21 @@ +import { ForceFailure } from '../../../nodes'; + +/* +```yaml +conditions: + force-false: +``` +*/ + +var CreateForceFailureNode = function (data, child) { + if (IsPlainObject(data)) { + data.child = child; + } else { + data = { + child: child + } + } + return new ForceFailure(data); +} + +export default CreateForceFailureNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/ForceSuccess.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/ForceSuccess.js new file mode 100644 index 000000000..d32542714 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/ForceSuccess.js @@ -0,0 +1,21 @@ +import { ForceSuccess } from '../../../nodes'; + +/* +```yaml +conditions: + force-true: +``` +*/ + +var CreateForceSuccessNode = function (data, child) { + if (IsPlainObject(data)) { + data.child = child; + } else { + data = { + child: child + } + } + return new ForceSuccess(data); +} + +export default CreateForceSuccessNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/If.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/If.js new file mode 100644 index 000000000..6adafbd17 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/If.js @@ -0,0 +1,24 @@ +import { If } from '../../../nodes'; +import IsPlainObject from '../../../../../utils/object/IsPlainObject.js'; + +/* +```yaml +conditions: + if: A > 10 + # if: {expression:'A > 10'} +``` +*/ + +var CreateIfNode = function (data, child) { + if (IsPlainObject(data)) { + data.child = child; + } else { + data = { + expression: data, + child: child + } + } + return new If(data); +} + +export default CreateIfNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/Invert.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/Invert.js new file mode 100644 index 000000000..dbbe4f312 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/Invert.js @@ -0,0 +1,21 @@ +import { Invert } from '../../../nodes'; + +/* +```yaml +conditions: + invert: +``` +*/ + +var CreateInvertNode = function (data, child) { + if (IsPlainObject(data)) { + data.child = child; + } else { + data = { + child: child + } + } + return new Invert(data); +} + +export default CreateInvertNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/Repeat.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/Repeat.js new file mode 100644 index 000000000..0c39a1103 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/Repeat.js @@ -0,0 +1,25 @@ +import { Repeat } from '../../../nodes'; +import IsPlainObject from '../../../../../utils/object/IsPlainObject.js'; + +/* +```yaml +conditions: + repeat: + # repeat: 3 + # repeat: {maxLoop:3} +``` +*/ + +var CreateRepeatNode = function (data, child) { + if (IsPlainObject(data)) { + data.child = child; + } else { + data = { + maxLoop: data, + child: child + } + } + return new Repeat(data); +} + +export default CreateRepeatNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/RepeatUntilFailure.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/RepeatUntilFailure.js new file mode 100644 index 000000000..e40931675 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/RepeatUntilFailure.js @@ -0,0 +1,25 @@ +import { RepeatUntilFailure } from '../../../nodes'; +import IsPlainObject from '../../../../../utils/object/IsPlainObject.js'; + +/* +```yaml +conditions: + repeat-until-false: + # repeat-until-false: 3 + # repeat-until-false: {maxLoop:3} +``` +*/ + +var CreateRepeatUntilFailureNode = function (data, child) { + if (IsPlainObject(data)) { + data.child = child; + } else { + data = { + maxLoop: data, + child: child + } + } + return new RepeatUntilFailure(data); +} + +export default CreateRepeatUntilFailureNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/RepeatUntilSuccess.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/RepeatUntilSuccess.js new file mode 100644 index 000000000..605da94fa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/RepeatUntilSuccess.js @@ -0,0 +1,25 @@ +import { RepeatUntilSuccess } from '../../../nodes'; +import IsPlainObject from '../../../../../utils/object/IsPlainObject.js'; + +/* +```yaml +conditions: + repeat-until-true: + # repeat-until-true: 3 + # repeat-until-true: {maxLoop:3} +``` +*/ + +var CreateRepeatUntilSuccessNode = function (data, child) { + if (IsPlainObject(data)) { + data.child = child; + } else { + data = { + maxLoop: data, + child: child + } + } + return new RepeatUntilSuccess(data) +} + +export default CreateRepeatUntilSuccessNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/TimeLimit.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/TimeLimit.js new file mode 100644 index 000000000..65787d0fa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/parsers/yaml/decorators/TimeLimit.js @@ -0,0 +1,24 @@ +import { TimeLimit } from '../../../nodes'; +import IsPlainObject from '../../../../../utils/object/IsPlainObject.js'; + +/* +```yaml +conditions: + time-limit: 1000 + # time-limit: {duration:1000, returnSuccess:true} +``` +*/ + +var CreateTimeLimitNode = function (data, child) { + if (IsPlainObject(data)) { + data.child = child; + } else { + data = { + duration: data, + child: child + } + } + return new TimeLimit(data); +} + +export default CreateTimeLimitNode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/tick/Tick.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/tick/Tick.js new file mode 100644 index 000000000..5813fb2e9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/tick/Tick.js @@ -0,0 +1,95 @@ +import RemoveItem from "../../../utils/array/Remove.js"; +import { CURRENT_TIME } from '../constants.js' + +class Tick { + + constructor() { + // set by BehaviorTree + + this.tree = null; + + this.blackboard = null; + + this.target = null; + + // updated during the tick signal + + this._openNodes = []; // Open nodes of current tick + + this._nodeCount = 0; + + this._currentNode = null; + } + + // Set members + setTree(tree) { + this.tree = tree; + return this; + } + + setBlackBoard(blackboard) { + this.blackboard = blackboard; + return this; + } + + setTarget(target) { + this.target = target; + return this; + } + + reset() { + this._openNodes.length = 0; + this._nodeCount = 0; + return this; + } + + getGlobalMemory() { + return this.blackboard.getGlobalMemory(); + } + + getTreeMemory() { + return this.blackboard.getTreeMemory(this.tree.id); + } + + getNodeMemory(nodeID) { + return this.blackboard.getNodeMemory(this.tree.id, nodeID); + } + + get currentTime() { + if (this.blackboard.has(CURRENT_TIME)) { + // Inject current-time through blackboard + return this.blackboard.get(CURRENT_TIME); + } else { + return (new Date()).getTime(); + } + } + + evalExpression(expression) { + return expression.eval(this.blackboard.getGlobalMemory()); + } + + _enterNode(node) { + this._nodeCount++; + this._openNodes.push(node); + this._currentNode = node; + } + + _openNode(node) { + this._currentNode = node; + } + + _tickNode(node) { + this._currentNode = node; + } + + _closeNode(node) { + RemoveItem(this._openNodes, node); + this._currentNode = node; + } + + _exitNode(node) { + this._currentNode = node; + } +}; + +export default Tick; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/utils/CreateID.js b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/utils/CreateID.js new file mode 100644 index 000000000..36c875162 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/behaviortree/utils/CreateID.js @@ -0,0 +1,36 @@ +import UUID from '../../../utils/string/UUID.js'; + +var sn = null; +var snPrefix = '#'; + +var SetSerialNumber = function (value) { + if (value === undefined) { + value = null; + } + + sn = value; +} + +var SetSerialNumberPrefix = function (prefix) { + snPrefix = prefix; +} + +var GetSerialNumber = function () { + return sn; +} + +var CreateID = function () { + if (sn === null) { + return UUID(); + } + + sn += 1; + return `${snPrefix}${sn}`; +} + +export { + CreateID, + SetSerialNumber, + SetSerialNumberPrefix, + GetSerialNumber, +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser/BracketParser.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser/BracketParser.d.ts new file mode 100644 index 000000000..45f29f4ec --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser/BracketParser.d.ts @@ -0,0 +1,27 @@ +import BracketParserBase from '../bracketparserbase/BracketParser'; +export default BracketParser; + +declare namespace BracketParser { + interface IConfig extends BracketParserBase.IConfig { + regex?: { + tag?: string, + value?: string, + } + } + namespace Events { + type StartCallbackType = (parser: BracketParser) => void; + type CompleteCallbackType = (parser: BracketParser) => void; + type PauseCallbackType = (parser: BracketParser) => void; + type ResumeCallbackType = (parser: BracketParser) => void; + + type TagOnCallbackType = (...values: any) => void; + type AnyTagOnCallbackType = (tagName: string, ...values: any) => void; + type TagOffCallbackType = () => void; + type AnyTagOffCallbackType = (tagName: string) => void; + type ContentCallbackType = (content: string) => void; + } +} + +declare class BracketParser extends BracketParserBase { + constructor(config?: BracketParser.IConfig); +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser/BracketParser.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser/BracketParser.js new file mode 100644 index 000000000..69533046c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser/BracketParser.js @@ -0,0 +1,95 @@ +import BracketParserBase from '../bracketparserbase/BracketParser.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import ParseValue from './ParseValue.js'; +import EscapeRegex from '../../../utils/string/EscapeRegex.js'; + +class BracketParser extends BracketParserBase { + constructor(config) { + if (config === undefined) { + config = {}; + } + + if (!config.hasOwnProperty('multipleLinesTag')) { + config.multipleLinesTag = false; + } + + super(config); + + // Parameters for regex + this.setTagExpression(GetValue(config, 'regex.tag', undefined)); + this.setValueExpression(GetValue(config, 'regex.value', undefined)); + // Brackets and generate regex + var delimiters = GetValue(config, 'delimiters', '<>'); + this.setDelimiters(delimiters[0], delimiters[1]); + } + + setTagExpression(express) { + if (!express) { + express = DefaultTokenExpression; + } + this.tagExpression = express; + return this; + } + + setValueExpression(express) { + if (!express) { + express = DefaultTokenExpression; + } + this.valueExpression = express; + return this; + } + + setDelimiters(delimiterLeft, delimiterRight) { + super.setDelimiters(delimiterLeft, delimiterRight); + + var tag = `(${this.tagExpression})(=(${this.valueExpression}))?`; + this.reTag = RegExp(tag, 'i'); + + if ((this.tagExpression !== DefaultTokenExpression) || (this.valueExpression !== DefaultTokenExpression)) { + var startTagExpression = `${this.tagExpression}(=${this.valueExpression})?` + var endTagExpression = `/${this.tagExpression}`; + + delimiterLeft = EscapeRegex(this.delimiterLeft); + delimiterRight = EscapeRegex(this.delimiterRight); + + var flag = (this.multipleLinesTagEnable) ? 'gs' : 'gi'; + this.reSplit = RegExp(`${delimiterLeft}((${startTagExpression})|(${endTagExpression}))${delimiterRight}`, flag); + } + + return this; + } + + onTag(tagContent) { + var regexResult = tagContent.match(this.reTag); + var tagName = regexResult[1]; + + var isEndTag = (tagName.charAt(0) === '/'); + if (isEndTag) { + tagName = tagName.substring(1, tagName.length); + } + + if (this.translateTagNameCallback) { + tagName = this.translateTagNameCallback(tagName); + } + + this.skipEventFlag = false; + if (!isEndTag) { + var values = ParseValue(regexResult[3], this.valueConverter); + this.emit(`+${tagName}`, ...values); + if (!this.skipEventFlag) { + this.emit('+', tagName, ...values); + } + this.lastTagStart = tagName; + } else { + this.emit(`-${tagName}`); + if (!this.skipEventFlag) { + this.emit('-', tagName); + } + this.lastTagEnd = tagName; + } + } +} + +const DefaultTokenExpression = `[^=]+`; + +export default BracketParser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser/ParseValue.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser/ParseValue.js new file mode 100644 index 000000000..10fa1234d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser/ParseValue.js @@ -0,0 +1,12 @@ +var ParseValue = function (text, valueConverter) { + if (text == null) { + return []; + } + var values = text.split(','); + for (var i = 0, cnt = values.length; i < cnt; i++) { + values[i] = valueConverter(values[i]); + } + return values; +} + +export default ParseValue; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser2/BracketParser.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser2/BracketParser.d.ts new file mode 100644 index 000000000..ba9c645dc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser2/BracketParser.d.ts @@ -0,0 +1,26 @@ +import BracketParserBase from '../bracketparserbase/BracketParser'; + +export default BracketParser; + +declare namespace BracketParser { + interface IConfig extends BracketParserBase.IConfig { + + } + + namespace Events { + type StartCallbackType = (parser: BracketParser) => void; + type CompleteCallbackType = (parser: BracketParser) => void; + type PauseCallbackType = (parser: BracketParser) => void; + type ResumeCallbackType = (parser: BracketParser) => void; + + type TagOnCallbackType = (payload: { [name: string]: any }) => void; + type AnyTagOnCallbackType = (tagName: string, payload: { [name: string]: any }) => void; + type TagOffCallbackType = (payload: { [name: string]: any }) => void; + type AnyTagOffCallbackType = (tagName: string, payload: { [name: string]: any }) => void; + type ContentCallbackType = (content: string) => void; + } +} + +declare class BracketParser extends BracketParserBase { + constructor(config?: BracketParser.IConfig); +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser2/BracketParser.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser2/BracketParser.js new file mode 100644 index 000000000..d89933b14 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser2/BracketParser.js @@ -0,0 +1,84 @@ +import BracketParserBase from '../bracketparserbase/BracketParser.js'; +import EscapeRegex from '../../../utils/string/EscapeRegex.js'; +import ParseValue from './ParseValue.js'; + + +class BracketParser extends BracketParserBase { + constructor(config) { + if (config === undefined) { + config = {}; + } + + if (!config.hasOwnProperty('multipleLinesTag')) { + config.multipleLinesTag = true; + } + + super(config); + } + + setDelimiters(delimiterLeft, delimiterRight) { + super.setDelimiters(delimiterLeft, delimiterRight); + + this.reTagName = RegExp(reTagName, 'i'); + this.reParamPair = RegExp(reParamPair, 'gi'); + + return this; + } + + onTag(tagContent) { + var regexResult = tagContent.match(this.reTagName); + var tagName = regexResult[1]; + + if (this.translateTagNameCallback) { + tagName = this.translateTagNameCallback(tagName); + } + + this.reParamPair.lastIndex = regexResult.index + regexResult[0].length; + var payload = {}; + while (true) { + var regexResult = this.reParamPair.exec(tagContent); + if (!regexResult) { + break; + } + payload[regexResult[1]] = ParseValue(regexResult[2], this.valueConverter); + } + + var isEndTag = (tagName.charAt(0) === '/'); + if (isEndTag) { + tagName = tagName.substring(1, tagName.length); + } + + var eventPrefix = (isEndTag) ? '-' : '+'; + this.skipEventFlag = false; + this.emit(`${eventPrefix}${tagName}`, payload); + if (!this.skipEventFlag) { + this.emit(eventPrefix, tagName, payload); + } + + if (!isEndTag) { + this.lastTagStart = tagName; + } else { + this.lastTagEnd = tagName; + } + } +} + +var CreateQuotesExpression = function (leftQuote, rightQuote) { + if (rightQuote === undefined) { + rightQuote = leftQuote; + } + leftQuote = EscapeRegex(leftQuote); + rightQuote = EscapeRegex(rightQuote); + return `${leftQuote}[^${leftQuote}${rightQuote}]+${rightQuote}` +} + +const varName = `[^ =\n]+`; // Any character except space ,'=', and '\n' +const varStringValue = `${CreateQuotesExpression('"')}|${CreateQuotesExpression("'")}`; +const varArrayValue = CreateQuotesExpression('[', ']'); +const varDictionaryValue = CreateQuotesExpression('{', '}'); +const varValue = `${varStringValue}|${varArrayValue}|${varDictionaryValue}|${varName}`; // Any character except '=' +const escapeSpace = `[ \n]*`; +const reTagName = `${escapeSpace}(${varName})${escapeSpace}`; +const reParamPair = `(${varName})${escapeSpace}=${escapeSpace}(${varValue})${escapeSpace}` + +export default BracketParser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser2/ParseValue.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser2/ParseValue.js new file mode 100644 index 000000000..24d8023f6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparser2/ParseValue.js @@ -0,0 +1,29 @@ +var ParseValue = function (text, valueConverter) { + if (text == null) { + return null; + } + + var lastTextIndex = text.length - 1; + var firstChar = text.charAt(0); + var lastChar = text.charAt(lastTextIndex); + + if ( + ((firstChar === '"') && (lastChar === '"')) || + ((firstChar === '"') && (lastChar === '"')) + ) { + // Is a quotes string + return text.substring(1, lastTextIndex); + } else if (((firstChar === '[') && (lastChar === ']')) || + ((firstChar === '{') && (lastChar === '}'))) { + // Is an array or a dictionary + try { + return JSON.parse(text); + } catch { + return text; + } + } + + return valueConverter(text); +} + +export default ParseValue; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/BracketParser.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/BracketParser.d.ts new file mode 100644 index 000000000..6629a7fed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/BracketParser.d.ts @@ -0,0 +1,44 @@ +import EventEmitter from '../../../utils/eventemitter/EventEmitter'; +export default BracketParser; + +declare namespace BracketParser { + type ValueConvertCallback = (s: string) => any; + type TranslateTagNameCallbackType = (s: string) => string; + + interface IConfig { + multipleLinesTag?: boolean, + delimiters?: string | [string, string], + valueConvert?: boolean | ValueConvertCallback, + translateTagNameCallback?: TranslateTagNameCallbackType, + + eventEmitter?: EventEmitter | false, + + loop?: boolean + } +} + +declare class BracketParser extends EventEmitter { + constructor( + config?: BracketParser.IConfig + ); + + start(text: string): this; + + pause(): this; + pauseUntilEvent( + eventEmitter: EventEmitter, + eventName: string + ): this; + + next(): this; + + restart(): this; + + skipEvent(): this; + + readonly isRunning: boolean; + readonly isPaused: boolean; + + setDelimiters(delimiterLeft: string, delimiterRight?: string): this; + setTranslateTagNameCallback(callback?: BracketParser.TranslateTagNameCallbackType): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/BracketParser.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/BracketParser.js new file mode 100644 index 000000000..e001cc0f0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/BracketParser.js @@ -0,0 +1,260 @@ +import EventEmitterMethods from '../../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import DefaultValueConverter from '../../../utils/string/TypeConvert.js'; +import EscapeRegex from '../../../utils/string/EscapeRegex.js'; + +class BracketParser { + constructor(config) { + // Event emitter + this.setEventEmitter(GetValue(config, 'eventEmitter', undefined)); + + // Value convert + this.setValueConverter(GetValue(config, 'valueConvert', true)); + // Loop + this.setLoopEnable(GetValue(config, 'loop', false)); + + // Brackets and generate regex + this.setMultipleLinesTagEnable(GetValue(config, 'multipleLinesTag', false)); + var delimiters = GetValue(config, 'delimiters', '<>'); + this.setDelimiters(delimiters[0], delimiters[1]); + + // Translate tagName callback + this.setTranslateTagNameCallback(GetValue(config, 'translateTagNameCallback')); + + this.isRunning = false; + this.isPaused = false; + this.skipEventFlag = false; + this.justCompleted = false; + this.lastTagStart = null; + this.lastTagEnd = null; + this.lastContent = null; + } + + shutdown() { + this.destroyEventEmitter(); + } + + destroy() { + this.shutdown(); + } + + setMultipleLinesTagEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.multipleLinesTagEnable = enable; + return this; + } + + // Override + setDelimiters(delimiterLeft, delimiterRight) { + if (delimiterRight === undefined) { + delimiterRight = delimiterLeft[1]; + delimiterLeft = delimiterLeft[0]; + } + this.delimiterLeft = delimiterLeft; + this.delimiterRight = delimiterRight; + + delimiterLeft = EscapeRegex(this.delimiterLeft); + delimiterRight = EscapeRegex(this.delimiterRight); + + var flag = (this.multipleLinesTagEnable) ? 'gs' : 'gi'; + this.reSplit = RegExp(`${delimiterLeft}(.+?)${delimiterRight}`, flag); + + return this; + } + + setTranslateTagNameCallback(callback) { + this.translateTagNameCallback = callback; + return this; + } + + setValueConverter(converter) { + if (converter === true) { + converter = DefaultValueConverter; + } else if (!converter) { + converter = BypassValueConverter; + } + this.valueConverter = converter; + return this; + } + + setLoopEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.loopEnable = enable; + return this; + } + + setSource(source) { + this.source = source; + return this; + } + + resetIndex(index) { + if (index === undefined) { + index = 0; + } + this.progressIndex = index; + this.reSplit.lastIndex = index; + this.lastTagStart = null; + this.lastTagEnd = null; + this.lastContent = null; + this.justCompleted = false; + this.isRunning = false; + return this; + } + + start(source) { + this + .setSource(source) + .restart(); + return this; + } + + restart() { + this + .resetIndex() + .next(); + } + + next() { + if (this.isPaused) { + this.onResume(); + } + + // Don't re-enter this method + if (this.isRunning) { + return this; + } + + this.isRunning = true; + + if (this.justCompleted) { + this.isRunning = false; + return this; + } + + if (this.reSplit.lastIndex === 0) { + this.onStart(); + } + + var text = this.source, + lastIndex = text.length; + + this.reSplit.lastIndex = this.progressIndex; + while (true) { + var regexResult = this.reSplit.exec(text); + // No tag found, complete + if (!regexResult) { + if (this.progressIndex < lastIndex) { + this.onContent(text.substring(this.progressIndex, lastIndex)); + // Might pause here + if (this.isPaused) { + this.progressIndex = lastIndex; + break; + } + } + this.onComplete(); + this.isRunning = false; + return; + } + + var matchEnd = this.reSplit.lastIndex; + var matchStart = matchEnd - regexResult[0].length; + + // Process content between previous tag and current tag + if (this.progressIndex < matchStart) { + this.onContent(text.substring(this.progressIndex, matchStart)); + // Might pause here + if (this.isPaused) { + this.progressIndex = matchStart; + break; + } + } + + // Process current tag + this.onTag(regexResult[1]); + + this.progressIndex = matchEnd; + // Might pause here + if (this.isPaused) { + break; + } + + } + + this.isRunning = false; + return this; + } + + skipEvent() { + this.skipEventFlag = true; + return this; + } + + pause() { + if (!this.isPaused) { + this.onPause(); + } + return this; + } + + pauseUntilEvent(eventEmitter, eventName) { + if (this.isPaused) { + return this; + } + + this.pause(); + eventEmitter.once(eventName, function () { + this.next(); + }, this); + return this; + } + + onContent(content) { + this.skipEventFlag = false; + this.emit('content', content); + this.lastContent = content; + } + + // Override + onTag(tagContent) { + + } + + onStart() { + this.isRunning = true; + this.emit('start', this); + } + + onComplete() { + this.isRunning = false; + this.justCompleted = true; + this.emit('complete', this); + if (this.loopEnable) { + this.resetIndex(); + } + } + + onPause() { + this.isPaused = true; + this.emit('pause', this); + } + + onResume() { + this.isPaused = false; + this.emit('resume', this); + } + +} + +const BypassValueConverter = function (s) { return s; } + +Object.assign( + BracketParser.prototype, + EventEmitterMethods, +); + +export default BracketParser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/ParseValue.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/ParseValue.js new file mode 100644 index 000000000..24d8023f6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/ParseValue.js @@ -0,0 +1,29 @@ +var ParseValue = function (text, valueConverter) { + if (text == null) { + return null; + } + + var lastTextIndex = text.length - 1; + var firstChar = text.charAt(0); + var lastChar = text.charAt(lastTextIndex); + + if ( + ((firstChar === '"') && (lastChar === '"')) || + ((firstChar === '"') && (lastChar === '"')) + ) { + // Is a quotes string + return text.substring(1, lastTextIndex); + } else if (((firstChar === '[') && (lastChar === ']')) || + ((firstChar === '{') && (lastChar === '}'))) { + // Is an array or a dictionary + try { + return JSON.parse(text); + } catch { + return text; + } + } + + return valueConverter(text); +} + +export default ParseValue; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/TokenExpressionMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/TokenExpressionMethods.js new file mode 100644 index 000000000..64243af60 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/bracketparserbase/TokenExpressionMethods.js @@ -0,0 +1,60 @@ +import EscapeRegex from '../../../utils/string/EscapeRegex.js'; +import ParseValue from './ParseValue.js'; + +var CreateQuotesExpression = function (leftQuote, rightQuote) { + if (rightQuote === undefined) { + rightQuote = leftQuote; + } + leftQuote = EscapeRegex(leftQuote); + rightQuote = EscapeRegex(rightQuote); + return `${leftQuote}[^${leftQuote}${rightQuote}]+${rightQuote}` +} + +const varName = `[^ =\n]+`; // Any character except space ,'=', and '\n' +const varStringValue = `${CreateQuotesExpression('"')}|${CreateQuotesExpression("'")}`; +const varArrayValue = CreateQuotesExpression('[', ']'); +const varDictionaryValue = CreateQuotesExpression('{', '}'); +const varValue = `${varStringValue}|${varArrayValue}|${varDictionaryValue}|${varName}`; // Any character except '=' +const escapeSpace = `[ \n]*`; +const reTagName = `${escapeSpace}(${varName})${escapeSpace}`; +const reParamPair = `(${varName})${escapeSpace}=${escapeSpace}(${varValue})${escapeSpace}` + +export default { + setDelimiters(delimiterLeft, delimiterRight) { + if (delimiterRight === undefined) { + delimiterRight = delimiterLeft[1]; + delimiterLeft = delimiterLeft[0]; + } + this.delimiterLeft = delimiterLeft; + this.delimiterRight = delimiterRight; + + delimiterLeft = EscapeRegex(delimiterLeft); + delimiterRight = EscapeRegex(delimiterRight); + + this.reTagName = RegExp(reTagName, 'i'); + this.reParamPair = RegExp(reParamPair, 'gi'); + + this.reSplit = RegExp(`${delimiterLeft}(.+?)${delimiterRight}`, 'gs'); + return this; + }, + + parseTag(tagContent) { + var regexResult = tagContent.match(this.reTagName); + var name = regexResult[1]; + + this.reParamPair.lastIndex = regexResult.index + regexResult[0].length; + var payload = {}; + while (true) { + var regexResult = this.reParamPair.exec(tagContent); + if (!regexResult) { + break; + } + payload[regexResult[1]] = ParseValue(regexResult[2], this.valueConverter); + } + + return { + name: name, + payload: payload, + }; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/TagPlayer.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/TagPlayer.d.ts new file mode 100644 index 000000000..f82f319a2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/TagPlayer.d.ts @@ -0,0 +1,123 @@ +import Managers from '../../runcommands/managers/Managers'; + +export default TagPlayer; + +declare namespace TagPlayer { + interface IConfigParser { + delimiters?: string, + comment?: string, + translateTagNameCallback?: (s: string) => string, + } + + interface IConfigSounds { + bgm?: { + initial?: string, + loop?: boolean, + fade?: number + }, + bgm2?: { + initial?: string, + loop?: boolean, + fade?: number + } + } + + interface ISpriteGameObjectConfig { + createGameObject?: 'sprite' | 'image' | Managers.CreateGameObjectCallbackType, + + fade?: number | { + mode?: 0 | 1 | 'tint' | 'alpha', + time?: number + }, + + viewportCoordinate?: boolean | { + enable?: boolean, + viewport?: Phaser.Geom.Rectangle + } + } + + interface ITextGameObjectConfig { + createGameObject?: Managers.CreateGameObjectCallbackType, + + fade?: number | { + mode?: 0 | 1 | 'tint' | 'alpha', + time?: number + }, + + viewportCoordinate?: boolean | { + enable?: boolean, + viewport?: Phaser.Geom.Rectangle + } + } + + type NextPageInputTypes = string | ((callback: Function) => void) | null; + + type ClickTrgetTypes = Phaser.GameObjects.GameObject | Phaser.Scene; + + interface IConfig { + parser?: IConfigParser, + + sounds?: Managers.IConfigSounds, + + sprites?: ISpriteGameObjectConfig | false, + + texts?: ITextGameObjectConfig | false, + + nextPageInput?: NextPageInputTypes, + + clickTarget?: ClickTrgetTypes, + } +} + +declare class TagPlayer extends Phaser.Events.EventEmitter { + constructor( + scene: Phaser.Scene, + config?: TagPlayer.IConfig + ); + + destroy(fromScene?: boolean): this; + + addGameObjectManager(config: Managers.IGameObjectConfig): this; + + play(commands: string): this; + playPromise(commands: string): Promise; + + pause(): this; + pauseUntilEvent( + eventEmitter: Phaser.Events.EventEmitter, + eventName: string + ): this; + + resume(): this; + + isPlaying: boolean; + + setTimeScale(timeScale: number): this; + timeScale: number; + + setClickTarget(clickTarget: TagPlayer.ClickTrgetTypes): this; + readonly clickTarget: TagPlayer.ClickTrgetTypes; + + setTargetCamera(camera: Phaser.Cameras.Scene2D.BaseCamera): this; + readonly targetCamera: Phaser.Cameras.Scene2D.BaseCamera; + + getGameObject( + goType: string, + name: string + ): Phaser.GameObjects.GameObject; + getGameObject( + goType: string, + ): { [name: string]: Phaser.GameObjects.GameObject } + addGameObject( + goType: string, + name: string, + gameObject: Phaser.GameObjects.GameObject + ): this; + + setDataEnabled(): this; + setData(key: string | object, data?: any): this; + incData(key: string | object, data?: any): this; + toggleData(key: string | object): this; + getData(key: string | string[]): any; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/TagPlayer.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/TagPlayer.js new file mode 100644 index 000000000..7311e472a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/TagPlayer.js @@ -0,0 +1,91 @@ +import Extend from '../../../utils/managers/Extend.js'; +import Parser from './parser/Parser.js'; +import AddSpriteManager from './methods/spritemanager/AddSpriteManager.js'; +import AddTextManager from './methods/textmanager/AddTextManager.js'; +import Methods from './methods/Methods.js'; +import ClearEvents from './methods/utils/ClearEvents.js'; + +const EventEmitter = Phaser.Events.EventEmitter; +const GetValue = Phaser.Utils.Objects.GetValue; + +class TagPlayer extends Extend(EventEmitter) { + constructor(scene, config) { + if (config === undefined) { + config = {}; + } + super(); + + this.scene = scene; + + this.parser = new Parser(this, GetValue(config, 'parser', undefined)); + + this.setTargetCamera(GetValue(config, 'camera', this.scene.sys.cameras.main)); + + this.initManagers(scene, config); + + var spriteManagerConfig = GetValue(config, 'sprites'); + if ((spriteManagerConfig !== false) && (spriteManagerConfig !== null)) { + AddSpriteManager.call(this, spriteManagerConfig); + } + + var textManagerConfig = GetValue(config, 'texts'); + if ((textManagerConfig !== false) && (textManagerConfig !== null)) { + AddTextManager.call(this, textManagerConfig); + } + + this.setClickTarget(GetValue(config, 'clickTarget', scene)); // this.clickEE + } + + get isPlaying() { + return this.parser.isRunning; + } + + get spriteManager() { + return this.getGameObjectManager('sprite'); + } + + get textManager() { + return this.getGameObjectManager('text'); + } + + get gameObjectManagerNames() { + var names = []; + for (var name in this.gameObjectManagers) { + names.push(name); + } + return names; + } + + destroy(fromScene) { + // This Game Object has already been destroyed + if (!this.scene) { + return; + } + + ClearEvents(this); + + this.targetCamera = undefined; + + super.destroy(); + + this.destroyManagers(fromScene); + + this.scene = undefined; + } + + set timeScale(value) { + this.setTimeScale(value); + } + + get timeScale() { + return this.getTimeScale(); + } + +} + +Object.assign( + TagPlayer.prototype, + Methods +); + +export default TagPlayer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/ContentMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/ContentMethods.js new file mode 100644 index 000000000..de1f41f9b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/ContentMethods.js @@ -0,0 +1,7 @@ +export default { + setContentCallback(callback, scope) { + this.contentCallback = callback; + this.contentCallbackScope = scope; + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/Methods.js new file mode 100644 index 000000000..294556373 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/Methods.js @@ -0,0 +1,33 @@ +import GameObjectManagerMethods from './gameobjectmanager/GameObjectManagerMethods.js'; +import SetClickTarget from './SetClickTarget.js'; +import SetTargetCamera from './SetTargetCamera.js'; +import SetSkipSoundEffect from './SetSkipSoundEffect.js'; +import PlayMethods from './PlayMethods.js'; +import PauseMethods from './PauseMethods.js'; +import ResumeMethods from './ResumeMethods.js'; +import Wait from './Wait.js'; +import SpriteMethods from './spritemanager/SpriteMethods.js'; +import TextMethods from './textmanager/TextMethods.js'; +import ContentMethods from './ContentMethods.js'; +import DataManagerMethods from '../../../../utils/data/DataManagerMethods.js'; + +var Methods = { + setClickTarget: SetClickTarget, + setTargetCamera: SetTargetCamera, + setSkipSoundEffect: SetSkipSoundEffect, + wait: Wait, +} + +Object.assign( + Methods, + PlayMethods, + PauseMethods, + ResumeMethods, + GameObjectManagerMethods, + SpriteMethods, + TextMethods, + ContentMethods, + DataManagerMethods, +) + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/PauseMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/PauseMethods.js new file mode 100644 index 000000000..ec495cb8f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/PauseMethods.js @@ -0,0 +1,11 @@ +export default { + pause() { + this.parser.pause(); + return this; + }, + + pauseUntilEvent(eventEmitter, eventName) { + this.parser.pauseUntilEvent(eventEmitter, eventName); + return this; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/PlayMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/PlayMethods.js new file mode 100644 index 000000000..7fa81ac4a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/PlayMethods.js @@ -0,0 +1,15 @@ +import { WaitComplete } from '../../../../utils/promise/WaitEvent.js'; + +export default { + play(content) { + this.parser.start(content); + return this; + }, + + playPromise(content) { + var promise = WaitComplete(this); + this.play(content); + return promise; + }, + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/ResumeMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/ResumeMethods.js new file mode 100644 index 000000000..60a1fb6c3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/ResumeMethods.js @@ -0,0 +1,6 @@ +export default { + resume() { + this.parser.next(); + return this; + } +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/SetClickTarget.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/SetClickTarget.js new file mode 100644 index 000000000..e99d46976 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/SetClickTarget.js @@ -0,0 +1,17 @@ +import IsSceneObject from '../../../../utils/system/IsSceneObject.js'; + +var SetClickTarget = function (target) { + this.clickTarget = target; + + if (!target) { + this.clickEE = null; + } else if (IsSceneObject(target)) { + this.clickEE = target.input; + } else { // Assume that target is a gameObject + this.clickEE = target.setInteractive(); + } + + return this; +} + +export default SetClickTarget; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/SetSkipSoundEffect.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/SetSkipSoundEffect.js new file mode 100644 index 000000000..1463bb65e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/SetSkipSoundEffect.js @@ -0,0 +1,16 @@ +var SetSkipSoundEffect = function (value) { + if (value === undefined) { + value = true; + } + this.skipSoundEffect = value; + + if (value) { + var soundManager = this._soundManager; + if (soundManager) { + soundManager.fadeOutAllSoundEffects(100, true); + } + } + return this; +} + +export default SetSkipSoundEffect; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/SetTargetCamera.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/SetTargetCamera.js new file mode 100644 index 000000000..b56ad47f2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/SetTargetCamera.js @@ -0,0 +1,6 @@ +var SetTargetCamera = function (camera) { + this.targetCamera = camera; + return this; +} + +export default SetTargetCamera; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/Wait.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/Wait.js new file mode 100644 index 000000000..204e0d2c7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/Wait.js @@ -0,0 +1,15 @@ +import WaitMultiple from './utils/wait/WaitMultiple.js'; + +var Wait = function (name) { + // Already in typingPaused state, or ignore any wait + if (this.ignoreWait) { + return this; + } + + this.pause(); + WaitMultiple(this, name, this.resume, [], this); + + return this; +} + +export default Wait; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/GameObjectManagerMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/GameObjectManagerMethods.js new file mode 100644 index 000000000..3fa3a847f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/GameObjectManagerMethods.js @@ -0,0 +1,43 @@ +import GameObjectManagerMethods from '../../../../../utils/managers/GameObjectManagerMethods.js'; +import OnParseAddGameObjectTag from './OnParseAddGameObjectTag.js'; +import OnParseRemoveAllGameObjectsTag from './OnParseRemoveAllGameObjectsTag.js'; +import OnParseCallGameObjectMethodTag from './OnParseCallGameObjectMethodTag.js'; +import OnParseEaseGameObjectPropertyTag from './OnParseEaseGameObjectPropertyTag.js'; + +const ParseCallbacks = [ + OnParseAddGameObjectTag, OnParseRemoveAllGameObjectsTag, + OnParseCallGameObjectMethodTag, + OnParseEaseGameObjectPropertyTag +]; + +const AddGameObjectManager = GameObjectManagerMethods.addGameObjectManager; + +export default { + addGameObjectManager(config, GameObjectManagerClass) { + if (config === undefined) { + config = {}; + } + var name = config.name; + if (!name) { + console.warn(`Parameter 'name' is required in TagPlayer.addGameObjectManager(config) method`); + } + + AddGameObjectManager.call(this, config, GameObjectManagerClass); + + // Register parse callbacks + var customParseCallbacks = config.parseCallbacks; + if (!customParseCallbacks) { + customParseCallbacks = ParseCallbacks; + } else { + customParseCallbacks = [ + ...customParseCallbacks, // customParseCallbacks have higher priority + ...ParseCallbacks + ]; + } + for (var i = 0, cnt = customParseCallbacks.length; i < cnt; i++) { + customParseCallbacks[i](this, this.parser, config); + } + + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseAddGameObjectTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseAddGameObjectTag.js new file mode 100644 index 000000000..7a1d70e1e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseAddGameObjectTag.js @@ -0,0 +1,46 @@ +var IsAddGameObjectTag = function (tags, goType) { + // goType.name + return (tags.length === 2) && (tags[0] === goType) +} + +var OnParseAddGameObjectTag = function (tagPlayer, parser, config) { + var goType = config.name; + var gameObjectManager = tagPlayer.getGameObjectManager(goType); + parser + .on('+', function (tag, ...args) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name=key,frame], or [goType.name] + var tags = tag.split('.'); + var name; + if (IsAddGameObjectTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + gameObjectManager.add(name, ...args); + + parser.skipEvent(); + }) + .on('-', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [/goType.name] + var tags = tag.split('.'); + var name; + if (IsAddGameObjectTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + gameObjectManager.remove(name); + + parser.skipEvent(); + }) +} + +export default OnParseAddGameObjectTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseCallGameObjectMethodTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseCallGameObjectMethodTag.js new file mode 100644 index 000000000..7fbe37e50 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseCallGameObjectMethodTag.js @@ -0,0 +1,48 @@ +var IsPropTag = function (tags, goType) { + // goType.name.prop + return (tags.length === 3) && (tags[0] === goType); +} + +var OnParseCallGameObjectMethodTag = function (tagPlayer, parser, config) { + var goType = config.name; + var gameObjectManager = tagPlayer.getGameObjectManager(goType); + parser + .on(`+`, function (tag, ...parameters) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name.methodName=value0,value1,value2...] + // [goType.name.prop=value] + var tags = tag.split('.'); + var name, prop; + if (IsPropTag(tags, goType)) { + name = tags[1]; + prop = tags[2]; + } else { + return; + } + + var eventName = `${goType}.${prop}`; + tagPlayer.emit( + eventName, + name, ...parameters + ); + if (tagPlayer.listenerCount(eventName) > 0) { + parser.skipEvent(); + return; + } + + if (gameObjectManager.hasMethod(name, prop)) { + // Is method + gameObjectManager.call(name, prop, ...parameters); + } else { + // Is property + gameObjectManager.setProperty(name, prop, parameters[0]); + } + + parser.skipEvent(); + }) +} + +export default OnParseCallGameObjectMethodTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseEaseGameObjectPropertyTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseEaseGameObjectPropertyTag.js new file mode 100644 index 000000000..1055c2f77 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseEaseGameObjectPropertyTag.js @@ -0,0 +1,76 @@ +var EaseMode = { + to: true, yoyo: true, from: true, + toLeft: true, toRight: true, toUp: true, toDown: true, + yoyoLeft: true, yoyoRight: true, yoyoUp: true, yoyoDown: true, + fromLeft: true, fromRight: true, fromUp: true, fromDown: true, +} + +var IsEasePropertyTag = function (tags, goType) { + // goType.name.prop.to + return (tags.length === 4) && (tags[0] === goType) && EaseMode[tags[3]]; +} + +var OnParseEaseGameObjectPropertyTag = function (tagPlayer, parser, config) { + var goType = config.name; + var gameObjectManager = tagPlayer.getGameObjectManager(goType); + parser + .on(`+`, function (tag, value, duration, ease, repeat) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name.prop.to=value,duration] + // [goType.name.prop.to=value,duration,ease,repeat] + // [goType.name.prop.to=value,duration,repeat] + var tags = tag.split('.'); + var name, property, currentValue, easeMode; + if (IsEasePropertyTag(tags, goType)) { + name = tags[1]; + property = tags[2]; + currentValue = gameObjectManager.getProperty(name, property); + // Only can tween number property + if (typeof (currentValue) !== 'number') { + return; + } + + easeMode = tags[3]; + } else { + return; + } + + if (typeof (ease) === 'number') { + repeat = ease; + ease = undefined; + } + + if (easeMode.endsWith('Left') || easeMode.endsWith('Up')) { + if (easeMode.startsWith('to') || easeMode.startsWith('yoyo')) { + value = currentValue - value; + } else if (easeMode.startsWith('from')) { + gameObjectManager.setProperty(name, property, (currentValue - value)); + value = currentValue; + } + } else if (easeMode.endsWith('Right') || easeMode.endsWith('Down')) { + if (easeMode.startsWith('to') || easeMode.startsWith('yoyo')) { + value = currentValue + value; + } else if (easeMode.startsWith('from')) { + gameObjectManager.setProperty(name, property, (currentValue + value)); + value = currentValue; + } + } else if (easeMode === 'from') { + gameObjectManager.setProperty(name, property, value); + value = currentValue; + } + + var isYoyo = easeMode.startsWith('yoyo'); + + gameObjectManager.easeProperty( + name, property, value, + duration, ease, repeat, isYoyo + ); + + parser.skipEvent(); + }) +} + +export default OnParseEaseGameObjectPropertyTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseRemoveAllGameObjectsTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseRemoveAllGameObjectsTag.js new file mode 100644 index 000000000..294813436 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/gameobjectmanager/OnParseRemoveAllGameObjectsTag.js @@ -0,0 +1,21 @@ +var OnParseRemoveAllGameObjectsTag = function (tagPlayer, parser, config) { + var goType = config.name; + var gameObjectManager = tagPlayer.getGameObjectManager(goType); + parser + .on('-', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [/goType] + if (tag === goType) { + } else { + return; + } + + gameObjectManager.removeAll(); + parser.skipEvent(); + }) +} + +export default OnParseRemoveAllGameObjectsTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/AddSpriteManager.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/AddSpriteManager.js new file mode 100644 index 000000000..9ab7a4371 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/AddSpriteManager.js @@ -0,0 +1,21 @@ +import SpriteManager from '../../../../../utils/sprite/spritemanager/SpriteManager.js'; +import OnParsePlayAnimationTag from './OnParsePlayAnimationTag.js'; +import OnParsePauseAnimationTag from './OnParsePauseAnimationTag.js'; +import OnParseChainAnimationTag from './OnParseChainAnimationTag.js'; + +const ParseCallbacks = [ + OnParsePlayAnimationTag, + OnParsePauseAnimationTag, + OnParseChainAnimationTag, +]; + +var AddSpriteManager = function (config) { + if (config === undefined) { + config = {}; + } + config.name = 'sprite'; + config.parseCallbacks = ParseCallbacks; + this.addGameObjectManager(config, SpriteManager); +} + +export default AddSpriteManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/OnParseChainAnimationTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/OnParseChainAnimationTag.js new file mode 100644 index 000000000..4cce4ec60 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/OnParseChainAnimationTag.js @@ -0,0 +1,30 @@ +var IsChainAnimationTag = function (tags, goType) { + // goType.name.chain + return (tags.length === 3) && (tags[0] === goType) && (tags[2] === 'chain'); +} + +var OnParseChainAnimationTag = function (tagPlayer, parser, config) { + var goType = config.name; + var gameObjectManager = tagPlayer.getGameObjectManager(goType); + parser + .on('+', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name.chain=key] + var tags = tag.split('.'); + var name; + if (IsChainAnimationTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + var keys = Array.prototype.slice.call(arguments, 1); + gameObjectManager.chainAnimation(name, keys); + + parser.skipEvent(); + }) +} + +export default OnParseChainAnimationTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/OnParsePauseAnimationTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/OnParsePauseAnimationTag.js new file mode 100644 index 000000000..377a7ce6d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/OnParsePauseAnimationTag.js @@ -0,0 +1,29 @@ +var IsPauseAnimationTag = function (tags, goType) { + // goType.name.pause + return (tags.length === 3) && (tags[0] === goType) && (tags[2] === 'pause'); +} + +var OnParsePauseAnimationTag = function (tagPlayer, parser, config) { + var goType = config.name; + var gameObjectManager = tagPlayer.getGameObjectManager(goType); + parser + .on('+', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name.pause=key] + var tags = tag.split('.'); + var name; + if (IsPauseAnimationTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + gameObjectManager.pauseAnimation(name); + + parser.skipEvent(); + }) +} + +export default OnParsePauseAnimationTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/OnParsePlayAnimationTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/OnParsePlayAnimationTag.js new file mode 100644 index 000000000..8e9094896 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/OnParsePlayAnimationTag.js @@ -0,0 +1,73 @@ +var IsPlayAnimationTag = function (tags, goType) { + // goType.name.play + return (tags.length === 3) && (tags[0] === goType) && (tags[2] === 'play'); +} + +var IsStopAnimationTag = function (tags, goType) { + // goType.name.stop + return (tags.length === 3) && (tags[0] === goType) && (tags[2] === 'stop'); +} + +var OnParsePlayAnimationTag = function (tagPlayer, parser, config) { + var goType = config.name; + var gameObjectManager = tagPlayer.getGameObjectManager(goType); + parser + .on('+', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name.play=key], or [goType.name.play=key0,key1,...] + var tags = tag.split('.'); + var name; + if (IsPlayAnimationTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + var keys = Array.prototype.slice.call(arguments, 1); + var firstKey = keys.shift(); + gameObjectManager.playAnimation(name, firstKey); + if (keys.length > 0) { + gameObjectManager.chainAnimation(name, keys); + } + + parser.skipEvent(); + }) + .on('+', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [goType.name.stop] + var tags = tag.split('.'); + var name; + if (IsStopAnimationTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + gameObjectManager.stopAnimation(name); + + parser.skipEvent(); + }) + .on('-', function (tag) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + // [/goType.name.play] + var tags = tag.split('.'); + var name; + if (IsPlayAnimationTag(tags, goType)) { + name = tags[1]; + } else { + return; + } + gameObjectManager.stopAnimation(name); + + parser.skipEvent(); + }) +} + +export default OnParsePlayAnimationTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/SpriteMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/SpriteMethods.js new file mode 100644 index 000000000..57cdbb186 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/spritemanager/SpriteMethods.js @@ -0,0 +1,11 @@ +export default { + getSprite(name) { + return this.getGameObject('sprite', name); + }, + + addSprite(name, gameObject) { + this.addGameObject('sprite', name, gameObject); + return this; + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/AddTextManager.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/AddTextManager.js new file mode 100644 index 000000000..188983e03 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/AddTextManager.js @@ -0,0 +1,19 @@ +import TextManager from '../../../../../utils/text/textmanager/TextManager.js'; +import OnParseSetTextTag from './OnParseSetTextTag.js'; +import OnParseTypingTextTag from './OnParseTypingTextTag.js'; + +const ParseCallbacks = [ + OnParseSetTextTag, + OnParseTypingTextTag +]; + +var AddTextManager = function (config) { + if (config === undefined) { + config = {}; + } + config.name = 'text'; + config.parseCallbacks = ParseCallbacks; + this.addGameObjectManager(config, TextManager); +} + +export default AddTextManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/OnParseSetTextTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/OnParseSetTextTag.js new file mode 100644 index 000000000..354e96dfb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/OnParseSetTextTag.js @@ -0,0 +1,16 @@ +var OnParseSetTextTag = function (tagPlayer, parser, config) { + var goType = config.name; + var gameObjectManager = tagPlayer.getGameObjectManager(goType); + + // [goType.name.text] -> event : 'goType.text' + tagPlayer.on(`${goType}.text`, function (name) { + // Clear text + gameObjectManager.clearText(name); + // Append text + tagPlayer.setContentCallback(function (content) { + gameObjectManager.appendText(name, content); + }); + }); +} + +export default OnParseSetTextTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/OnParseTypingTextTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/OnParseTypingTextTag.js new file mode 100644 index 000000000..be3141d06 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/OnParseTypingTextTag.js @@ -0,0 +1,19 @@ +var OnParseTypingTextTag = function (tagPlayer, parser, config) { + var goType = config.name; + var gameObjectManager = tagPlayer.getGameObjectManager(goType); + + // [goType.name.typing] -> event : 'goType.typing' + tagPlayer.on(`${goType}.typing`, function (name, speed) { + // Clear text + gameObjectManager.clearTyping(name); + // Append text + tagPlayer.setContentCallback(function (content) { + if (speed !== undefined) { + gameObjectManager.setTypingSpeed(name, speed); + } + gameObjectManager.typing(name, content); + }); + }); +} + +export default OnParseTypingTextTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/TextMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/TextMethods.js new file mode 100644 index 000000000..dd87d339e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/textmanager/TextMethods.js @@ -0,0 +1,11 @@ +export default { + getTextGameObject(name) { + return this.getGameObject('text', name); + }, + + addTextGameObject(name, gameObject) { + this.addGameObject('text', name, gameObject); + return this; + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/ClearEvents.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/ClearEvents.js new file mode 100644 index 000000000..c107d0275 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/ClearEvents.js @@ -0,0 +1,9 @@ +import { ClearEvents as Events } from './Events.js'; + +var ClearEvents = function (tagPlayer) { + for (var i = 0, cnt = Events.length; i < cnt; i++) { + tagPlayer.emit(Events[i]); + } +} + +export default ClearEvents; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/Events.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/Events.js new file mode 100644 index 000000000..4c37982e1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/Events.js @@ -0,0 +1,15 @@ +// Internal events + +const RemoveWaitEvents = '_remove.wait'; +const StopPlayEvent = '_remove.play'; + +const ClearEvents = [ + RemoveWaitEvents, + StopPlayEvent +] + +export { + RemoveWaitEvents, + StopPlayEvent, + ClearEvents +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/GetWrapCallback.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/GetWrapCallback.js new file mode 100644 index 000000000..3448303bf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/GetWrapCallback.js @@ -0,0 +1,9 @@ +import { RemoveWaitEvents } from '../Events.js'; + +var GetWrapCallback = function (tagPlayer, callback, args, scope, removeFrom) { + return function () { + tagPlayer.emit(RemoveWaitEvents, removeFrom); // Remove all wait events + callback.apply(scope, args); + } +} +export default GetWrapCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitCallback.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitCallback.js new file mode 100644 index 000000000..8819ed41e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitCallback.js @@ -0,0 +1,10 @@ +import GetWrapCallback from './GetWrapCallback.js'; + +var WaitCallback = function (tagPlayer, postfixName, callback, args, scope) { + var wrapCallback = GetWrapCallback(tagPlayer, callback, args, scope, 'custom'); + + var eventName = (postfixName) ? `wait.${postfixName}` : 'wait'; + tagPlayer.emit(eventName, wrapCallback); +} + +export default WaitCallback; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitCameraEffect.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitCameraEffect.js new file mode 100644 index 000000000..129599657 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitCameraEffect.js @@ -0,0 +1,77 @@ +import GetWrapCallback from './GetWrapCallback.js'; +import { RemoveWaitEvents } from '../Events.js'; + +var IsWaitCameraEffect = function (name) { + switch (name) { + case 'camera.fadein': + case 'camera.fadeout': + case 'camera.flash': + case 'camera.shake': + case 'camera.zoom': + case 'camera.rotate': + case 'camera.scroll': + return true; + default: + return false; + } +} + +var WaitCameraEffect = function (tagPlayer, effectName, callback, args, scope) { + var wrapCallback = GetWrapCallback(tagPlayer, callback, args, scope, `camera.${effectName}`); + + var camera = tagPlayer.targetCamera; + + var effect, completeEventName; + switch (effectName) { + case 'camera.fadein': + effect = camera.fadeEffect; + completeEventName = 'camerafadeincomplete'; + break; + + case 'camera.fadeout': + effect = camera.fadeEffect; + completeEventName = 'camerafadeoutcomplete'; + break; + + case 'camera.flash': + effect = camera.flashEffect; + completeEventName = 'cameraflashcomplete'; + break; + + case 'camera.shake': + effect = camera.shakeEffect; + completeEventName = 'camerashakecomplete'; + break; + + case 'camera.zoom': + effect = camera.zoomEffect; + completeEventName = 'camerazoomcomplete'; + break; + + case 'camera.rotate': + effect = camera.rotateToEffect; + completeEventName = 'camerarotatecomplete'; + break; + + case 'camera.scroll': + effect = camera.panEffect; + completeEventName = 'camerapancomplete'; + break; + } + + if (!effect.isRunning) { + tagPlayer.emit('wait.camera', effectName); + wrapCallback(); + + } else { + // Remove all wait events + tagPlayer.once(RemoveWaitEvents, function (removeFrom) { + camera.off(completeEventName, wrapCallback, tagPlayer); + }); + camera.once(completeEventName, wrapCallback, tagPlayer); + tagPlayer.emit('wait.camera', effectName); + } + +} + +export { IsWaitCameraEffect, WaitCameraEffect }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitClick.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitClick.js new file mode 100644 index 000000000..69f74c386 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitClick.js @@ -0,0 +1,23 @@ +import GetWrapCallback from './GetWrapCallback.js'; +import { RemoveWaitEvents } from '../Events.js'; + +var WaitClick = function (tagPlayer, callback, args, scope) { + var clickEE = tagPlayer.clickEE; + + if (!clickEE) { + return; + } + + var wrapCallback = GetWrapCallback(tagPlayer, callback, args, scope, 'click'); + + // Remove all wait events + tagPlayer.once(RemoveWaitEvents, function () { + clickEE.off('pointerdown', wrapCallback, tagPlayer); + }); + + clickEE.once('pointerdown', wrapCallback, tagPlayer); + + tagPlayer.emit('wait.click'); +} + +export default WaitClick; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitGameObject.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitGameObject.js new file mode 100644 index 000000000..ad75da357 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitGameObject.js @@ -0,0 +1,107 @@ +import GetWrapCallback from './GetWrapCallback.js'; +import { RemoveWaitEvents } from '../Events.js'; + +var IsWaitGameObject = function (tagPlayer, name) { + var names = name.split('.'); + return tagPlayer.gameObjectManagers.hasOwnProperty(names[0]); +} + +var WaitGameObject = function (tagPlayer, tag, callback, args, scope) { + var wrapCallback = GetWrapCallback(tagPlayer, callback, args, scope); + var tags = tag.split('.'); + var goType = tags[0]; + var gameObjectManager = tagPlayer.getGameObjectManager(goType); + var waitEventName = `wait.${goType}` + switch (tags.length) { + case 1: // 'goType' : wait all sprites has beeen destroyed + if (gameObjectManager.isEmpty) { + tagPlayer.emit(waitEventName); + wrapCallback(); + } else { + // Remove all wait events + tagPlayer.once(RemoveWaitEvents, function (removeFrom) { + gameObjectManager.off('empty', wrapCallback, tagPlayer); + }); + gameObjectManager.once('empty', wrapCallback, tagPlayer); + tagPlayer.emit(waitEventName); + } + return; + + case 2: // 'goType.name' : wait goType.name has been destroyed + var name = tags[1]; + if (!gameObjectManager.has(name)) { + tagPlayer.emit(waitEventName, name); + wrapCallback(); + } else { + var spriteData = gameObjectManager.get(name); + var gameObject = spriteData.gameObject; + // Remove all wait events + tagPlayer.once(RemoveWaitEvents, function () { + gameObject.off('destroy', wrapCallback, tagPlayer); + }); + + gameObject.once('destroy', wrapCallback, tagPlayer); + tagPlayer.emit(waitEventName, name); + } + return; + + case 3: // 'goType.name.prop' : wait ease goType.name.prop has been completed + var name = tags[1], + prop = tags[2]; + + // Can start tween task for a number property + if (gameObjectManager.isNumberProperty(name, prop)) { + var task = gameObjectManager.getTweenTask(name, prop); + if (!task) { + tagPlayer.emit(waitEventName, name, prop); + wrapCallback(); + } else { + // Remove all wait events + tagPlayer.once(RemoveWaitEvents, function () { + task.off('complete', wrapCallback, tagPlayer); + }); + + task.once('complete', wrapCallback, tagPlayer); + tagPlayer.emit(waitEventName, name, prop); + } + return; + } + + var dataKey = prop; + var matchFalseFlag = dataKey.startsWith('!'); + if (matchFalseFlag) { + dataKey = dataKey.substring(1); + } + // Wait until flag is true/false + if (gameObjectManager.hasData(name, dataKey)) { + var gameObject = gameObjectManager.getGO(name); + var flag = gameObject.getData(dataKey); + var matchTrueFlag = !matchFalseFlag; + if (flag === matchTrueFlag) { + tagPlayer.emit(waitEventName, name, prop); + wrapCallback(); + } else { + // Remove all wait events + var eventName = `changedata-${dataKey}`; + var callback = function (gameObject, value, previousValue) { + value = !!value; + if (value === matchTrueFlag) { + wrapCallback.call(tagPlayer); + } + } + tagPlayer.once(RemoveWaitEvents, function () { + gameObject.off(eventName, callback); + }); + + gameObject.on(eventName, callback); + tagPlayer.emit(waitEventName, name, prop); + } + return; + } + + } + +} + + +export { IsWaitGameObject, WaitGameObject }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitKeyDown.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitKeyDown.js new file mode 100644 index 000000000..de6205da4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitKeyDown.js @@ -0,0 +1,20 @@ +import GetWrapCallback from './GetWrapCallback.js'; +import { RemoveWaitEvents } from '../Events.js'; + +var WaitKeyDown = function (tagPlayer, keyName, callback, args, scope) { + var wrapCallback = GetWrapCallback(tagPlayer, callback, args, scope, 'keydown'); + + var eventName = `keydown-${keyName.toUpperCase()}`; + var keyboard = tagPlayer.scene.input.keyboard; + + // Remove all wait events + tagPlayer.once(RemoveWaitEvents, function () { + keyboard.off(eventName, wrapCallback, tagPlayer); + }); + + keyboard.once(eventName, wrapCallback, tagPlayer); + + tagPlayer.emit('wait.keydown', keyName); +} + +export default WaitKeyDown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitMultiple.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitMultiple.js new file mode 100644 index 000000000..226c215db --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitMultiple.js @@ -0,0 +1,62 @@ +import WaitCallback from './WaitCallback.js'; +import WaitTime from './WaitTime.js'; +import WaitClick from './WaitClick.js'; +import WaitMusic from './WaitMusic.js'; +import { IsWaitCameraEffect, WaitCameraEffect } from './WaitCameraEffect.js'; +import WaitKeyDown from './WaitKeyDown.js'; +import { IsWaitGameObject, WaitGameObject } from './WaitGameObject.js'; + +const KeyCodes = Phaser.Input.Keyboard.KeyCodes; + +var WaitMultiple = function (tagPlayer, names, callback, args, scope) { + if ((typeof (names) === 'string') && (names.length > 1) && (names.indexOf('|') !== -1)) { + names = names.split('|'); + } else { + names = [names]; + } + + for (var i = 0, cnt = names.length; i < cnt; i++) { + var name = names[i]; + + if ((name == null) || (name === 'wait')) { // Wait event + WaitCallback(tagPlayer, undefined, callback, args, scope); + + } else if ((typeof (name) === 'number') || !isNaN(name)) { // A number, or a number string + WaitTime(tagPlayer, parseFloat(name), callback, args, scope); + + } else if (name === 'click') { // 'click' + WaitClick(tagPlayer, callback, args, scope); + + } else if (name === 'se') { + var music = tagPlayer.soundManager.getLastSoundEffect(); + WaitMusic(tagPlayer, music, callback, args, scope); + + } else if (name === 'se2') { + var music = tagPlayer.soundManager.getLastSoundEffect2(); + WaitMusic(tagPlayer, music, callback, args, scope); + + } else if (name === 'bgm') { + var music = tagPlayer.soundManager.getBackgroundMusic(); + WaitMusic(tagPlayer, music, callback, args, scope); + + } else if (name === 'bgm2') { + var music = tagPlayer.soundManager.getBackgroundMusic2(); + WaitMusic(tagPlayer, music, callback, args, scope); + + } else if (KeyCodes.hasOwnProperty(name.toUpperCase())) { + WaitKeyDown(tagPlayer, name, callback, args, scope); + + } else if (IsWaitCameraEffect(name)) { + WaitCameraEffect(tagPlayer, name, callback, args, scope); + + } else if (IsWaitGameObject(tagPlayer, name)) { + WaitGameObject(tagPlayer, name, callback, args, scope); + + } else { + WaitCallback(tagPlayer, name, callback, args, scope); + + } + } +} + +export default WaitMultiple; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitMusic.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitMusic.js new file mode 100644 index 000000000..4d3cdc18a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitMusic.js @@ -0,0 +1,23 @@ +import GetWrapCallback from './GetWrapCallback.js'; +import { RemoveWaitEvents } from '../Events.js'; + +var WaitMusic = function (tagPlayer, music, callback, args, scope) { + var wrapCallback = GetWrapCallback(tagPlayer, callback, args, scope, 'music'); + + if (music) { + // Remove all wait events + tagPlayer.once(RemoveWaitEvents, function () { + music.off('complete', wrapCallback, tagPlayer); + }); + + music.once('complete', wrapCallback, tagPlayer); + } + + tagPlayer.emit('wait.music', music); + + if (!music) { + wrapCallback(); + } +} + +export default WaitMusic; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitTime.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitTime.js new file mode 100644 index 000000000..ca6640896 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/methods/utils/wait/WaitTime.js @@ -0,0 +1,22 @@ +import GetWrapCallback from './GetWrapCallback.js'; +import { RemoveWaitEvents } from '../Events.js'; + +var WaitTime = function (tagPlayer, time, callback, args, scope) { + var wrapCallback = GetWrapCallback(tagPlayer, callback, args, scope, 'time'); + + var timer; + + // Remove all wait events + tagPlayer.once(RemoveWaitEvents, function () { + if (timer) { + timer.remove(); + timer = undefined; + } + }); + + timer = tagPlayer.timeline.delayCall(time, wrapCallback); + + tagPlayer.emit('wait.time', time); +} + +export default WaitTime; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/AddParseCallbacks.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/AddParseCallbacks.js new file mode 100644 index 000000000..a0530b97f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/AddParseCallbacks.js @@ -0,0 +1,50 @@ +import ParseWaitTag from './wait/OnParseWaitTag.js'; +import ParsePlaySoundEffectTag from './soundeffect/OnParsePlaySoundEffectTag.js'; +import ParseFadeInSoundEffectTag from './soundeffect/OnParseFadeInSoundEffectTag.js'; +import ParseFadeOutSoundEffectTag from './soundeffect/OnParseFadeOutSoundEffectTag.js'; +import ParseSetSoundEffectVolumeTag from './soundeffect/OnParseSetSoundEffectVolumeTag.js'; +import ParsePlayBackgroundMusicTag from './backgroundmusic/OnParsePlayBackgroundMusicTag.js'; +import ParseFadeInBackgroundMusicTag from './backgroundmusic/OnParseFadeInBackgroundMusicTag.js'; +import ParseFadeOutBackgroundMusicTag from './backgroundmusic/OnParseFadeOutBackgroundMusicTag.js'; +import ParseCrossFadeBackgroundMusicTag from './backgroundmusic/OnParseCrossFadeBackgroundMusicTag.js'; +import ParsePauseBackgroundMusicTag from './backgroundmusic/OnParsePauseBackgroundMusicTag.js'; +import ParseFadeInCameraTag from './camera/OnParseFadeInCameraTag.js'; +import ParseFadeOutCameraTag from './camera/OnParseFadeOutCameraTag.js'; +import ParseShakeCameraTag from './camera/OnParseShakeCameraTag.js'; +import ParseFlashCameraTag from './camera/OnParseFlashCameraTag.js'; +import ParseZoomCameraTag from './camera/OnParseZoomCameraTag.js'; +import ParseRotateCameraTag from './camera/OnParseRotateCameraTag.js'; +import ParseScrollCameraTag from './camera/OnParseScrollCameraTag.js'; +import ParseContent from './content/OnParseContent.js'; +import ParseCustomTag from './custom/OnParseCustomTag.js'; + +const ParseCallbacks = [ + + ParseWaitTag, + + ParsePlaySoundEffectTag, ParseFadeInSoundEffectTag, ParseFadeOutSoundEffectTag, ParseSetSoundEffectVolumeTag, + ParsePlayBackgroundMusicTag, ParseFadeInBackgroundMusicTag, ParseFadeOutBackgroundMusicTag, ParseCrossFadeBackgroundMusicTag, ParsePauseBackgroundMusicTag, + + ParseFadeInCameraTag, ParseFadeOutCameraTag, ParseShakeCameraTag, ParseFlashCameraTag, ParseZoomCameraTag, ParseRotateCameraTag, ParseScrollCameraTag, + + ParseContent, + + ParseCustomTag, +]; + +var AddParseCallbacks = function (tagPlayer, parser, config) { + for (var i = 0, cnt = ParseCallbacks.length; i < cnt; i++) { + ParseCallbacks[i](tagPlayer, parser, config); + } + + parser + .on('start', function () { + tagPlayer.emit('start', parser); + }) + .on('complete', function () { + tagPlayer.emit('complete', parser); + }) + +} + +export default AddParseCallbacks; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/Parser.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/Parser.js new file mode 100644 index 000000000..0511b00fb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/Parser.js @@ -0,0 +1,33 @@ +import BracketParser from '../../bracketparser/BracketParser.js'; +import AddParseCallbacks from './AddParseCallbacks.js'; +import PreProcessSource from './PreProcessSource.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Parser extends BracketParser { + constructor(tagPlayer, config) { + if (config === undefined) { + config = {}; + } + if (!config.hasOwnProperty('delimiters')) { + config.delimiters = '[]'; + } + super(config); + + AddParseCallbacks(tagPlayer, this, config); + + this.setCommentLineStartSymbol(GetValue(config, 'comment', '//')); + } + + setCommentLineStartSymbol(symbol) { + this.commentLineStart = symbol; + return this; + } + + start(source) { + super.start(PreProcessSource(this, source)); + return this; + } +} + +export default Parser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/PreProcessSource.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/PreProcessSource.js new file mode 100644 index 000000000..a351f48c9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/PreProcessSource.js @@ -0,0 +1,28 @@ +/* +Skip line +- An empty line, only has space +- A comment line, start with commentLineStart ('//') +*/ + +var PreProcess = function (parser, source) { + var comentLineStart = parser.commentLineStart; + var lines = source.split('\n'); + for (var i = 0, cnt = lines.length; i < cnt; i++) { + var line = lines[i]; + if (line === '') { + // Do nothing + + } else if (line.trim().length === 0) { + // An empty line, only has space + lines[i] = ''; + + } else if (comentLineStart && line.startsWith(comentLineStart)) { + // A comment line, start with commentLineStart ('//') + lines[i] = ''; + } + } + + return lines.join(''); +} + +export default PreProcess; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseCrossFadeBackgroundMusicTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseCrossFadeBackgroundMusicTag.js new file mode 100644 index 000000000..3630eab1a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseCrossFadeBackgroundMusicTag.js @@ -0,0 +1,26 @@ +var OnParseCrossFadeBackgroundMusicTag = function (tagPlayer, parser, config) { + var tagName = 'bgm.cross'; + parser + .on(`+${tagName}`, function (name, fadeTime) { + tagPlayer.soundManager.crossFadeBackgroundMusic(name, fadeTime); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'bgm2.cross'; + parser + .on(`+${tagName}`, function (name, fadeTime) { + tagPlayer.soundManager.crossFadeBackgroundMusic2(name, fadeTime); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +export default OnParseCrossFadeBackgroundMusicTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseFadeInBackgroundMusicTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseFadeInBackgroundMusicTag.js new file mode 100644 index 000000000..121f916b5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseFadeInBackgroundMusicTag.js @@ -0,0 +1,26 @@ +var OnParseFadeInBackgroundMusicTag = function (tagPlayer, parser, config) { + var tagName = 'bgm.fadein'; + parser + .on(`+${tagName}`, function (time) { + tagPlayer.soundManager.fadeInBackgroundMusic(time); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'bgm2.fadein'; + parser + .on(`+${tagName}`, function (time) { + tagPlayer.soundManager.fadeInBackgroundMusic2(time); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +export default OnParseFadeInBackgroundMusicTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseFadeOutBackgroundMusicTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseFadeOutBackgroundMusicTag.js new file mode 100644 index 000000000..3bc93c210 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseFadeOutBackgroundMusicTag.js @@ -0,0 +1,28 @@ +var OnParseFadeOutBackgroundMusicTag = function (tagPlayer, parser, config) { + var tagName = 'bgm.fadeout'; + parser + .on(`+${tagName}`, function (time, isStopped) { + isStopped = (isStopped === 'stop'); + tagPlayer.soundManager.fadeOutBackgroundMusic(time, isStopped); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'bgm2.fadeout'; + parser + .on(`+${tagName}`, function (time, isStopped) { + isStopped = (isStopped === 'stop'); + tagPlayer.soundManager.fadeOutBackgroundMusic2(time, isStopped); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +export default OnParseFadeOutBackgroundMusicTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParsePauseBackgroundMusicTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParsePauseBackgroundMusicTag.js new file mode 100644 index 000000000..977224e8d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParsePauseBackgroundMusicTag.js @@ -0,0 +1,30 @@ +var OnParsePauseBackgroundMusicTag = function (tagPlayer, parser, config) { + var tagName = 'bgm.pause'; + parser + .on(`+${tagName}`, function () { + tagPlayer.soundManager.pauseBackgroundMusic(); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + tagPlayer.soundManager.resumeBackgroundMusic(); + + parser.skipEvent(); + }) + + + var tagName = 'bgm2.pause'; + parser + .on(`+${tagName}`, function () { + tagPlayer.soundManager.pauseBackgroundMusic2(); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + tagPlayer.soundManager.resumeBackgroundMusic2(); + + parser.skipEvent(); + }) +} + +export default OnParsePauseBackgroundMusicTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParsePlayBackgroundMusicTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParsePlayBackgroundMusicTag.js new file mode 100644 index 000000000..f237c8ef3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParsePlayBackgroundMusicTag.js @@ -0,0 +1,36 @@ +var OnParsePlayBackgroundMusicTag = function (tagPlayer, parser, config) { + var tagName = 'bgm'; + parser + .on(`+${tagName}`, function (name, fadeInTime) { + tagPlayer.soundManager.playBackgroundMusic(name); + if (fadeInTime) { + tagPlayer.soundManager.fadeInBackgroundMusic(fadeInTime); + } + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + tagPlayer.soundManager.stopBackgroundMusic(); + + parser.skipEvent(); + }) + + + var tagName = 'bgm2'; + parser + .on(`+${tagName}`, function (name, fadeInTime) { + tagPlayer.soundManager.playBackgroundMusic2(name); + if (fadeInTime) { + tagPlayer.soundManager.fadeInBackgroundMusic2(fadeInTime); + } + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + tagPlayer.soundManager.stopBackgroundMusic2(); + + parser.skipEvent(); + }) +} + +export default OnParsePlayBackgroundMusicTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseSetBackgroundMusicVolumeTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseSetBackgroundMusicVolumeTag.js new file mode 100644 index 000000000..a340d3ce1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/backgroundmusic/OnParseSetBackgroundMusicVolumeTag.js @@ -0,0 +1,26 @@ +var OnParseSetBackgroundMusicVolumeTag = function (tagPlayer, parser, config) { + var tagName = 'bgm.volume'; + parser + .on(`+${tagName}`, function (volume) { + tagPlayer.soundManager.setBackgroundMusicVolume(volume); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'bgm2.volume'; + parser + .on(`+${tagName}`, function (volume) { + tagPlayer.soundManager.setBackgroundMusicVolume2(volume); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +export default OnParseSetBackgroundMusicVolumeTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseFadeInCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseFadeInCameraTag.js new file mode 100644 index 000000000..51a872f6b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseFadeInCameraTag.js @@ -0,0 +1,11 @@ +var OnParseFadeInCameraTag = function (tagPlayer, parser, config) { + var tagName = 'camera.fadein'; + parser + .on(`+${tagName}`, function (duration, red, green, blue) { + tagPlayer.targetCamera.fadeIn(duration, red, green, blue); + + parser.skipEvent(); + }) +} + +export default OnParseFadeInCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseFadeOutCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseFadeOutCameraTag.js new file mode 100644 index 000000000..6bc08318f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseFadeOutCameraTag.js @@ -0,0 +1,11 @@ +var OnParseFadeOutCameraTag = function (tagPlayer, parser, config) { + var tagName = 'camera.fadeout'; + parser + .on(`+${tagName}`, function (duration, red, green, blue) { + tagPlayer.targetCamera.fadeOut(duration, red, green, blue); + + parser.skipEvent(); + }) +} + +export default OnParseFadeOutCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseFlashCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseFlashCameraTag.js new file mode 100644 index 000000000..df357a928 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseFlashCameraTag.js @@ -0,0 +1,11 @@ +var OnParseFlashCameraTag = function (tagPlayer, parser, config) { + var tagName = 'camera.flash'; + parser + .on(`+${tagName}`, function (duration, red, green, blue) { + tagPlayer.targetCamera.flash(duration, red, green, blue); + + parser.skipEvent(); + }) +} + +export default OnParseFlashCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseRotateCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseRotateCameraTag.js new file mode 100644 index 000000000..3a5d15ead --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseRotateCameraTag.js @@ -0,0 +1,19 @@ +const DegToRad = Phaser.Math.DegToRad; + +var OnParseRotateCameraTag = function (tagPlayer, parser, config) { + var tagName = 'camera.rotate'; + parser + .on(`+${tagName}`, function (value) { + tagPlayer.targetCamera.setRotation(DegToRad(value)); + + parser.skipEvent(); + }) + .on(`+${tagName}.to`, function (value, duration, ease) { + value = DegToRad(value); + tagPlayer.targetCamera.rotateTo(DegToRad(value), false, duration, ease); + + parser.skipEvent(); + }) +} + +export default OnParseRotateCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseScrollCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseScrollCameraTag.js new file mode 100644 index 000000000..710adad60 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseScrollCameraTag.js @@ -0,0 +1,26 @@ +var OnParseScrollCameraTag = function (tagPlayer, parser, config) { + var tagName = 'camera.scroll'; + parser + .on(`+${tagName}`, function (x, y) { + tagPlayer.targetCamera.setScroll(x, y); + + parser.skipEvent(); + }) + .on(`+${tagName}.to`, function (x, y, duration, ease) { + // this: tagPlayer + var camera = tagPlayer.targetCamera; + var xSave = camera.scrollX; + var ySave = camera.scrollY; + camera.setScroll(x, y); + x += camera.centerX; + y += camera.centerY; + camera.setScroll(xSave, ySave); + + // x,y in pan() is the centerX, centerY + camera.pan(x, y, duration, ease); + + parser.skipEvent(); + }) +} + +export default OnParseScrollCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseShakeCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseShakeCameraTag.js new file mode 100644 index 000000000..7ff958393 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseShakeCameraTag.js @@ -0,0 +1,11 @@ +var OnParseShakeCameraTag = function (tagPlayer, parser, config) { + var tagName = 'camera.shake'; + parser + .on(`+${tagName}`, function (duration, intensity) { + tagPlayer.targetCamera.shake(duration, intensity); + + parser.skipEvent(); + }) +} + +export default OnParseShakeCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseZoomCameraTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseZoomCameraTag.js new file mode 100644 index 000000000..435785dac --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/camera/OnParseZoomCameraTag.js @@ -0,0 +1,16 @@ +var OnParseZoomCameraTag = function (tagPlayer, parser, config) { + var tagName = 'camera.zoom'; + parser + .on(`+${tagName}`, function (value) { + tagPlayer.targetCamera.setZoom(value); + + parser.skipEvent(); + }) + .on(`+${tagName}.to`, function (value, duration, ease) { + tagPlayer.targetCamera.zoomTo(value, duration, ease); + + parser.skipEvent(); + }) +} + +export default OnParseZoomCameraTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/content/OnParseContent.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/content/OnParseContent.js new file mode 100644 index 000000000..2189983d6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/content/OnParseContent.js @@ -0,0 +1,32 @@ +var OnParseContent = function (tagPlayer, parser, config) { + parser + .on('content', function (content) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + if (content === '\n') { + return; + } + + content = content.replaceAll('\\n', '\n'); + + var callback = tagPlayer.contentCallback; + if (callback) { + var scope = tagPlayer.contentCallbackScope; + if (scope) { + callback.call(scope, content); + } else { + callback(content); + } + parser.skipEvent(); + } + + tagPlayer.emit(`+${parser.lastTagStart}#content`, content); + + // Route 'content' event to tagPlayer + tagPlayer.emit('content', content); + }) +} + +export default OnParseContent; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/custom/OnParseCustomTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/custom/OnParseCustomTag.js new file mode 100644 index 000000000..92b1642ae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/custom/OnParseCustomTag.js @@ -0,0 +1,19 @@ +var OnParseCustomTag = function (tagPlayer, parser, config) { + parser + .on('+', function (tagName, ...params) { + if (parser.skipEventFlag) { // Has been processed before + return; + } + + tagPlayer.emit(`+${tagName}`, parser, ...params); + }) + .on('-', function (tagName) { + if (parser.skipEventFlag) { + return; + } + + tagPlayer.emit(`-${tagName}`, parser); + }) +} + +export default OnParseCustomTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParseFadeInSoundEffectTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParseFadeInSoundEffectTag.js new file mode 100644 index 000000000..c24fb6a4d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParseFadeInSoundEffectTag.js @@ -0,0 +1,26 @@ +var OnParseFadeInSoundEffectTag = function (tagPlayer, parser, config) { + var tagName = 'se.fadein'; + parser + .on(`+${tagName}`, function (time) { + tagPlayer.soundManager.fadeInSoundEffect(time); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'se2.fadein'; + parser + .on(`+${tagName}`, function (time) { + tagPlayer.soundManager.fadeInSoundEffect2(time); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +export default OnParseFadeInSoundEffectTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParseFadeOutSoundEffectTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParseFadeOutSoundEffectTag.js new file mode 100644 index 000000000..31411a98c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParseFadeOutSoundEffectTag.js @@ -0,0 +1,28 @@ +var OnParseFadeOutSoundEffectTag = function (tagPlayer, parser, config) { + var tagName = 'se.fadeout'; + parser + .on(`+${tagName}`, function (time, isStopped) { + isStopped = (isStopped === 'stop'); + tagPlayer.soundManager.fadeOutSoundEffect(time, isStopped); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'se2.fadeout'; + parser + .on(`+${tagName}`, function (time, isStopped) { + isStopped = (isStopped === 'stop'); + tagPlayer.soundManager.fadeOutSoundEffect2(time, isStopped); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +export default OnParseFadeOutSoundEffectTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParsePlaySoundEffectTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParsePlaySoundEffectTag.js new file mode 100644 index 000000000..4a070f8e4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParsePlaySoundEffectTag.js @@ -0,0 +1,40 @@ +var OnParsePlaySoundEffectTag = function (tagPlayer, parser, config) { + var tagName = 'se'; + parser + .on(`+${tagName}`, function (name, fadeInTime) { + if (this.skipSoundEffect) { + return; + } + + tagPlayer.soundManager.playSoundEffect(name); // this: tagPlayer + if (fadeInTime) { + tagPlayer.soundManager.fadeInSoundEffect(fadeInTime); + } + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'se2'; + parser + .on(`+${tagName}`, function (name, fadeInTime) { + if (this.skipSoundEffect) { + return; + } + + tagPlayer.soundManager.playSoundEffect2(name); // this: tagPlayer + if (fadeInTime) { + tagPlayer.soundManager.fadeInSoundEffect2(fadeInTime); + } + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +export default OnParsePlaySoundEffectTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParseSetSoundEffectVolumeTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParseSetSoundEffectVolumeTag.js new file mode 100644 index 000000000..393597fca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/soundeffect/OnParseSetSoundEffectVolumeTag.js @@ -0,0 +1,26 @@ +var OnParseSetSoundEffectVolumeTag = function (tagPlayer, parser, config) { + var tagName = 'se.volume'; + parser + .on(`+${tagName}`, function (volume) { + tagPlayer.soundManager.setSoundEffectVolume(volume, true); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) + + + var tagName = 'se2.volume'; + parser + .on(`+${tagName}`, function (volume) { + tagPlayer.soundManager.setSoundEffectVolume2(volume, true); + + parser.skipEvent(); + }) + .on(`-${tagName}`, function () { + parser.skipEvent(); + }) +} + +export default OnParseSetSoundEffectVolumeTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/wait/OnParseWaitTag.js b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/wait/OnParseWaitTag.js new file mode 100644 index 000000000..41e727af6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/bracketparser/tagplayer/parser/wait/OnParseWaitTag.js @@ -0,0 +1,21 @@ +var OnParseWaitTag = function (tagPlayer, parser, config) { + var tagWait = 'wait'; + var tagClick = 'click'; + parser + .on(`+${tagWait}`, function (name) { + tagPlayer.wait(name); + parser.skipEvent(); + }) + .on(`-${tagWait}`, function () { + parser.skipEvent(); + }) + .on(`+${tagClick}`, function () { // Equal to [wait=click] + tagPlayer.wait('click'); + parser.skipEvent(); + }) + .on(`-${tagClick}`, function () { // Equal to [/wait] + parser.skipEvent(); + }) +} + +export default OnParseWaitTag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/conditiontable/ConditionsTable.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/conditiontable/ConditionsTable.d.ts new file mode 100644 index 000000000..0a7dda170 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/conditiontable/ConditionsTable.d.ts @@ -0,0 +1,54 @@ +export default ConditionsTable; + +declare namespace ConditionsTable { + + type ContextType = { + [name: string]: any + } + + type ResultsType = { + [name: string]: boolean + } + + type TestFunctionType = ( + context: ContextType + ) => boolean; + +} + +declare class ConditionsTable { + + clear(): this; + + add( + name: string, + callback: ConditionsTable.TestFunctionType + ): this; + + getTestResults( + context: ConditionsTable.ContextType, + ): ConditionsTable.ResultsType; + + anyPassTest( + context: ConditionsTable.ContextType, + callback: (testName: string) => void, + scope?: object + ): this; + + anyPassTest( + context: ConditionsTable.ContextType + ): string; + + eachPassTest( + context: ConditionsTable.ContextType, + callback: (testName: string) => void, + scope?: object + ): this; + + eachTest( + context: ConditionsTable.ContextType, + callback: (testName: string, result: boolean) => void, + scope?: object + ): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/conditiontable/ConditionsTable.js b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/conditiontable/ConditionsTable.js new file mode 100644 index 000000000..5e3f7fc94 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/conditiontable/ConditionsTable.js @@ -0,0 +1,93 @@ +class ConditionsTable { + constructor() { + this.tests = []; // [{name, function}, ...] + } + + clear() { + this.tests.length = 0; + return this; + } + + add(name, callback) { + this.tests.push({ + name: name, + function: callback + }); + return this; + } + + getTestResults(context) { + var results = {}; // {name: boolean} + var name, f; + for (var i = 0, cnt = this.tests.length; i < cnt; i++) { + name = this.tests[i].name; + f = this.tests[i].function; + + if (f(context)) { + results[name] = true; + } else if (!results.hasOwnProperty(name)) { + results[name] = false; + } + } + + return results; + } + + anyPassTest(context, callback, scope) { + var name, f; + for (var i = 0, cnt = this.tests.length; i < cnt; i++) { + name = this.tests[i].name; + f = this.tests[i].function; + if (!f(context)) { + name = false; + continue; + } + + if (callback) { + if (scope) { + callback.call(scope, name); + } else { + callback(name); + } + } + break; + } + + return (callback) ? this : name; + } + + eachPassTest(context, callback, scope) { + var name, f; + for (var i = 0, cnt = this.tests.length; i < cnt; i++) { + name = this.tests[i].name; + f = this.tests[i].function; + if (!f(context)) { + continue; + } + + if (scope) { + callback.call(scope, name); + } else { + callback(name); + } + } + return this; + } + + eachTest(context, callback, scope) { + var pass, name, f; + for (var i = 0, cnt = this.tests.length; i < cnt; i++) { + name = this.tests[i].name; + f = this.tests[i].function; + pass = f(context); + if (scope) { + callback.call(scope, name, pass); + } else { + callback(name, pass); + } + } + return this; + } +} + +export default ConditionsTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/csvconditiontable/ConditionsTable.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/csvconditiontable/ConditionsTable.d.ts new file mode 100644 index 000000000..28e8ecf20 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/csvconditiontable/ConditionsTable.d.ts @@ -0,0 +1,17 @@ +import Base from '../conditiontable/ConditionsTable'; + +export default ConditionsTable; + +declare namespace ConditionsTable { + interface ILoadConfig { + delimiter?: string + } +} + +declare class ConditionsTable extends Base { + + loadCSV( + csvString: string, + config?: ConditionsTable.ILoadConfig + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/csvconditiontable/ConditionsTable.js b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/csvconditiontable/ConditionsTable.js new file mode 100644 index 000000000..72c0f4e33 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/csvconditiontable/ConditionsTable.js @@ -0,0 +1,27 @@ +import Base from '../conditiontable/ConditionsTable.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import CSVParser from 'papaparse/papaparse.min.js'; +import CreateTestFunction from './CreateTestFunction.js'; + +class ConditionsTable extends Base { + loadCSV(csvString, config) { + this.clear(); + + var delimiter = GetValue(config, 'delimiter', ','); + var table = CSVParser.parse(csvString, { + delimiter: delimiter + }).data; + var keys = table[0]; + keys.shift(); + var items, testName; + for (var i = 1, cnt = table.length; i < cnt; i++) { + items = table[i]; + testName = items.shift(); + this.add(testName, CreateTestFunction(keys, items)); + } + return this; + } + +} + +export default ConditionsTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/csvconditiontable/CreateTestFunction.js b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/csvconditiontable/CreateTestFunction.js new file mode 100644 index 000000000..c4130da36 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/csvconditiontable/CreateTestFunction.js @@ -0,0 +1,37 @@ +var CreateTestFunction = function (keys, equations) { + var conditions = []; + for (var i = 0, cnt = keys.length; i < cnt; i++) { + if ((equations[i] === '') || (equations[i] == null)) { + continue; + } + conditions.push( + CreateComparisonLogic(keys[i], equations[i]) + ); + } + + var logic = (conditions.length > 0) ? conditions.join('&&') : 'false'; + var f = new Function('values', 'return ' + logic); + return f; +}; + +var IsEquation = function (s) { + return (s.indexOf('==') != -1) || + (s.indexOf('!=') != -1) || + (s.indexOf('>=') != -1) || + (s.indexOf('<=') != -1) || + (s.indexOf('>') != -1) || + (s.indexOf('<') != -1); +}; + +var CreateComparisonLogic = function (key, equation) { + if (!IsEquation(equation)) { + if (isNaN(equation)) { + equation = `'${equation}'`; + } + + equation = `==(${equation})`; + } + return `(values['${key}']${equation})`; +}; + +export default CreateTestFunction; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/ymlconditiontable/ConditionsTable.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/ymlconditiontable/ConditionsTable.d.ts new file mode 100644 index 000000000..2ae78146d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/ymlconditiontable/ConditionsTable.d.ts @@ -0,0 +1,13 @@ +import Base from '../conditiontable/ConditionsTable'; + +export default ConditionsTable; + +declare namespace ConditionsTable { +} + +declare class ConditionsTable extends Base { + + loadYML( + ymlString: string + ): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/ymlconditiontable/ConditionsTable.js b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/ymlconditiontable/ConditionsTable.js new file mode 100644 index 000000000..f5d562103 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/conditionstable/ymlconditiontable/ConditionsTable.js @@ -0,0 +1,33 @@ +import Base from '../conditiontable/ConditionsTable.js'; +import ParseYaml from '../../../utils/yaml/ParseYaml.js'; +import CreateTestFunction from '../../../math/expressionparser/utils/Complile.js'; + +class ConditionsTable extends Base { + loadYML(ymlString) { + this.clear(); + + var doc = ParseYaml(ymlString); + if (!doc) { + return this; + } + + if (Array.isArray(doc)) { + var docArray = doc; + for (var i = 0, cnt = docArray.length; i < cnt; i++) { + doc = docArray[i]; + for (var testName in doc) { + this.add(testName, CreateTestFunction(doc[testName])); + } + } + } else { + for (var testName in doc) { + this.add(testName, CreateTestFunction(doc[testName])); + } + } + + return this; + } + +} + +export default ConditionsTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/EventBehaviorTree.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/EventBehaviorTree.js new file mode 100644 index 000000000..000d1283b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/EventBehaviorTree.js @@ -0,0 +1,32 @@ +import { BehaviorTree, IfSelector } from '../../behaviortree'; + +class EventBehaviorTree extends BehaviorTree { + constructor(config) { + if (config === undefined) { + config = {}; + } + super(config); + + var { parallel = false } = config + this.properties.parallel = parallel; + + var { condition = 'true' } = config; + var root = new IfSelector({ + title: this.title, + expression: condition, + returnPending: true // Always return PENDING instead of RUNNING, or SUCCESS + }) + this.setRoot(root); + } + + get isParallel() { + return this.properties.parallel; + } + + get eventConditionPassed() { + var nodeMemory = this.root.getNodeMemory(this.ticker); + return (nodeMemory.$runningChild === 0); + } +} + +export default EventBehaviorTree; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/EventSheetTrees.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/EventSheetTrees.d.ts new file mode 100644 index 000000000..a8d96a97f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/EventSheetTrees.d.ts @@ -0,0 +1,50 @@ +import { EventEmitter } from 'eventemitter3'; + +export default EventSheetTrees; + +declare namespace EventSheetTrees { + interface IConfig { + taskHandlers?: Object, + parallel?: boolean, + } +} + +declare class EventSheetTrees extends EventEmitter { + constructor(config?: EventSheetTrees.IConfig); + + readonly memory: { [key: string]: any }; + + setTaskHandlers(taskHandlers?: Object): this; + + addEventSheet(content?: string, config?: any): this; + + clearAllEventSheets(): this; + + getEventSheetTitleList(): string[]; + + removeEventSheet(title: string): this; + + dumpTrees(): Object[]; + + loadTrees(data: Object[]): this; + + setData(key: string, value: any): this; + + hasData(key: string): this; + + toggleData(key: string): this; + + getData(key: string): any; + + dumpState(includeTree?: boolean): Object; + + loadState(state: Object): this; + + evalExpression(expression: any): any; + + renderString(template: string): string; + + start(): this; + + stop(): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/EventSheetTrees.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/EventSheetTrees.js new file mode 100644 index 000000000..e4cf38688 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/EventSheetTrees.js @@ -0,0 +1,53 @@ +import EventEmitter from 'eventemitter3'; +import { BehaviorTree, Blackboard } from '../../behaviortree'; +import TreeMethods from './methods/TreeMethods.js'; +import DataMethods from './methods/DataMethods.js'; +import StateMethods from './methods/StateMethods'; +import ValueConvertMethods from './methods/ValueConvertMethods'; +import RunMethods from './methods/RunMethods.js'; + +BehaviorTree.setStartIDValue(0); + +class EventSheetTrees extends EventEmitter { + constructor({ + taskHandlers, + parallel = false, + } = {}) { + + super(); + + this.setTaskHandlers(taskHandlers); + this.parallel = parallel; + + this.blackboard = new Blackboard(); + this.blackboard.treeManager = this; // For TaskAction + + this.trees = []; + this.pendingTrees = []; + this.closedTrees = []; // Temporary tree array + + this.isRunning = false; + this._threadKey = null; + } + + get memory() { + return this.blackboard.getGlobalMemory(); + } + + setTaskHandlers(taskHandlers) { + this.taskHandlers = taskHandlers; + return this; + } + +} + +Object.assign( + EventSheetTrees.prototype, + TreeMethods, + DataMethods, + StateMethods, + ValueConvertMethods, + RunMethods, +) + +export default EventSheetTrees; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/TaskAction.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/TaskAction.js new file mode 100644 index 000000000..10df7a529 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/TaskAction.js @@ -0,0 +1,73 @@ +import { Action, } from '../../behaviortree'; +import IsEventEmitter from '../../../utils/system/IsEventEmitter.js'; +import DeepClone from '../../../utils/object/DeepClone.js'; + +class TaskAction extends Action { + constructor(config) { + // config: {name, parameters:{...} } + super({ + name: 'TaskAction', + title: config.name, + properties: config, + }); + + this.isRunning = false; + } + + open(tick) { + this.isRunning = false; + + var taskData = this.properties; + + var taskName = taskData.name; + if (!taskName) { + return; + } + + var treeManager = tick.blackboard.treeManager; + var taskParameters = DeepClone(taskData.parameters); + var taskHandlers = tick.target; + var handler = taskHandlers[taskName]; + if (!handler) { + if (taskHandlers.getHandler) { + handler = taskHandlers.getHandler(taskName, taskParameters, treeManager); + } + if (!handler) { + return; + } + } + + var eventEmitter = handler.call(taskHandlers, taskParameters, treeManager); + if (IsEventEmitter(eventEmitter)) { + this.isRunning = true; + + eventEmitter.once('complete', this.onTaskComplete, this); + + this.continueCallback = treeManager.continue.bind(treeManager); + this.continueEE = eventEmitter; + } + } + + onTaskComplete() { + this.isRunning = false; + this.continueEE = undefined; + + this.continueCallback(); + } + + tick(tick) { + return (this.isRunning) ? this.RUNNING : this.SUCCESS; + } + + close(tick) { + } + + abort(tick) { + if (this.continueEE) { + this.continueEE.off('complete', this.onTaskComplete, this); + this.continueEE = undefined; + } + } +} + +export default TaskAction \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/TaskSequence.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/TaskSequence.js new file mode 100644 index 000000000..72f2f47c8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/TaskSequence.js @@ -0,0 +1,29 @@ +import { Sequence, SUCCESS, FAILURE } from '../../behaviortree'; + +class TaskSequence extends Sequence { + open(tick) { + super.open(tick); + + var treeManager = tick.blackboard.treeManager; + treeManager.emit('label.enter', this.title, treeManager); + + } + + tick(tick) { + var status = super.tick(tick); + // Turn FAILURE by SUCCESS + if (status === FAILURE) { + status = SUCCESS; + } + return status; + } + + close(tick) { + super.close(tick); + + var treeManager = tick.blackboard.treeManager; + treeManager.emit('label.exit', this.title, treeManager); + } +} + +export default TaskSequence; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/Tree.md b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/Tree.md new file mode 100644 index 000000000..e2490f8f3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/Tree.md @@ -0,0 +1,21 @@ +# Event sheet tree + +## Tree architecture + +```mermaid +graph LR + Tree --> Condition["Condition
    IfSelector"] + Condition --> Labels["Labels
    Sequence"] + Labels --> Tasks0["Label--Tasks
    Sequence"] + Labels --> IfSelector["If
    Selector"] + IfSelector --> If["If
    If decorator"] + If --> IfLabels["Labels
    Sequence"] + IfLabels --> Tasks1["Label--Tasks
    Sequence"] + + IfSelector --> ElseIf["Else If
    If decorator"] + ElseIf --> ElseIfLabels["Labels
    Sequence"] + ElseIfLabels --> Tasks2["Label--Tasks
    Sequence"] + + Condition --> Catch["Catch
    ForceFailure"] + Catch --> CatchTasks["Tasks
    Sequence"] +``` diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/CustomNodeMapping.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/CustomNodeMapping.js new file mode 100644 index 000000000..58efc4613 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/CustomNodeMapping.js @@ -0,0 +1,5 @@ +import TaskAction from '../TaskAction.js'; + +export default { + TaskAction: TaskAction, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/DataMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/DataMethods.js new file mode 100644 index 000000000..192938673 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/DataMethods.js @@ -0,0 +1,24 @@ +export default { + setData(key, value) { + this.blackboard.setData(key, value); + return this; + }, + + hasData(key) { + return this.blackboard.hasData(key); + }, + + incData(key, inc) { + this.blackboard.incData(key, inc); + return this; + }, + + toggleData(key) { + this.blackboard.toggleData(key); + return this; + }, + + getData(key) { + return this.blackboard.getData(key); + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/RunMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/RunMethods.js new file mode 100644 index 000000000..64a89ffb3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/RunMethods.js @@ -0,0 +1,123 @@ +import { RUNNING, PENDING, IDLE, SUCCESS } from '../../../behaviortree'; +import RemoveItem from '../../../../utils/array/Remove.js'; + +export default { + start() { + if (this.isRunning) { + return this; + } + + this.isRunning = true; + + var trees = this.trees; + var pendingTrees = this.pendingTrees; + var blackboard = this.blackboard; + var taskHandlers = this.taskHandlers; + + pendingTrees.length = 0; + + // Run parallel tree, will return pending, or failure + for (var i = 0, cnt = trees.length; i < cnt; i++) { + var tree = trees[i]; + + tree.resetState(blackboard); + if (tree.isParallel) { + var status = tree.tick(blackboard, taskHandlers); + if (status === PENDING) { + pendingTrees.push(tree); + } + } else { + pendingTrees.push(tree); + } + } + + this.continue(); + + return this; + }, + + continue() { + if (!this.isRunning) { + return this; + } + + var trees = this.pendingTrees; + var closedTrees = this.closedTrees; + var blackboard = this.blackboard; + var taskHandlers = this.taskHandlers; + + closedTrees.length = 0; + for (var i = 0, cnt = trees.length; i < cnt; i++) { + var tree = trees[i]; + var status = blackboard.getTreeState(tree.id); + + if (status === IDLE) { + // Will goto PENDING, or FAILURE/ERROR state + status = tree.tick(blackboard, taskHandlers); + } + + var eventConditionPassed = tree.eventConditionPassed; + if ((status === PENDING)) { + if (eventConditionPassed) { + this.emit('eventsheet.enter', tree.title, this); + } else { + this.emit('eventsheet.catch', tree.title, this); + } + } + + if (!this.isRunning) { + // Can break here + break; + } + + // Will goto RUNNING, or SUCCESS/FAILURE/ERROR state + status = tree.tick(blackboard, taskHandlers); + + if (status === RUNNING) { + break; + } else { + closedTrees.push(tree); + if (eventConditionPassed) { + this.emit('eventsheet.exit', tree.title, this); + } + } + + if (!this.isRunning) { + // Can break here + break; + } + + } + + if (closedTrees.length > 0) { + RemoveItem(trees, closedTrees); + } + + if (trees.length === 0) { + this.isRunning = false; + this.emit('complete', this); + } + + return this; + }, + + stop() { + this.isRunning = false; + + var blackboard = this.blackboard; + var taskHandlers = this.taskHandlers; + this.pendingTrees.forEach(function (tree) { + tree.abort(blackboard, taskHandlers); + }) + this.pendingTrees.length = 0; + + return this; + }, + + getContinueCallback() { + var self = this; + return function () { + self.continue(); + } + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/StateMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/StateMethods.js new file mode 100644 index 000000000..d303c55a4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/StateMethods.js @@ -0,0 +1,46 @@ +export default { + dumpState(includeTree) { + if (includeTree === undefined) { + includeTree = false; + } + + var state = { + blackboard: this.blackboard.dump(), + isRunning: this.isRunning, + pendingTrees: this.pendingTrees.map(function (tree) { + return tree.id; + }) + } + + if (includeTree) { + state.trees = this.dumpTrees(); + } + return state; + }, + + loadState(state) { + this.stop(); + + if (state.trees) { + this.trees.length = 0; + this.loadTrees(state.trees); + } + + this.blackboard.load(state.blackboard); + this.isRunning = state.isRunning; + + var pendingTrees = this.pendingTrees; + pendingTrees.length = 0; + this.trees.forEach(function (tree) { + if (state.pendingTrees.indexOf(tree.id) > -1) { + pendingTrees.push(tree); + } + }) + + if (this.isRunning) { + this.continue(); + } + + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/TreeMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/TreeMethods.js new file mode 100644 index 000000000..3190fd959 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/TreeMethods.js @@ -0,0 +1,84 @@ +import CustomNodeMapping from './CustomNodeMapping.js'; +import RemoveItem from '../../../../utils/array/Remove.js'; +import { BehaviorTree, PENDING, RUNNING } from '../../../behaviortree'; +import DeepClone from '../../../../utils/object/DeepClone.js'; + +export default { + // Override it + addEventSheet(s, config) { + + }, + + addTree(tree) { + this.trees.push(tree); + return this; + }, + + getTreeState(tree) { + var treeID = (typeof (tree) === 'string') ? tree : tree.id; + return this.blackboard.getTreeState(treeID); + }, + + clearAllEventSheets() { + this.trees.forEach(function (tree) { + this.blackboard.removeTreeData(tree.id); + }, this) + this.trees.length = 0; + this.pendingTrees.length = 0; + return this; + }, + + getEventSheetTitleList(out) { + if (out === undefined) { + out = []; + } + this.trees.forEach(function (tree) { + out.push(tree.title); + }) + return out; + }, + + removeEventSheet(title) { + var removedTrees = []; + this.trees.forEach(function (tree) { + if (!tree.title === title) { + return; + } + var status = this.getTreeState(tree); + if (status === RUNNING) { + // Can't remove RUNNING tree + return; + } + + removedTrees.push(tree); + this.blackboard.removeTreeData(tree.id); + }, this); + + if (removedTrees.length > 0) { + RemoveItem(this.trees, removedTrees); + RemoveItem(this.pendingTrees, removedTrees); + } + + return this; + }, + + dumpTrees() { + return this.trees.map(function (tree) { + return tree.dump() + }) + }, + + loadTrees(data) { + data.forEach(function (treeData) { + var tree = new BehaviorTree({ + id: treeData.id, + title: treeData.title, + properties: DeepClone(treeData.properties), + }); + tree.load(treeData, CustomNodeMapping); + this.trees.push(tree); + }, this); + return this; + }, + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/ValueConvertMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/ValueConvertMethods.js new file mode 100644 index 000000000..b1927412d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/eventsheettrees/methods/ValueConvertMethods.js @@ -0,0 +1,16 @@ +import Compile from '../../../../math/expressionparser/utils/Complile.js'; +import mustache from 'mustache'; + +export default { + evalExpression(expression) { + if (typeof (expression) === 'number') { + return expression; + } + + return Compile(expression)(this.memory); + }, + + renderString(template) { + return mustache.render(template, this.memory); + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/MarkedEventSheets.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/MarkedEventSheets.d.ts new file mode 100644 index 000000000..0066ccfcd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/MarkedEventSheets.d.ts @@ -0,0 +1,23 @@ +import EventSheetTrees from '../eventsheettrees/EventSheetTrees'; + +export default MarkedEventSheets; + +declare namespace MarkedEventSheets { + interface IConfig extends EventSheetTrees.IConfig { + + } + + interface IAddEventSheet { + lineReturn?: string, + commentLineStart?: string, + parallel?: boolean, + } +} + +declare class MarkedEventSheets extends EventSheetTrees { + addEventSheet( + content: string, + config?: MarkedEventSheets.IAddEventSheet + ): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/MarkedEventSheets.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/MarkedEventSheets.js new file mode 100644 index 000000000..d7b3b5c1a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/MarkedEventSheets.js @@ -0,0 +1,22 @@ +import EventSheetTrees from '../eventsheettrees/EventSheetTrees.js'; +import Marked2Tree from './methods/Marked2Tree.js'; + +class MarkedEventSheets extends EventSheetTrees { + addEventSheet(markedString, { + lineReturn = '\\', + commentLineStart = '\/\/', + parallel = this.parallel, + } = {}) { + + var tree = Marked2Tree(markedString, { + lineReturn, + commentLineStart, + parallel + }); + + this.addTree(tree); + return this; + } +} + +export default MarkedEventSheets; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/CreateTaskSequence.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/CreateTaskSequence.js new file mode 100644 index 000000000..8089b9e8a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/CreateTaskSequence.js @@ -0,0 +1,193 @@ +import { Sequence, Selector, If, Succeeder, RepeatUntilFailure, Abort, Failer } from '../../../behaviortree'; +import GetNodeType from './GetNodeType.js'; +import GetConditionExpression from './GetConditionExpression'; +import ParseProperty from './ParseProperty'; +import TaskSequence from '../../eventsheettrees/TaskSequence'; +import TaskAction from '../../eventsheettrees/TaskAction.js'; + +var TypeNames = ['if', 'else', 'while']; + +var CreateTaskSequence = function (node, config) { + + if (Array.isArray(node)) { + var nodes = node; + if (nodes.length === 1) { + return CreateTaskSequence(nodes[0], config); + + } else { + var sequence = new Sequence({ title: '[root]' }); + var lastIfSelector; + for (var i = 0, cnt = nodes.length; i < cnt; i++) { + var node = nodes[i]; + var child = CreateTaskSequence(node, config); + // Construct if-branch selector + switch (child.title) { + case '[if]': + sequence.addChild(child); + lastIfSelector = child; + break; + + case '[else]': + if (lastIfSelector) { + lastIfSelector.insertChild(child, null, -1); + } else { + // No [If] heading before this [else] heading + console.warn(`Can't find [If] heading before '${node.title}'`); + } + break; + + default: // Normal tasks + sequence.addChild(child); + lastIfSelector = null; + break; + } + + } + return sequence; + + } + + } else { + var nodeType = GetNodeType(node, TypeNames); + switch (nodeType) { + case 'if': + var selector = new Selector({ + title: '[if]' + }); + + var ifDecorator = new If({ + expression: GetConditionExpression(node) + }); + ifDecorator.addChild(CreateTaskSequence(node.children, config)); + selector.addChild(ifDecorator) + + var succeeder = new Succeeder(); + selector.addChild(succeeder); + + return selector; + + case 'else': + var ifDecorator = new If({ + title: '[else]', + expression: GetConditionExpression(node) + }); + ifDecorator.addChild(CreateTaskSequence(node.children, config)); + + return ifDecorator; + + case 'while': + var whileDecorator = new RepeatUntilFailure({ + title: '[while]', + returnSuccess: true, + }) + var ifDecorator = new If({ + title: '[while]', + expression: GetConditionExpression(node) + }); + ifDecorator.addChild(CreateTaskSequence(node.children, config)); + whileDecorator.addChild(ifDecorator); + return whileDecorator; + + default: + var sequence = new TaskSequence({ title: node.title }); + var paragraphs = node.paragraphs; // paragraphs -> TaskAction[] + for (var i = 0, cnt = paragraphs.length; i < cnt; i++) { + var commandData = GetCommandData(paragraphs[i], config); + if (!commandData) { + continue; + } + + var commandType = commandData.type; + delete commandData.type; + + var actionNode; + switch (commandType) { + case 'exit': + actionNode = new Abort({ title: '[exit]' }); + break; + + case 'break': + actionNode = new Failer({ title: '[break]' }); + break; + + default: + actionNode = new TaskAction(commandData); + break; + } + + sequence.addChild(actionNode); + + } + return sequence; + } + } +} + +var GetCommandData = function (paragraph, config) { + var commandData; + if (paragraph.hasOwnProperty('block')) { + commandData = ParseCommandString(paragraph.block, ',', config); + commandData.parameters.text = paragraph.text; + } else { + commandData = ParseCommandString(paragraph.text, '\n', config); + } + + return commandData; +} + +var ParseCommandString = function (commandString, delimiter, { + lineReturn = '\\', + commentLineStart = '\/\/', +} = {}) { + var lines = commandString.split(delimiter); + + if (delimiter === '\n') { + // Discard comment lines + lines = lines.filter(function (line) { + return !line.trimLeft().startsWith(commentLineStart); + }) + + if (lines.length === 0) { + return null; + } else if (lines.length === 1) { + var line = lines[0]; + if (IsExitCommand(line)) { + return { type: 'exit' }; + } else if (IsBreakLabelCommand(line)) { + return { type: 'break' }; + } else if (line.indexOf(',') !== -1) { + lines = commandString.split(','); + } + } + } + + var commandData = { + type: 'task', + name: TrimString(lines[0], lineReturn), + parameters: {} + }; + + var parameters = commandData.parameters; + for (var i = 1, cnt = lines.length; i < cnt; i++) { + ParseProperty(TrimString(lines[i], lineReturn), parameters); + } + return commandData; +} + +var TrimString = function (s, lineReturn) { + if (lineReturn && (s.at(-1) === lineReturn)) { + s = s.substring(0, s.length - 1); + } + return s.trimLeft(); +} + +var IsExitCommand = function (s) { + return s.trim().toLowerCase() === '[exit]'; +} + +var IsBreakLabelCommand = function (s) { + return s.trim().toLowerCase() === '[break]'; +} + + +export default CreateTaskSequence; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetConditionExpression.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetConditionExpression.js new file mode 100644 index 000000000..6c372deb9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetConditionExpression.js @@ -0,0 +1,56 @@ +var GetConditionExpression = function (nodes) { + if (!Array.isArray(nodes)) { + return GetANDExpression(nodes); + } + + var expression; + switch (nodes.length) { + case 0: + expression = 'true'; + break; + + case 1: + expression = GetANDExpression(nodes[0]); + break; + + default: + expression = nodes.map(function (node) { + return `(${GetANDExpression(node)})` + }).join(' || '); + break; + } + + return expression; +} + +var GetANDExpression = function (node) { + var paragraphs = node.paragraphs; + var lines = []; + for (var i = 0, cnt = paragraphs.length; i < cnt; i++) { + var paragraph = paragraphs[i]; + if (paragraph.hasOwnProperty('block')) { + continue; + } + + lines.push(...paragraph.text.split('\n')) + } + + var expression; + switch (lines.length) { + case 0: + expression = 'true'; + break; + + case 1: + expression = lines[0]; + break; + + default: + expression = lines.map(function (line) { return `(${line})` }).join(' && '); + break; + + } + return expression; +} + +export default GetConditionExpression; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetHeadingTree.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetHeadingTree.js new file mode 100644 index 000000000..bbfd28e6b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetHeadingTree.js @@ -0,0 +1,62 @@ +import marked from '../../../../utils/marked/marked.min.js'; + +var GetHeadingTree = function (text) { + var items = marked.lexer(text); + + var tree = null; + var parents = []; + for (var i = 0; i < items.length; i++) { + var item = items[i]; + switch (item.type) { + case 'heading': + var level = item.depth - 1; + // First node + if (tree === null) { + if (level === 0) { + var node = CreateNewNode(item.text); + parents.push(node); + tree = node; + } + // Ignore items if tree is null + } else { + if (level <= parents.length) { + var node = CreateNewNode(item.text); + parents.length = level; + var lastNode = parents[parents.length - 1]; + lastNode.children.push(node); + parents.push(node); + } + // Ignore items if out of parents + } + break; + + case 'paragraph': + case 'code': + if (parents.length === 0) { + continue; + } + // Append raw to last-node + var lastNode = parents[parents.length - 1]; + var node = { text: item.text }; + if (item.lang) { + node.block = item.lang; + } + lastNode.paragraphs.push(node); + break; + + // Ignore other kinds of items + } + } + + return tree; +} + +var CreateNewNode = function (title) { + return { + title: title, + paragraphs: [], + children: [], + } +} + +export default GetHeadingTree; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetNodeType.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetNodeType.js new file mode 100644 index 000000000..09532636b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetNodeType.js @@ -0,0 +1,13 @@ +var GetNodeType = function (node, typeNames) { + var title = node.title.toLowerCase(); + for (var i = 0, cnt = typeNames.length; i < cnt; i++) { + var typeName = typeNames[i]; + if (title.indexOf(`[${typeName}]`) > -1) { + return typeName; + } + } + + return '' +} + +export default GetNodeType \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetTreeConfig.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetTreeConfig.js new file mode 100644 index 000000000..dba41b2e6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/GetTreeConfig.js @@ -0,0 +1,15 @@ +import ParseProperty from './ParseProperty.js'; + +var GetTreeConfig = function (paragraphs) { + var config = {}; + paragraphs.forEach(function (paragraph) { + var lines = paragraph.text.split('\n'); + lines.forEach(function (line) { + ParseProperty(line, config); + }) + }) + + return config; +} + +export default GetTreeConfig; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/Marked2Tree.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/Marked2Tree.js new file mode 100644 index 000000000..4d2ba97e1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/Marked2Tree.js @@ -0,0 +1,43 @@ +import { ForceFailure, Succeeder } from '../../../behaviortree/index.js'; +import EventBehaviorTree from '../../eventsheettrees/EventBehaviorTree.js'; +import GetHeadingTree from './GetHeadingTree.js'; +import GetTreeConfig from './GetTreeConfig.js'; + +import ParseNodes from './ParseNodes.js'; +import GetConditionExpression from './GetConditionExpression.js'; +import CreateTaskSequence from './CreateTaskSequence.js'; + +var Marked2Tree = function (markedString, { + lineReturn = '\\', + commentLineStart = '\/\/', + parallel = false, +} = {}) { + + var headingTree = GetHeadingTree(markedString); + var treeConfig = GetTreeConfig(headingTree.paragraphs); + var { conditionNodes, mainTaskNodes, catchNodes } = ParseNodes(headingTree.children); + + var { parallel = parallel } = treeConfig; + var taskSequenceConfig = { lineReturn, commentLineStart }; + + var tree = new EventBehaviorTree({ + title: headingTree.title, + parallel: parallel, + condition: GetConditionExpression(conditionNodes) + }) + + var rootNode = tree.root; + rootNode.addChild(CreateTaskSequence(mainTaskNodes, taskSequenceConfig)); + + var forceFailure = new ForceFailure(); + if (catchNodes.length > 0) { + forceFailure.addChild(CreateTaskSequence(catchNodes[0], taskSequenceConfig)); + } else { + forceFailure.addChild(new Succeeder()); + } + rootNode.addChild(forceFailure); + + return tree; +} + +export default Marked2Tree; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/ParseNodes.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/ParseNodes.js new file mode 100644 index 000000000..d69a4d1fc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/ParseNodes.js @@ -0,0 +1,56 @@ +import GetNodeType from './GetNodeType.js'; + +const STATE_CONDITION = 1; +const STATE_TASK = 2; +const STATE_CATCH = 3; + +var TypeNames = ['condition', 'catch']; + +var ParseNodes = function (nodes) { + var conditionNodes = []; + var mainTaskNodes = []; + var catchNodes = []; + + var state = STATE_CONDITION; + var nextNodeType = GetNodeType(nodes[0], TypeNames); + for (var i = 0, cnt = nodes.length; i < cnt; i++) { + var node = nodes[i]; + if (state === STATE_CONDITION) { + if (nextNodeType === '') { + state = STATE_TASK; + } else if (nextNodeType === 'catch') { + state = STATE_CATCH; + } + } else if (state === STATE_TASK) { + if (nextNodeType === 'catch') { + state = STATE_CATCH; + } + } + + switch (state) { + case STATE_CONDITION: + conditionNodes.push(node) + break; + + case STATE_TASK: + mainTaskNodes.push(node); + break; + + case STATE_CATCH: + catchNodes.push(node); + break; + } + + if ((i + 1) < cnt) { + nextNodeType = GetNodeType(nodes[i + 1], TypeNames); + } + } + + return { + conditionNodes: conditionNodes, + mainTaskNodes: mainTaskNodes, + catchNodes: catchNodes, + } +} + +export default ParseNodes; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/ParseProperty.js b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/ParseProperty.js new file mode 100644 index 000000000..b7b45f01a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/eventsheets/markedeventsheets/methods/ParseProperty.js @@ -0,0 +1,16 @@ +import TypeConvert from '../../../../utils/string/TypeConvert.js'; + +var ParseProperty = function (s, out) { + var index = s.indexOf('='); + if (index === -1) { + out[s] = true; + } else { + var name = s.substring(0, index); + var expression = s.substring(index + 1); + out[name] = TypeConvert(expression); + } + + return out; +} + +export default ParseProperty; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSM.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSM.d.ts new file mode 100644 index 000000000..d262264b3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSM.d.ts @@ -0,0 +1,64 @@ +import FSMBase from './FSMBase'; + +export default FSM; + +declare namespace FSM { + + interface IStateConfig extends FSMBase.IStateConfig { + update?: (time: number, delta: number) => void; + preupdate?: (time: number, delta: number) => void; + postupdate?: (time: number, delta: number) => void; + } + + interface IConfig extends FSMBase.IConfig { + scene?: Phaser.Scene; + } +} + +declare class FSM extends FSMBase { + constructor(config?: FSM.IConfig); + + addState( + name: string, + state: FSM.IStateConfig + ): this; + addState(state: FSM.IStateConfig): this; + + addStates( + states: { [name: string]: FSM.IStateConfig }, + ): this; + addStates( + states: FSM.IStateConfig[] + ): this; + + update( + time: number, + delta: number + ): void; + + preupdate( + time: number, + delta: number + ): void; + + postupdate( + time: number, + delta: number + ): void; + + startUpdate( + scene?: Phaser.Scene + ): this; + stopUpdate(): this; + + startPreUpdate( + scene?: Phaser.Scene + ): this; + stopPreUpdate(): this; + + startPostUpdate( + scene?: Phaser.Scene + ): this; + stopPostUpdate(): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSM.js b/ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSM.js new file mode 100644 index 000000000..c20c27974 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSM.js @@ -0,0 +1,137 @@ +import FSMBase from './FSMBase.js'; +import GetValue from '../../utils/object/GetValue.js'; +import HasListener from '../../utils/eventemitter/HasListener.js'; + +const StateProperties = ['next', 'exit', 'enter', 'update', 'preupdate', 'postupdate']; + +class FSM extends FSMBase { + /* + var config = { + start: 'A', // default: undefined + states: { + A: { + next: 'B', // function() { return 'B'; } + enter: function() {}, + exit: function() {}, + update: function(time, delta) {}, + preupdate: function(time, delta) {}, + postupdate: function(time, delta) {}, + }, + // ... + }, + extend: { + i: 0, + name: 'abc' + // ... + }, + init: function() {}, + enable: true, + scene: undefined, + eventEmitter: true, + }; + */ + shutdown() { + this.stopUpdate(); + this.stopPreUpdate(); + this.stopPostUpdate(); + this._scene = undefined; + + super.shutdown(); + } + + resetFromJSON(o) { + super.resetFromJSON(o); + this._scene = GetValue(o, 'scene', undefined); + return this; + } + + get stateProperties() { + return StateProperties; + } + + update(time, delta) { + this.runMethod('update', time, delta); + } + + preupdate(time, delta) { + this.runMethod('preupdate', time, delta); + } + + postupdate(time, delta) { + this.runMethod('postupdate', time, delta); + } + + startUpdate(scene) { + if (!scene) { + scene = this._scene; + } + + var eventEmitter = scene.sys.events; + if (HasListener(eventEmitter, 'update', this.update, this)) { + return this; + } + + this._scene = scene; + eventEmitter.on('update', this.update, this); + return this; + } + + stopUpdate() { + if (!this._scene) { + return this; + } + + this._scene.sys.events.off('update', this.update, this); + return this; + } + + startPreUpdate(scene) { + if (!scene) { + scene = this._scene; + } + + var eventEmitter = scene.sys.events; + if (HasListener(eventEmitter, 'preupdate', this.preupdate, this)) { + return this; + } + + this._scene = scene; + eventEmitter.on('preupdate', this.preupdate, this); + return this; + } + + stopPreUpdate() { + if (!this._scene) { + return this; + } + + this._scene.sys.events.off('preupdate', this.preupdate, this); + return this; + } + + startPostUpdate(scene) { + if (!scene) { + scene = this._scene; + } + + var eventEmitter = scene.sys.events; + if (HasListener(eventEmitter, 'postupdate', this.postupdate, this)) { + return this; + } + + this._scene = scene; + eventEmitter.on('postupdate', this.postupdate, this); + return this; + } + + stopPostUpdate() { + if (!this._scene) { + return this; + } + + this._scene.sys.events.off('postupdate', this.postupdate, this); + return this; + } +} + +export default FSM; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSMBase.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSMBase.d.ts new file mode 100644 index 000000000..4a3edc4e0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSMBase.d.ts @@ -0,0 +1,66 @@ +import EventEmitter from '../../utils/eventemitter/EventEmitter'; + +export default FSMBase; + +declare namespace FSMBase { + + interface IStateConfig { + name?: string, + next?: string | (() => string), + enter?: Function, + exit?: Function, + } + + interface IConfig { + start?: string, + states?: { [name: string]: IStateConfig }, + + init?: Function, + + extend?: { + [name: string]: any, + }, + + enable?: boolean, + + eventEmitter?: EventEmitter | false, + } + + namespace Events { + type StateChangeCallbackType = (state: FSMBase) => void; + type ExitStateCallbackType = (state: FSMBase) => void; + type EnterStateCallbackType = (state: FSMBase) => void; + } +} + +declare class FSMBase extends EventEmitter { + constructor(config?: FSMBase.IConfig); + + start(newState: string): this; + next(): this; + goto(nextState: string): this; + state: string; + readonly prevState: string; + + setEnable(enable?: boolean): this; + toggleEnable(): this; + enable: boolean; + + addState( + name: string, + state: FSMBase.IStateConfig + ): this; + addState(state: FSMBase.IStateConfig): this; + + addStates( + states: { [name: string]: FSMBase.IStateConfig }, + ): this; + addStates( + states: FSMBase.IStateConfig[] + ): this; + + runMethod( + methodName: string, + ...args: unknown[] + ): unknown; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSMBase.js b/ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSMBase.js new file mode 100644 index 000000000..ba8b3b8ee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/fsm/FSMBase.js @@ -0,0 +1,232 @@ +import EventEmitterMethods from '../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../utils/object/GetValue.js'; + +const StateProperties = ['next', 'exit', 'enter']; + +class FSM { + /* + var config = { + start: 'A', // default: undefined + states: { + A: { + next: 'B', // function() { return 'B'; } + enter: function() {}, + exit: function() {}, + }, + // ... + }, + extend: { + i: 0, + name: 'abc' + // ... + }, + init: function() {}, + enable: true, + eventEmitter: true, + }; + */ + constructor(config) { + // Attach get-next-state function + var states = GetValue(config, 'states', undefined); + if (states) { + this.addStates(states); + } + + // Attach extend members + var extend = GetValue(config, 'extend', undefined); + if (extend) { + for (var name in extend) { + if (!this.hasOwnProperty(name) || this[name] === undefined) { + this[name] = extend[name]; + } + } + } + + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + + this._stateLock = false; + this.resetFromJSON(config); + } + + shutdown() { + this.destroyEventEmitter(); + } + + destroy() { + this.shutdown(); + } + + resetFromJSON(o) { + this.setEnable(GetValue(o, 'enable', true)); + this.start(GetValue(o, 'start', undefined)); + var init = GetValue(o, 'init', undefined); + if (init) { + init.call(this); + } + + return this; + } + + toJSON() { + return { + curState: this.state, + prevState: this.prevState, + + enable: this.enable, + start: this._start + }; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + set state(newState) { + if (!this.enable || this._stateLock) { + return; + } + if (this._state === newState) { + return; + } + this._prevState = this._state; + this._state = newState; + + this._stateLock = true; // lock state + + this.emit('statechange', this); + + if (this._prevState != null) { + var exitEventName = 'exit_' + this._prevState; + var exitCallback = this[exitEventName]; + if (exitCallback) { + exitCallback.call(this); + } + this.emit(exitEventName, this); + } + + this._stateLock = false; + + if (this._state != null) { + var enterEventName = 'enter_' + this._state; + var enterCallback = this[enterEventName]; + if (enterCallback) { + enterCallback.call(this); + } + this.emit(enterEventName, this); + } + } + + get state() { + return this._state; + } + + get prevState() { + return this._prevState; + } + + start(state) { + this._start = state; + this._prevState = undefined; + this._state = state; // Won't fire statechange events + return this; + } + + goto(nextState) { + if (nextState != null) { + this.state = nextState; + } + return this; + } + + next() { + var nextState; + var getNextState = this['next_' + this.state]; + if (getNextState) { + if (typeof (getNextState) === 'string') { + nextState = getNextState; + } else { + nextState = getNextState.call(this); + } + } + + this.goto(nextState); + return this; + } + + get stateProperties() { + return StateProperties; + } + + addState(name, state) { + if (typeof (name) !== 'string') { + state = name; + name = state.name; + } + + var stateProperties = this.stateProperties; + for (var i = 0, cnt = stateProperties.length; i < cnt; i++) { + var propertyName = stateProperties[i]; + var propertyValue = state[propertyName]; + if (propertyValue) { + this[`${propertyName}_${name}`] = propertyValue; + } + } + + return this; + } + + addStates(states) { + if (Array.isArray(states)) { + for (var i = 0, cnt = states.length; i < cnt; i++) { + this.addState(states[i]); + } + } else { + for (var name in states) { + this.addState(name, states[name]); + } + } + return this; + } + + runMethod(methodName, a1, a2, a3, a4, a5) { + var fn = this[methodName + '_' + this.state]; + if (!fn) { + return undefined; + } + + // Copy from eventemitter3 + var len = arguments.length; + switch (len) { + case 1: return fn.call(this); + case 2: return fn.call(this, a1); + case 3: return fn.call(this, a1, a2); + case 4: return fn.call(this, a1, a2, a3); + case 5: return fn.call(this, a1, a2, a3, a4); + case 6: return fn.call(this, a1, a2, a3, a4, a5); + } + var args = new Array(len - 1); + for (var i = 1; i < len; i++) { + args[i - 1] = arguments[i]; + } + return fn.apply(this, args); + } +} + +Object.assign( + FSM.prototype, + EventEmitterMethods, +); + +export default FSM; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/loopindexgenerator/LoopIndex.js b/ui/src/phaser3-rex-plugins/plugins/logic/loopindexgenerator/LoopIndex.js new file mode 100644 index 000000000..d4481376e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/loopindexgenerator/LoopIndex.js @@ -0,0 +1,41 @@ +class LoopIndex { + constructor(key, start, end, step, items) { + this.key = key; + this.start = start; + this.end = end; + this.step = step; + this.items = items; + this._current = start; + } + + reset() { + this._current = this.start; + } + + get isEnd() { + return (this.step >= 0) ? (this._current >= this.end) : (this._current <= this.end); + } + + get length() { + if (((this.step >= 0) && (this.start > this.end)) || + ((this.step < 0) && (this.start < this.end))) { + return 0; + } + return Math.floor(this.end - this.start) + 1; + } + + next() { + if (this.isEnd) { + this._current = this.start; + } else { + this._current += this.step; + } + return this; + } + + get current() { + return (!this.items) ? this._current : this.items[this._current]; + } +} + +export default LoopIndex; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/loopindexgenerator/LoopIndexGenerator.js b/ui/src/phaser3-rex-plugins/plugins/logic/loopindexgenerator/LoopIndexGenerator.js new file mode 100644 index 000000000..04af056f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/loopindexgenerator/LoopIndexGenerator.js @@ -0,0 +1,114 @@ +import LoopIndex from './LoopIndex.js'; + +class LoopIndexGenerator { + constructor() { + this.indexes = []; + this.length = 0; + this.reset(); + } + + reset() { + for (var i = 0, cnt = this.indexes.length; i < cnt; i++) { + this.indexes[i].reset(); + } + this.firstPass = true; + this.currentCount = 0; + return this; + } + + addNumberLoop(key, start, end, step) { + if (step === undefined) { + step = (end >= start) ? 1 : -1; + } + this.indexes.push(new LoopIndex(key, start, end, step)); + this.length = this._getLength(); + return this; + } + + addItemsLoop(key, items, reverse) { + if (reverse === undefined) { + reverse = false; + } + var lastIndex = items.length - 1; + var start = (reverse) ? lastIndex : 0; + var end = (reverse) ? 0 : lastIndex; + var step = (reverse) ? -1 : 1; + this.indexes.push(new LoopIndex(key, start, end, step, items)); + this.length = this._getLength(); + return this; + } + + addLoop(config) { + this.indexes.push(new LoopIndex(config.key, config.start, config.end, config.step, config.items)); + this.length = this._getLength(); + return this; + } + + removeLoops() { + this.indexes.length = 0; + this.length = 0; + return this; + } + + _getLength() { + var total = undefined; + for (var i = 0, cnt = this.indexes.length; i < cnt; i++) { + if (total === undefined) { + total = this.indexes[i].length; + } else { + total *= this.indexes[i].length; + } + } + return (total === undefined) ? 0 : total; + } + + get progress() { + return this.currentCount / this.length; + } + + get isEnd() { + for (var i = this.indexes.length - 1; i >= 0; i--) { + if (!this.indexes[i].isEnd) { + return false; + } + } + return true; + } + + next() { + var loopIndex, goNext; + for (var i = this.indexes.length - 1; i >= 0; i--) { + loopIndex = this.indexes[i]; + goNext = loopIndex.isEnd; + loopIndex.next(); + if (!goNext) { + break; + } + } + return this; + } + + getCurrent(out) { + if (out === undefined) { + out = {}; + } + var loopIndex; + for (var i = this.indexes.length - 1; i >= 0; i--) { + loopIndex = this.indexes[i]; + out[loopIndex.key] = loopIndex.current; + } + return out; + } + + getNext(out) { + if (!this.firstPass) { + this.next(); + } else { + this.firstPass = false; + } + this.getCurrent(out); + this.currentCount++; + return out; + } +} +export default LoopIndexGenerator; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/loopinticks/LoopInTicks.js b/ui/src/phaser3-rex-plugins/plugins/logic/loopinticks/LoopInTicks.js new file mode 100644 index 000000000..897485252 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/loopinticks/LoopInTicks.js @@ -0,0 +1,101 @@ +import TickTask from '../../utils/componentbase/TickTask.js'; +import LoopIndexGenerator from '../loopindexgenerator/LoopIndexGenerator.js'; +import Clear from '../../utils/object/Clear.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class LoopInTicks extends TickTask { + constructor(scene, config) { + super(scene, config); + + this.deltaPeriod = 1000 / scene.game.loop.targetFps; + this.deltaPercentage = 1; + this.loopIndexGenerator = new LoopIndexGenerator(); + this.currentIndexes = {}; + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.setCallback(GetValue(o, 'callback', this.callback), GetValue(o, 'scope', this.scope)); + this.setDeltaPercentage(GetValue(o, 'deltaPercentage', this.deltaPercentage)); + this.loopIndexGenerator.reset(); + Clear(this.currentIndexes); + return this; + } + + startTicking() { + super.startTicking(); + this.scene.sys.events.on('preupdate', this.preupdate, this); + } + + stopTicking() { + super.stopTicking(); + if (this.scene) { // Scene might be destoryed + this.scene.sys.events.off('preupdate', this.preupdate, this); + } + } + + setCallback(callback, scope) { + this.callback = callback; + this.scope = scope; + return this; + } + + setDeltaPercentage(percentage) { + this.deltaPercentage = percentage; + return this; + } + + addNumberLoop(key, start, end, step) { + this.loopIndexGenerator.addNumberLoop(key, start, end, step); + return this; + } + + addItemsLoop(key, items, reverse) { + this.loopIndexGenerator.addItemsLoop(key, items, reverse); + return this; + } + + addLoop(config) { + this.loopIndexGenerator.addLoop(config); + return this; + } + + get curTime() { + return new Date().getTime(); + } + + get progress() { + return this.loopIndexGenerator.progress; + } + + preupdate(time, delta) { + if ((!this.isRunning) || (!this.callback)) { + return; + } + + var startTime = this.curTime; + var totalTime = this.deltaPeriod * this.deltaPercentage; + var isTimeOut; + this.emit('tickstart', this); + do { + if (this.loopIndexGenerator.isEnd) { + this.complete(); + return; + } + + this.currentIndexes = this.loopIndexGenerator.getNext(this.currentIndexes); + if (this.scope) { + this.callback.call(this.scope, this.currentIndexes, this); + } else { + this.callback(this.currentIndexes, this); + } + isTimeOut = (this.curTime - startTime) >= totalTime; + } while (!isTimeOut) + this.emit('tickend', this); + return; + } +} + +export default LoopInTicks; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/quest/quest/Quest.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/quest/quest/Quest.d.ts new file mode 100644 index 000000000..43b86a0c1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/quest/quest/Quest.d.ts @@ -0,0 +1,34 @@ +import { + QuestionType as QuestionTypeRef, + OptionsType as OptionsTypeRef +} from '../questions/types'; +import DataMethods from '../../../utils/data/DataMethods'; + +export default Quest; + +declare namespace Quest { + type QuestionType = QuestionTypeRef; + type OptionsType = OptionsTypeRef; + + interface IConfig { + shuffleQuestions?: boolean, + shuffleOptions?: boolean, + } +} + +declare class Quest extends DataMethods{ + getNextQuestion( + questionKey?: string + ): Quest.QuestionType; + + + isLastQuestion(): boolean; + + start(): this; + + getOption( + question: string | Quest.QuestionType, + optionKey: string + ): Quest.OptionsType; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/quest/quest/Quest.js b/ui/src/phaser3-rex-plugins/plugins/logic/quest/quest/Quest.js new file mode 100644 index 000000000..d5b6e6b9d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/quest/quest/Quest.js @@ -0,0 +1,110 @@ +import EventEmitterMethods from '../../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import Shuffle from '../../../utils/array/Shuffle.js'; +import DataMethods from '../../../utils/data/DataMethods.js'; + +class Quest { + constructor(questionsManager, config) { + // Event emitter + this.setEventEmitter(GetValue(config, 'eventEmitter', undefined)); + + this.questionsManager = questionsManager; + this.questionKeys = []; + + this.resetFromJSON(config); + this.start(); + } + + resetFromJSON(o) { + this.setShuffleQuestionsEnable(GetValue(o, 'shuffleQuestions', false)); + this.setShuffleOptionsEnable(GetValue(o, 'shuffleOptions', false)); + return this; + } + + shutdown() { + this.destroyEventEmitter(); + this.questionsManager = undefined; + } + + destroy() { + this.shutdown(); + } + + setShuffleQuestionsEnable(enabled) { + if (enabled === undefined) { + enabled = true; + } + this.shuffleQuestionsEnable = enabled; + return this; + } + + setShuffleOptionsEnable(enabled) { + if (enabled === undefined) { + enabled = true; + } + this.shuffleOptionsEnable = enabled; + return this; + } + + start() { + // Reload keys + this.questionKeys.length = 0; + this.questionsManager.getKeys(this.questionKeys); + if (this.shuffleQuestionsEnable) { + Shuffle(this.questionKeys); + } + + this.nextIndex = -1; + this.nextKey = undefined; + return this; + } + + setNextKey(key) { + if (key === undefined) { + this.nextIndex++; + this.nextKey = this.questionKeys[this.nextIndex]; + } else if (this.questionsManager.has(key)) { + this.nextKey = key; + this.nextIndex = this.questionKeys.indexOf(key); + } else { + // Error + } + return this; + } + + getQuestion() { + var question = this.questionsManager.get(this.nextKey); + if (this.shuffleOptionsEnable) { + var options = question.options; + if (options) { + Shuffle(options); + } + } + this.emit('quest', question, this.questionsManager, this); + return question; + } + + getNextQuestion(key) { + return this.setNextKey(key).getQuestion(); + } + + isLastQuestion() { + return this.nextIndex === (this.questionKeys.length - 1); + } + + getOption(question, optionKey) { + if (optionKey === undefined) { + optionKey = question; + question = this.questionsManager.get(this.nextKey); + } + return this.questionsManager.getOption(question, optionKey); + } +} + +Object.assign( + Quest.prototype, + EventEmitterMethods, + DataMethods +); + +export default Quest; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/AddQuestion.js b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/AddQuestion.js new file mode 100644 index 000000000..f5c0784cb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/AddQuestion.js @@ -0,0 +1,35 @@ +var AddQuestion = function (question) { + Polyfills.call(this, question); + + // Remove duplicated question + var key = question.key; + if (this.questionMap.hasOwnProperty(key)) { + this.remove(key); + } + + // Add question + this.questions.push(question); + this.questionMap[key] = question; + + return this; +} + +var Polyfills = function (question) { + var options = question.options; + if (options) { + // Apply key via serial number + for (var i = 0, cnt = options.length; i < cnt; i++) { + var option = options[i]; + if (!option.hasOwnProperty('key')) { + option.key = `_${i}`; + } + } + } + + if (!question.hasOwnProperty('key')) { + // Apply key via serial numbers + question.key = `_${this.questions.length}`; + } +} + +export default AddQuestion; diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/DataMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/DataMethods.js new file mode 100644 index 000000000..d6c6b98b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/DataMethods.js @@ -0,0 +1,25 @@ +export default { + getData(key, defaultValue) { + return this._quest.getData(key, defaultValue); + }, + + setData(key, value) { + this._quest.setData(key, value); + return this; + }, + + incData(key, inc, defaultValue) { + this._quest.incData(key, inc, defaultValue); + return this; + }, + + mulData(key, mul, defaultValue) { + this._quest.mulData(key, mul, defaultValue); + return this; + }, + + clearData() { + this._quest.clearData(); + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/QuestMethods.js b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/QuestMethods.js new file mode 100644 index 000000000..44b3fc02e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/QuestMethods.js @@ -0,0 +1,40 @@ +import IsPlainObject from '../../../utils/object/IsPlainObject.js'; +import Quest from '../quest/Quest.js'; + +export default { + newQuest(config) { + var quest = new Quest(this, config); + return quest; + }, + + startQuest(config) { + if (this._quest) { + this._quest + .resetFromJSON(config) + .start(); + } else { + if (!IsPlainObject(config)) { + config = {}; + } + if (!config.hasOwnProperty('eventEmitter')) { + config.eventEmitter = this; + } + this._quest = this.newQuest(config); + } + return this; + }, + + restartQuest() { + this._quest.start(); + return this; + }, + + getNextQuestion(key) { + return this._quest.getNextQuestion(key); + }, + + isLastQuestion() { + return this._quest.isLastQuestion(); + }, + +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/QuestionManager.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/QuestionManager.d.ts new file mode 100644 index 000000000..631c55ab5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/QuestionManager.d.ts @@ -0,0 +1,115 @@ +import EventEmitter from "../../../utils/eventemitter/EventEmitter"; +import { + QuestionType as QuestionTypeRef, + OptionsType as OptionsTypeRef +} from './types'; +import Quest from '../quest/Quest'; + +export default QuestionManager; + +declare namespace QuestionManager { + + type QuestionType = QuestionTypeRef; + + type OptionsType = OptionsTypeRef; + + type ConvertParamCallbackType = (s: string, key: string) => any; + + interface IAddQuestionsConfig { + delimiter?: string, + types?: { + question?: string, + option?: string, + }, + convert?: true | ConvertParamCallbackType, + } + + interface IConfig extends IAddQuestionsConfig { + questions?: QuestionType[] | string, + + quest?: Quest.IConfig, + + eventEmitter?: EventEmitter | false, + } + + namespace Events { + type QuestCallbackType = ( + question: QuestionTypeRef, + questionManager: QuestionManager, + quest: Quest + ) => void; + } + + export class Quest { } + +} + +declare class QuestionManager extends EventEmitter { + constructor( + config?: QuestionManager.IConfig + ); + + add( + questions: QuestionManager.QuestionType[] | string, + config?: QuestionManager.IAddQuestionsConfig + ): this; + + remove(key: string): this; + + removeAll(): this; + + get(key: string): QuestionManager.QuestionType; + + getKeys(out?: string[]): string[]; + + has(key: string): boolean; + + readonly questions: QuestionManager.QuestionType[]; + + getOption( + question: string | QuestionManager.QuestionType, + optionKey: string + ): QuestionManager.OptionsType; + + startQuest( + config?: Quest.IConfig + ): this; + + getNextQuestion( + questionKey?: string + ): QuestionManager.QuestionType; + + isLastQuestion(): boolean; + + restartQuest(): this; + + getData( + key: string, + defaultValue?: any + ): any; + + getData(): any[]; + + setData( + key: string, + value: any + ): this; + + incData( + key: string, + inc: number, + defaultValue?: number + ): this; + + mulData( + key: string, + mul: number, + defaultValue?: number + ): this; + + clearData(): this; + + newQuest( + config?: Quest.IConfig + ): Quest; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/QuestionManager.js b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/QuestionManager.js new file mode 100644 index 000000000..d2d049e72 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/QuestionManager.js @@ -0,0 +1,115 @@ +import EventEmitterMethods from '../../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import ParseInputData from './parse/ParseInputData.js'; +import RemoveItem from '../../../utils/array/Remove.js'; +import Clear from '../../../utils/object/Clear.js'; +import AddQuestion from './AddQuestion.js'; +import QuestMethods from './QuestMethods.js'; +import DataMethods from './DataMethods.js'; + +class QuestionManager { + constructor(config) { + // Event emitter. Create a private event emitter for private quest task object. + this.setEventEmitter(GetValue(config, 'eventEmitter', undefined)); + + this.questions = []; + this.questionMap = {}; + this._quest = undefined; + + var questions = GetValue(config, 'questions', undefined); + if (questions) { + this.add(questions, config); + } + var questConfig = GetValue(config, 'quest', undefined); + if (questConfig) { + this.startQuest(questConfig) + } + } + + shutdown() { + this.destroyEventEmitter(); + if (this._quest) { + this._quest.destroy(); + this._quest = undefined; + } + } + + destroy() { + this.shutdown(); + } + + add(question, config) { + question = ParseInputData(question, config); + + if (Array.isArray(question)) { + var questions = question; + for (var i = 0, cnt = questions.length; i < cnt; i++) { + AddQuestion.call(this, questions[i]); + } + } else { + AddQuestion.call(this, question); + } + + return this; + } + + remove(key) { + if (this.questionMap.hasOwnProperty(key)) { + RemoveItem(this.questions, this.questionMap[key]); + delete this.questionMap[key]; + } + return this; + } + + removeAll() { + this.questions.length = 0; + Clear(this.questionMap); + } + + has(key) { + return this.questionMap.hasOwnProperty(key); + } + + get(key) { + return this.questionMap[key]; + } + + getKeys(out) { + if (out === undefined) { + out = []; + } + for (var i = 0, cnt = this.questions.length; i < cnt; i++) { + out.push(this.questions[i].key); + } + return out; + } + + getOption(question, optionKey) { + if (typeof (question) === 'string') { + question = this.get(question); + } + if (!question) { + return null; + } + var options = question.options; + if (options) { + var option; + for (var i = 0, cnt = options.length; i < cnt; i++) { + option = options[i]; + if (option.key === optionKey) { + return option; + } + } + } + return null; + } +} + +Object.assign( + QuestionManager.prototype, + EventEmitterMethods, + QuestMethods, + DataMethods +); + +export default QuestionManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/parse/ParseCSV.js b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/parse/ParseCSV.js new file mode 100644 index 000000000..4258b51ac --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/parse/ParseCSV.js @@ -0,0 +1,66 @@ +import GetValue from '../../../../utils/object/GetValue.js'; +import CSVParser from 'papaparse/papaparse.min.js'; +import DefaultConvertFn from '../../../../utils/string/TypeConvert.js'; + +var ParseCSV = function (csvString, config) { + var delimiter = GetValue(config, 'delimiter', ','); + var arr = CSVParser.parse(csvString, { + header: true, + delimiter: delimiter, + }).data; + + var questionType = GetValue(config, 'types.question', 'q'); + var optionType = GetValue(config, 'types.option', ''); + var convertFn = GetValue(config, 'convert', true); + if (convertFn === true) { + convertFn = DefaultConvertFn; + } + + var items = []; + var rowObj, rowType, + item, option; + for (var i = 0, cnt = arr.length; i < cnt; i++) { + rowObj = arr[i]; + rowType = rowObj.type; + delete rowObj.type; + + if (rowType === questionType) { + item = rowObj; + if (item.key === '') { + delete item.key; + } + + convert(item, convertFn); + + item.options = []; + items.push(item); + } else if (rowType === optionType) { + if (item) { + option = rowObj; + if (option.key === '') { + delete option.key; + } + + convert(option, convertFn); + item.options.push(option); + } else { + // Error + } + } + } + + return items; +}; + +var convert = function (item, convertFn) { + if (!convertFn) { + return item; + } + + for (var key in item) { + item[key] = convertFn(item[key], key); + } + return item; +} + +export default ParseCSV; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/parse/ParseInputData.js b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/parse/ParseInputData.js new file mode 100644 index 000000000..2dca44365 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/parse/ParseInputData.js @@ -0,0 +1,34 @@ +import GetValue from '../../../../utils/object/GetValue.js'; +import ParseCSV from './ParseCSV.js'; +import ParseYaml from './ParseYaml.js'; + +var ParseInputData = function (inputData, config) { + if (typeof (config) === 'string') { + config = { format: config }; + } + + var inputType; + if (typeof (inputData) === 'string') { + inputType = GetValue(config, 'format', 'csv'); + } else { + inputType = GetValue(config, 'format', undefined); + } + + switch (inputType) { + case 'csv': + inputData = ParseCSV(inputData, config); + break; + + case 'yaml': + inputData = ParseYaml(inputData, config); + break; + + case 'json': + inputData = JSON.parse(inputData); + break; + } + + return inputData; +} + +export default ParseInputData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/parse/ParseYaml.js b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/parse/ParseYaml.js new file mode 100644 index 000000000..4116833ce --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/parse/ParseYaml.js @@ -0,0 +1,27 @@ +import yaml from 'js-yaml'; + +var ParseYaml = function (yamlString, config) { + var items = []; + if (Array.isArray(yamlString)) { + yamlString.forEach(function (s) { + try { + items.push(yaml.load(s, config)); + } catch (e) { + console.log(e); + } + }) + + } else { + try { + yaml.loadAll(yamlString, function (item) { + items.push(item); + }, config); + } catch (e) { + console.log(e); + } + } + + return items; +} + +export default ParseYaml; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/types.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/types.d.ts new file mode 100644 index 000000000..d10abcd35 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/quest/questions/types.d.ts @@ -0,0 +1,10 @@ +export type QuestionType = { + key?: string, + [param: string]: any, + options?: OptionsType[], +} + +export type OptionsType = { + key?: string, + [param: string]: any, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/RunCommands.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/RunCommands.d.ts new file mode 100644 index 000000000..c4b83440f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/RunCommands.d.ts @@ -0,0 +1,17 @@ +export default RunCommands; + +declare namespace RunCommands { + + interface IConfig { + reverse?: boolean, + argsConvert?: ((s: any, cmd?: any[]) => any) | boolean, + + } + +} + +declare function RunCommands( + queue: any[], + scope?: object, + config?: RunCommands.IConfig +): any \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/RunCommands.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/RunCommands.js new file mode 100644 index 000000000..9a2ebc1cd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/RunCommands.js @@ -0,0 +1,64 @@ +import GetValue from '../../utils/object/GetValue.js'; +import ArrayCopy from '../../utils/array/Copy.js'; +import TypeConvert from '../../utils/string/TypeConvert.js'; +import IsArray from '../../utils/object/IsArray.js'; + +var RunCommands = function (queue, scope, config) { + var reverse = GetValue(config, 'reverse', false); + + var retVal; + if (IsArray(queue[0])) { + if (!reverse) { + for (var i = 0, len = queue.length; i < len; i++) { + retVal = RunCommands(queue[i], scope, config); + } + } else { + for (var len = queue.length, i = len - 1; i >= 0; i--) { + retVal = RunCommands(queue[i], scope, config); + } + } + } else { + retVal = RunCommand(queue, scope, config); + } + + return retVal; +} + +var RunCommand = function (cmd, scope, config) { + var argsConvert = GetValue(config, 'argsConvert', undefined); + var argsConvertScope = GetValue(config, 'argsConvertScope', undefined); + + var fnName = cmd[0]; + + ARGS = ArrayCopy(ARGS, cmd, 1); + if (argsConvert) { + // convert string to floating number, boolean, null, or string + if (argsConvert === true) { + argsConvert = TypeConvert; + argsConvertScope = undefined; + } + for (var i = 0, len = ARGS.length; i < len; i++) { + if (argsConvertScope) { + ARGS[i] = argsConvert.call(argsConvertScope, ARGS[i], cmd); + } else { + ARGS[i] = argsConvert(ARGS[i], cmd); + } + } + } + + var fn; + if (typeof (fnName) === 'string') { + fn = scope[fnName]; + if (fn == null) { + fn = GetValue(scope, fnName, null); + } + } else { + fn = fnName; + } + + var retValue = fn.apply(scope, ARGS); + return retValue; +} +var ARGS = []; // reuse this array + +export default RunCommands; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Player.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Player.d.ts new file mode 100644 index 000000000..2e469ad88 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Player.d.ts @@ -0,0 +1,12 @@ +import Base from '../tcrp/Player'; + +export default Player; + +declare namespace Player { + interface IConfig extends Base.IConfig { + + } +} +declare class Player extends Base { + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Player.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Player.js new file mode 100644 index 000000000..f71c1c084 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Player.js @@ -0,0 +1,22 @@ +import BasePlayer from '../tcrp/Player.js'; +import ArcadeStepClock from '../../../time/clock/ArcadeStepClock'; + +class Player extends BasePlayer { + constructor(parent, config) { + if (config === undefined) { + config = {}; + } + config.clock = new ArcadeStepClock(parent); + config.timeUnit = 0; // Force timeUnit to 0 + config.dtMode = 0; // Force dtMode to 0 + super(parent, config); + } + + load(commands, scope, config) { + // No config argument + super.load(commands, scope); + return this; + } +} + +export default Player; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Recorder.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Recorder.d.ts new file mode 100644 index 000000000..f687ba04d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Recorder.d.ts @@ -0,0 +1,13 @@ +import Base from '../tcrp/Recorder'; + +export default Recorder; + +declare namespace Recorder { + interface IConfig extends Base.IConfig { + + } +} + +declare class Recorder extends Base { + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Recorder.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Recorder.js new file mode 100644 index 000000000..9f99a6782 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/Recorder.js @@ -0,0 +1,14 @@ +import BaseRecorder from '../tcrp/Recorder.js'; +import ArcadeStepClock from '../../../time/clock/ArcadeStepClock'; + +class Recorder extends BaseRecorder { + constructor(parent, config) { + if (config === undefined) { + config = {}; + } + config.clock = new ArcadeStepClock(parent); + super(parent, config); + } +} + +export default Recorder; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/StepRunner.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/StepRunner.d.ts new file mode 100644 index 000000000..fb584552c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/StepRunner.d.ts @@ -0,0 +1,18 @@ +import ComponentBase from '../../../utils/componentbase/ComponentBase'; + +export default StepRunner; + +declare namespace StepRunner { + +} + +declare class StepRunner extends ComponentBase { + constructor( + parent: Phaser.Scene | Phaser.GameObjects.GameObject, + ); + + add( + command: any[], + scope?: object + ): this +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/StepRunner.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/StepRunner.js new file mode 100644 index 000000000..16548bb5f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/arcadetcrp/StepRunner.js @@ -0,0 +1,51 @@ +import ComponentBase from '../../../utils/componentbase/ComponentBase.js'; +import RunCommands from '../../../runcommands.js'; + +class StepRunner extends ComponentBase { + constructor(parent) { + super(parent, { eventEmitter: false }); + // this.parent = gameObject; + + this.commands = []; + this.boot(); + } + + boot() { + this.scene.physics.world.on('worldstep', this.update, this); + // 'worldstep' event is emitted *after* the bodies and colliders have been updated. + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.scene.physics.world.off('worldstep', this.update, this); + this.commands = undefined; + + super.shutdown(fromScene) + } + + add(commands, scope) { + this.commands.push([ + commands, scope + ]); + return this; + } + + update() { + if (this.commands.length === 0) { + return; + } + + var command; + for (var i = 0, cnt = this.commands.length; i < cnt; i++) { + command = this.commands[i]; + RunCommands(command[0], command[1]); + } + this.commands.length = 0; + } +} + +export default StepRunner; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/CSVScenario.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/CSVScenario.d.ts new file mode 100644 index 000000000..ef386613c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/CSVScenario.d.ts @@ -0,0 +1,79 @@ +export default CSVScenario; + +declare namespace CSVScenario { + type TimeUnitType = 0 | 1 | 'ms' | 's' | 'sec'; + type ConvertCallbackType = (s: string, instruction: any[]) => any; + + interface IConfig { + timeUnit?: TimeUnitType, + prefix?: RegExp, + argsConvert?: true | ConvertCallbackType, + argsConvertScope?: object, + delimiter?: string, + translateCommandNameCallback?: (commandName: string) => string, + } + + interface IStartConfig { + label?: string + offset?: number + } + + namespace Events { + type CompleteCallbackType = ( + scope: object, scenario: CSVScenario + ) => void; + + type LabelChangeCallbackType = ( + lastLabel: string, prevLabel: string, + scope: object, scenario: CSVScenario + ) => void; + + type LogCallbackType = ( + msg: string, + scope: object, scenario: CSVScenario + ) => void; + + type ErrorCallbackType = ( + msg: string, + scope: object, scenario: CSVScenario + ) => void; + } +} + +declare class CSVScenario extends Phaser.Events.EventEmitter { + constructor( + scene: Phaser.Scene, + config?: CSVScenario.IConfig + ); + + load( + csvString: string, + scope: object, + config?: CSVScenario.IConfig + ): this; + scope: object; + + append(csvString: string): this; + + start(config?: CSVScenario.IStartConfig): this; + play(config?: CSVScenario.IStartConfig): this; + playPromise(config?: CSVScenario.IStartConfig): Promise; + + continue(eventName: string): this; + continue(force: true): this; + + pause(): this; + + resume(): this; + + clear(): this; + + readonly isRunning: boolean; + readonly isPaused: boolean; + readonly lastLabel: string; + readonly lastCustomCommandName: string; + readonly previousLabel: string; + + setTimeScale(timeScale: number): this; + timeScale: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/CSVScenario.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/CSVScenario.js new file mode 100644 index 000000000..f26542bbb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/CSVScenario.js @@ -0,0 +1,394 @@ +import EventEmitterMethods from '../../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import CSVParser from 'papaparse/papaparse.min.js'; +import InstMem from './InstMem.js'; +import CmdHandlers from './commands/CmdHandlers.js'; +import { WaitComplete } from '../../../utils/promise/WaitEvent.js'; + + +class CSVScenario { + constructor(scene, config) { + // Event emitter + this.setEventEmitter(GetValue(config, 'eventEmitter', undefined)); + + this.scene = scene; + this.timer = undefined; + this._timeScale = 1; + this.instMem = new InstMem(this); + this.cmdHandlers = new CmdHandlers(this); + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this._inRunCmdLoop = false; + this.isRunning = GetValue(o, 'state', false); + this.isPaused = GetValue(o, 'pause', false); + this.waitEvent = GetValue(o, 'wait', undefined); + this.scope = GetValue(o, 'scope', undefined); + this.timeUnit = GetValue(o, 'timeUnit', 0); + this.cmdPrefix = GetValue(o, 'prefix', DEFAULT_PREFIX); + this.argsConvert = GetValue(o, 'argsConvert', true); + this.argsConvertScope = GetValue(o, 'argsConvertScope', undefined); + this.cmdHandlers.resetFromJSON(GetValue(o, 'handlers', undefined)); + this.instMem.resetFromJSON(GetValue(o, 'instMem', undefined)); + this.delimiter = GetValue(o, 'delimiter', ','); + this.translateCommandNameCallback = GetValue(o, 'translateCommandNameCallback', undefined); + return this; + } + + toJSON() { + return { + state: this.isRunning, + pause: this.isPaused, + wait: this.waitEvent, + scope: this.scope, + timeUnit: this.timeUnit, + prefix: this.cmdPrefix, + argsConvert: this.argsConvert, + argsConvertScope: this.argsConvertScope, + handlers: this.cmdHandlers.toJSON(), + instMem: this.instMem.toJSON(), + delimiter: this.delimiter + }; + } + + boot() { + this.scene.sys.events.once('shutdown', this.destroy, this); + } + + shutdown() { + if (!this.scene) { + return + } + + this.destroyEventEmitter(); + this.clear(); + this.scene.sys.events.off('shutdown', this.destroy, this); + this.scene = undefined; + } + + destroy() { + this.shutdown(); + } + + load(strCmd, scope, config) { + this.clear(); + + this.timeUnit = GetValue(config, 'timeUnit', this.timeUnit); + if (typeof (this.timeUnit) === 'string') { + this.timeUnit = TIMEUNITMODE[this.timeUnit]; + } + this.cmdPrefix = GetValue(config, 'prefix', this.cmdPrefix); + if (typeof (this.cmdPrefix) === 'string') { + this.cmdPrefix = new RegExp(this.cmdPrefix); + } + this.argsConvert = GetValue(config, 'argsConvert', this.argsConvert); + this.argsConvertScope = GetValue(config, 'argsConvertScope', this.argsConvertScope); + this.scope = scope; + + this.delimiter = GetValue(config, 'delimiter', this.delimiter); + this.translateCommandNameCallback = GetValue(config, 'translateCommandNameCallback', this.translateCommandNameCallback); + + this.append(strCmd); + return this; + } + + clear() { + this.stop(); + this.instMem.resetFromJSON(); + this.cmdHandlers.resetFromJSON(); + } + + start(config) { + this.stop(); + var label = GetValue(config, 'label', ''); + this.offset = GetValue(config, 'offset', 0); + if (this.isDebugMode) { + this.log('Start at Label: ' + label); + } + + var result = this.goto(label); + if (!result) { + return false; + } + + this.isRunning = true; + this.runNextCmd(); + return true; + } + + play(config) { + this.start(config); + return this; + } + + playPromise(config) { + var promise = WaitComplete(this); + this.start(config); + return promise; + } + + getIndex(label) { + var index = this.getCmdHandler('label').getIndex(label); + if (index == null) { + this.error(`Label: ${label} is not found`); + } + return index; + } + + goto(label) { + var index; + if (typeof (label) === 'string') { + index = this.getIndex(label); + } else { + index = label; + } + if (index == null) { + return false; + } + this.instMem.setNextIndex(index); + return true; + } + + get timeScale() { + return this._timeScale; + } + + set timeScale(value) { + this._timeScale = value; + if (this.timer) { + this.timer.timeScale = value; + } + } + + setTimeScale(timeScale) { + this.timeScale = timeScale; + return this; + } + + wait(eventName) { + this.waitEvent = eventName; + if (typeof (eventName) === 'number') { + var delay = eventName; + if (this.timeUnit === 1) { + delay *= 1000; + } + this.timer = this.scene.time.delayedCall(delay, this.continue, [eventName], this); + this.timer.timeScale = this._timeScale; + } else { + this.emit(`wait.${eventName}`, this); + } + + this.emit('wait', eventName, this); + return this; + } + + stop() { + if (!this.isRunning) { + return this; + } + + this.isRunning = false; + this.isPaused = false; + + // clear wait event + this.waitEvent = undefined; + if (this.timer) { + this.timer.remove(); + this.timer = undefined; + } + + return this; + } + + complete() { + this.emit('complete', this.scope, this); + this.stop(); + return this; + } + + append(csvString) { + var arr = CSVParser.parse(csvString, { + delimiter: this.delimiter + }).data; + this.parse(arr); + return this; + } + + pause() { + if (!this.isRunning) { + return this; + } + if (this.isPaused) { + return this; + } + + this.isPaused = true; + if (this.timer) { + this.timer.paused = true; + } + return this; + } + + resume() { + if (!this.isRunning) { + return this; + } + if (!this.isPaused) { + return this; + } + + this.isPaused = false; + if (this.timer) { + this.timer.paused = false; + } + return this; + } + + continue(eventName) { + if ((!this.isRunning) || + this.isPaused || + (this.waitEvent === undefined)) { + return this; + } + + if ((eventName === true) || (eventName === this.waitEvent)) { + this.waitEvent = undefined; + if (this.timer) { + this.timer.remove(); + this.timer = undefined; + } + this.runNextCmd(); + } + return this; + } + + get lastLabel() { + return this.cmdHandlers.cmds.label.lastLabel; + } + + get previousLabel() { + return this.cmdHandlers.cmds.label.prevLabel; + } + + get lastCustomCommandName() { + return this.cmdHandlers.cmds['-'].lastMethodName; + } + + getCmdHandler(name) { + if (typeof (name) !== 'string') { + name = name[0]; + } + return this.cmdHandlers.get(name); + } + + parse(arr) { + var item, name, prefix = this.cmdPrefix; + for (var i = 0, len = arr.length; i < len; i++) { + item = arr[i]; + name = item[0]; + if (name === '-') { + this.appendCustomCommand(item); + + } else if (!isNaN(name)) { + var time = parseFloat(name); + if (time > 0) { + // insert 'wait' command + this.appendCommand(['wait', time]); + } + this.appendCustomCommand(item); + + } else if (prefix.test(name)) { + var innerMatch = name.match(prefix); + item[0] = innerMatch[1].toLowerCase(); + var isValid = this.appendCommand(item); + + if (!isValid) { + this.error(`Line ${i}: ${JSON.stringify(item)} is not a valid command`); + } + + } else { + // insert 'wait' command + this.appendCommand(['wait', name]); + item[0] = '-'; + this.appendCommand(item); + } + } + + return this; + } + + appendCommand(inst) { + var handler = this.getCmdHandler(inst); + if (handler == null) { + return false; + } + inst = handler.parse(inst, this.instMem.length); + if (inst) { + this.instMem.append(inst); + } + return true; + } + + appendCustomCommand(inst) { + inst[0] = '-'; + if (this.translateCommandNameCallback) { + inst[1] = this.translateCommandNameCallback(inst[1]); + } + return this.appendCommand(inst); + } + + runNextCmd() { + if (this._inRunCmdLoop) { // prevent re-entry + return; + } + + var instMem = this.instMem; + var inst; + this._inRunCmdLoop = true; + while ( + this.isRunning && + (!this.isPaused) && + (this.waitEvent === undefined) + ) { + inst = instMem.get(); + instMem.setNextIndex(); + if (inst == null) { + this.complete(); + break; + } + this.getCmdHandler(inst).run(inst); + } + this._inRunCmdLoop = false; + return this; + } + + log(msg) { + this.emit('log', msg, this.scope, this); + return this; + } + + get isDebugMode() { + return (this.listenerCount('log') > 0); + } + + error(msg) { + this.emit('error', msg, this.scope, this); + return this; + } +} + +Object.assign( + CSVScenario.prototype, + EventEmitterMethods +); + +const TIMEUNITMODE = { + ms: 0, + s: 1, + sec: 1 +}; +const DEFAULT_PREFIX = /^#([a-zA-Z]+)/; + +export default CSVScenario; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/InstMem.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/InstMem.js new file mode 100644 index 000000000..708c305f0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/InstMem.js @@ -0,0 +1,61 @@ +import Clone from '../../../utils/object/Clone.js'; +import Clear from '../../../utils/object/Clear.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class InstMem { + constructor(scenario) { + this.scenario = scenario; + + this.queue = []; + this.currentIdx = -1; + this.nextIdx = 0; + } + + resetFromJSON(o) { + var queue = GetValue(o, 'queue', undefined); + if (queue === undefined) { + Clear(this.queue); + } else { + Clone(queue, this.queue); + } + + this.currentIdx = GetValue(o, 'curIdx', -1); + this.nextIdx = GetValue(o, 'nextIdx', 0); + return this; + } + + clear() { + this.currentIdx = -1; + this.nextIdx = 0; + this.queue.length = 0; + return this; + } + + append(item) { + this.queue.push(item); + return this; + } + + setNextIndex(index) { + if (index === undefined) { + index = this.currentIdx + 1; + } + this.nextIdx = index; + return this; + } + + get(index) { + if (index === undefined) { + index = this.nextIdx; + } + this.currentIdx = index; + return this.queue[index]; + } + + get length() { + return this.queue.length; + } +} + +export default InstMem; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/BaseCmd.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/BaseCmd.js new file mode 100644 index 000000000..b6ea4b11b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/BaseCmd.js @@ -0,0 +1,20 @@ +class BaseCmd { + constructor(scenario, type) { + this.scenario = scenario; + this.type = type; + } + + resetFromJSON(o) {} + + toJSON() { + return {}; + } + + parse(inst, index) { + return inst; + } + + run(inst) {} +} + +export default BaseCmd; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/CmdHandlers.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/CmdHandlers.js new file mode 100644 index 000000000..b60ed3cd6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/CmdHandlers.js @@ -0,0 +1,45 @@ +import CustomCmd from './CustomCmd.js'; +import WaitCmd from './WaitCmd.js'; +import LabelCmd from './LabelCmd.js'; +import ExitCmd from './ExitCmd.js'; +import GotoCmd from './GotoCmd.js'; +import IfCmd from './IfCmd.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class CmdHandlers { + constructor(scenario) { + this.cmds = { + '-': new CustomCmd(scenario), + 'wait': new WaitCmd(scenario), + 'label': new LabelCmd(scenario), + 'exit': new ExitCmd(scenario), + 'goto': new GotoCmd(scenario), + 'if': new IfCmd(scenario) + }; + } + + resetFromJSON(o) { + for (var name in this.cmds) { + this.cmds[name].resetFromJSON(GetValue(o, name, undefined)); + } + return this; + } + + toJSON() { + var ret = {}; + for (var name in this.cmds) { + ret[name] = this.cmds[name].toJSON(); + } + return ret; + } + + get(name) { + return this.cmds[name]; + } + + isValidCmdName(name) { + return this.cmds.hasOwnProperty(name); + } +} +export default CmdHandlers; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/CustomCmd.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/CustomCmd.js new file mode 100644 index 000000000..4941f86c7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/CustomCmd.js @@ -0,0 +1,104 @@ +import BaseCmd from './BaseCmd.js'; +import GetValue from '../../../../utils/object/GetValue.js'; +import RunCommands from '../../../../runcommands.js'; +import TypeConvert from '../../../../utils/string/TypeConvert.js'; + +const SpliceOne = Phaser.Utils.Array.SpliceOne; + +class CustomCmd extends BaseCmd { + constructor(scenario) { + super(scenario, '-'); + this.task = undefined; + this.lastMethodName = undefined; + } + + resetFromJSON(o) { + if (this.task) { + this.task.off('complete', this.resume, this); + this.task = undefined; + } + } + + parse(inst, index) { + var cmd = SpliceOne(inst, 0); + + var scenario = this.scenario; + var argsConvert = scenario.argsConvert; + var argsConvertScope = scenario.argsConvertScope; + if (argsConvert) { + if (argsConvert === true) { + argsConvert = TypeConvert; + argsConvertScope = undefined; + } + for (var i = 1, len = inst.length; i < len; i++) { + if (argsConvertScope) { + inst[i] = argsConvert.call(argsConvertScope, inst[i], inst); + } else { + inst[i] = argsConvert(inst[i], inst); + } + } + } + + inst = [cmd, inst]; + return inst; + } + + run(inst) { + if (!this.validate(inst)) { + this.scenario.error(`Command '${GetFunctionName(inst)}' is not found in scope`); + return; + } + + var command = inst[1]; + this.lastMethodName = command[0]; + var task = RunCommands(command, this.scenario.scope); + if (task && (typeof (task.once) === 'function')) { + task.once('complete', this.resume, this); + this.pause(); + this.task = task; + } else { + this.task = undefined; + } + } + + validate(inst) { + var fnName = GetFunctionName(inst); + if (fnName === null) { + return false; + } + + var scope = this.scenario.scope; + var fn = scope[fnName]; + if (fn == null) { + fn = GetValue(scope, fnName, null); + } + return !!fn; + } + + pause() { + this.scenario.pause(); + } + + resume() { + this.task = undefined; + var scenario = this.scenario + scenario.resume(); + scenario.runNextCmd(); + } +} + +var GetFunctionName = function (inst) { + var command = inst[1]; + if (!command) { + return null; + } + + var fnName = command[0]; + if (!fnName) { + return null; + } + + return fnName; +} + +export default CustomCmd; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/ExitCmd.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/ExitCmd.js new file mode 100644 index 000000000..ef94ab9f8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/ExitCmd.js @@ -0,0 +1,19 @@ +import BaseCmd from './BaseCmd.js'; + +class ExitCmd extends BaseCmd { + constructor(scenario) { + super(scenario, 'exit'); + } + + parse(inst, index) { + inst.length = 1; + return inst; + } + + run(inst) { + this.scenario.log('#EXIT'); + this.scenario.complete(); + } +} + +export default ExitCmd; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/GotoCmd.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/GotoCmd.js new file mode 100644 index 000000000..e0c89af32 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/GotoCmd.js @@ -0,0 +1,31 @@ +import BaseCmd from './BaseCmd.js'; + +class GotoCmd extends BaseCmd { + constructor(scenario) { + super(scenario, 'goto'); + } + + parse(inst, index) { + inst.length = 2; + return inst; + } + + run(inst) { + var label = this.getLabel(inst); + if (this.scenario.isDebugMode) { + this.scenario.log('#GOTO label: ' + label); + } + this.scenario.goto(label); + } + + getLabel(inst) { + var label = inst[1]; + if (label == null) { + label = ''; + inst[1] = label; + } + return label; + } +} + +export default GotoCmd; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/IfCmd.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/IfCmd.js new file mode 100644 index 000000000..8c2ae5f49 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/IfCmd.js @@ -0,0 +1,55 @@ +import BaseCmd from './BaseCmd.js'; + +class IfCmd extends BaseCmd { + constructor(scenario) { + super(scenario, 'if'); + } + + parse(inst, index) { + inst.length = 4; + var cond = '(' + this.getCond(inst) + ')'; + inst[1] = new Function('return ' + cond); + return inst; + } + + run(inst) { + var condFn = this.getCond(inst); + var result = condFn.call(this.scenario.scope); + var nextLabel = (result)? this.getTrueLabel(inst) : this.getFalseLabel(inst); + if (nextLabel !== '') { + if (this.scenario.isDebugMode) { + this.scenario.log('#IF ' + result + '- GOTO label: ' + nextLabel); + } + this.scenario.goto(nextLabel); + } + } + + getCond(inst) { + var cond = inst[1]; + if ((cond == null) || (cond === '')) { + cond = 'true'; + inst[1] = cond; + } + return cond; + } + + getTrueLabel(inst) { + var label = inst[2]; + if (label == null) { + label = ''; + inst[2] = label; + } + return label; + } + + getFalseLabel(inst) { + var label = inst[3]; + if (label == null) { + label = ''; + inst[3] = label; + } + return label; + } +} + +export default IfCmd; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/LabelCmd.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/LabelCmd.js new file mode 100644 index 000000000..9d0a1b2db --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/LabelCmd.js @@ -0,0 +1,80 @@ +import BaseCmd from './BaseCmd.js'; +import Clone from '../../../../utils/object/Clone.js'; +import Clear from '../../../../utils/object/Clear.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class LabelCmd extends BaseCmd { + constructor(scenario) { + super(scenario, 'label'); + + this.labels = {}; + this.prevLabel = ''; + this.lastLabel = ''; + } + + resetFromJSON(o) { + this.prevLabel = GetValue(o, 'preLabel', ''); + this.lastLabel = GetValue(o, 'lastLabel', ''); + var labels = GetValue(o, 'labels', undefined); + if (labels === undefined) { + Clear(this.labels); + } else { + Clone(labels, this.labels); + } + } + + toJSON() { + return { + preLabel: this.prevLabel, + lastLabel: this.lastLabel, + labels: this.labels + }; + } + + parse(inst, index) { + inst.length = 2; + var label = this.getLabel(inst); + this.addLabel(label, index); + return inst; + } + + run(inst) { + var label = this.getLabel(inst); + if (this.scenario.isDebugMode) { + this.scenario.log('#LABEL: ' + label); + } + + this.prevLabel = this.lastLabel; + this.lastLabel = label; + //this.scenario.resetClock(); // TODO + var scenario = this.scenario; + scenario.emit('labelchange', this.lastLabel, this.prevLabel, scenario.scope, scenario); + } + + getLabel(inst) { + var label = inst[1]; + if (label == null) { + label = ''; + inst[1] = label; + } + return label; + } + + addLabel(name, index) { + this.labels[name] = index; + } + + getIndex(name) { + if ((name === '') || !this.hasLabel(name)) { + return 0; + } + return this.labels[name]; + } + + hasLabel(name) { + return this.labels.hasOwnProperty(name); + } +} + +export default LabelCmd; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/WaitCmd.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/WaitCmd.js new file mode 100644 index 000000000..d8c4e843e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/csvscenario/commands/WaitCmd.js @@ -0,0 +1,56 @@ +import BaseCmd from './BaseCmd.js'; + +class WaitCmd extends BaseCmd { + constructor(scenario) { + super(scenario, 'wait'); + } + + parse(inst, index) { + inst.length = 2; + var eventName = this.getEventName(inst); + if (!isNaN(eventName)) { + inst[1] = parseFloat(eventName); + } + return inst; + } + + run(inst) { + var eventName = this.getEventName(inst); + if (typeof (eventName) === 'number') { + this.waitTime(eventName); + } else { + this.waitEvent(eventName); + } + } + + waitTime(delayTime) { + if (delayTime > this.scenario.offset) { + delayTime -= this.scenario.offset; + this.scenario.offset = 0; + if (this.scenario.isDebugMode) { + this.scenario.log('#WAIT: ' + delayTime); + } + this.scenario.wait(delayTime); + } else { + this.scenario.offset -= delayTime; + } + } + + waitEvent(eventName) { + if (this.scenario.isDebugMode) { + this.scenario.log('#WAIT: ' + eventName); + } + this.scenario.wait(eventName); + } + + getEventName(inst) { + var eventName = inst[1]; + if (eventName == null) { + eventName = ''; + inst[1] = eventName; + } + return eventName; + } +} + +export default WaitCmd; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/managers/Managers.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/managers/Managers.d.ts new file mode 100644 index 000000000..bbfef5579 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/managers/Managers.d.ts @@ -0,0 +1,2 @@ +import Managers from '../../../utils/managers/Managers'; +export default Managers; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/managers/Managers.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/managers/Managers.js new file mode 100644 index 000000000..10023cff6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/managers/Managers.js @@ -0,0 +1,2 @@ +import Managers from '../../../utils/managers/Managers.js'; +export default Managers; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/sequence/Sequence.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/sequence/Sequence.d.ts new file mode 100644 index 000000000..a8c573e54 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/sequence/Sequence.d.ts @@ -0,0 +1,44 @@ +import EventEmitter from "../../../utils/eventemitter/EventEmitter"; + +export default Sequence; + +declare namespace Sequence { + interface IConfig { + yoyo?: boolean, + repeat?: number, + loop?: boolean, + + eventEmitter?: EventEmitter | false, + } + + namespace Events { + type CompleteCallbackType = (actionScope: object, seq: Sequence) => void; + } +} + +declare class Sequence extends EventEmitter { + constructor(config?: Sequence.IConfig); + + load( + commands: any[], + actionScope: object + ): this; + + start(): this; + + cancel(): this; + + stop(): this; + + readonly state: number; + readonly completed: boolean; + + setYoyo(yoyo?: boolean): this; + yoyo: boolean; + + setRepeat(count: number): this; + readonly repeat: number; + + setLoop(loop?: boolean): this; + loop: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/sequence/Sequence.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/sequence/Sequence.js new file mode 100644 index 000000000..f9b144b08 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/sequence/Sequence.js @@ -0,0 +1,169 @@ +import EventEmitterMethods from '../../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../../utils/object/GetValue.js'; +import RunCommands from '../../../runcommands.js'; +import ArrayCopy from '../../../utils/array/Copy.js'; + + +class Sequence { + constructor(config) { + // Event emitter + this.setEventEmitter(GetValue(config, 'eventEmitter', undefined)); + + this.commands = []; + this.scope = undefined; + this.config = undefined; + this.index = 0; + this.indexStep = 1; // 1, or -1 + this.setYoyo(GetValue(config, 'yoyo', false)); + this.setRepeat(GetValue(config, 'repeat', 0)); + this.setLoop(GetValue(config, 'loop', false)); + this.state = 0; // 0: idle, 1: run, 2: run-last, 3: completed + this.task = undefined; + } + + shutdown() { + this.stop(); + this.destroyEventEmitter(); + this.commands.length = 0; + this.scope = undefined; + this.config = undefined; + } + + destroy() { + this.shutdown(); + } + + load(commands, scope, config) { + this.stop(); + this.setYoyo(GetValue(config, 'yoyo', this.yoyo)); + this.setRepeat(GetValue(config, 'repeat', this.repeat)); + this.setLoop(GetValue(config, 'loop', this.loop)); + + this.commands = ArrayCopy(this.commands, commands); + this.scope = scope; + this.config = config; + return this; + } + + start() { + this.stop(); + + this.resetRepeatCount(); + this.index = 0; + this.indexStep = 1; + this.state = 1; + if (this.commands.length > 0) { + this.runNextCommands(); + } else { + this.complete(); + } + return this; + } + + stop() { + if (this.task) { + this.task.off('complete', this.runNextCommands, this); + this.task = undefined; + } + this.state = 0; + return this; + } + + setYoyo(yoyo) { + if (yoyo === undefined) { + yoyo = true; + } + this.yoyo = yoyo; + return this; + } + + setRepeat(count) { + this.repeat = count; + this.resetRepeatCount(); + return this; + } + + setLoop(loop) { + if (loop === undefined) { + loop = true; + } + this.loop = loop; + this.resetRepeatCount(); + return this; + } + + resetRepeatCount() { + this.repeatCount = (this.repeat === -1 || this.loop) ? 999999999999 : this.repeat; + return this; + } + + get completed() { + return (this.state === 3); + } + + get currentCommandIndex() { + return (this.index - 1); + } + + runNextCommands() { + var task, isFirstCommand, isLastCommand; + while (1) { + if (this.state === 2) { + this.complete(); + return; + } + + task = RunCommands(this.commands[this.index], this.scope); + if (task && (typeof (task.once) === 'function')) { + task.once('complete', this.runNextCommands, this); + this.task = task; + } else { + this.task = undefined; + } + + isFirstCommand = (this.index === 0); + isLastCommand = (this.index === (this.commands.length - 1)); + if (!this.yoyo) { + if (isLastCommand) { + this.index = 0; + if (this.repeatCount > 0) { + this.repeatCount--; + } else { + this.state = 2; // goto completed at next running + } + } else { + this.index += this.indexStep; + } + } else { + if (((this.indexStep > 0) && isLastCommand) || + ((this.indexStep < 0) && isFirstCommand)) { + this.indexStep = -this.indexStep; + this.index += this.indexStep; + if (this.repeatCount > 0) { + this.repeatCount--; + } else { + this.state = 2; // goto completed at next running + } + } else { + this.index += this.indexStep; + } + } + + if (this.task) { + return this; + } + } + } + + complete() { + this.state = 3; + this.emit('complete', this.scope, this); + } +} + +Object.assign( + Sequence.prototype, + EventEmitterMethods +); + +export default Sequence; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Player.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Player.d.ts new file mode 100644 index 000000000..55712a0da --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Player.d.ts @@ -0,0 +1,59 @@ +import ComponentBase from '../../../utils/componentbase/ComponentBase.js'; + +export default Player; + +declare namespace Player { + type TimeUnitType = 0 | 1 | 'ms' | 's' | 'sec'; + type DtModeType = 0 | 1 | 'abs' | 'absolute' | 'inc' | 'increment'; + + interface IConfig extends ComponentBase.IConfig { + timeUnit?: TimeUnitType + dtMode?: DtModeType, + commands?: any[], + timeScale?: number, + scope?: object, + } + + interface ILoadConfig { + timeUnit?: TimeUnitType + dtMode?: DtModeType, + } + + namespace Events { + type CompleteCallbackType = ( + parent: Phaser.Scene | Phaser.GameObjects.GameObject, + player: Player + ) => void; + + type RunCommandCallbackType = (command: any[], scope: object) => void; + } +} + +declare class Player extends ComponentBase { + constructor( + parent: Phaser.Scene | Phaser.GameObjects.GameObject, + config?: Player.IConfig + ); + + load( + commands: any[], + scope?: object, + config?: Player.ILoadConfig + ): this; + + start(startAt?: number): this; + + pause(): this; + resume(): this; + stop(): this; + + seek(time: number): this; + seekToNext(): this; + + readonly isPlaying: boolean; + readonly completed: boolean; + readonly now: number; + + setTimeScale(timeScale: number): this; + timeScale: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Player.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Player.js new file mode 100644 index 000000000..a62be39f4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Player.js @@ -0,0 +1,251 @@ +import ComponentBase from '../../../utils/componentbase/ComponentBase.js'; +import Clock from '../../../clock.js'; +import ArrayCopy from '../../../utils/array/Copy.js'; +import RunCommands from '../../../runcommands.js'; +import IsArray from '../../../utils/object/IsArray.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Player extends ComponentBase { + constructor(parent, config) { + super(parent, config); + + var clock = GetValue(config, 'clock', undefined); + if (!clock) { + clock = new Clock(parent); + } + this.clock = clock; + this.clock.on('update', this.update, this); + + this.resetFromJSON(config); // this function had been called in super(config) + } + + resetFromJSON(o) { + this.clock.resetFromJSON(GetValue(o, 'clock', undefined)); + this.state = GetValue(o, 'state', 0); // 0=idle, 1=run, 2=completed + this.commands = GetValue(o, 'commands', []); // [[time, cmds], [time, cmds], ...] + this.scope = GetValue(o, 'scope', undefined); + this.setTimeUnit(GetValue(o, 'timeUnit', 0)); + this.setDtMode(GetValue(o, 'dtMode', 0)); + this.index = GetValue(o, 'index', 0); + this.nextTime = GetValue(o, 'nextTime', 0); + return this; + } + + toJSON() { + return { + clock: this.clock.toJSON(), + state: this.state, + commands: this.commands, + scope: this.scope, + timeUnit: this.timeUnit, + dtMode: this.dtMode, + index: this.index, + nextTime: this.nextTime + }; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.clock.shutdown(fromScene); + this.commands = undefined; + + super.shutdown(fromScene); + } + + load(commands, scope, config) { + this.stop(); + var timeUnit = GetValue(config, 'timeUnit', undefined); + if (timeUnit !== undefined) { + this.setTimeUnit(timeUnit) + } + var dtMode = GetValue(config, 'dtMode', undefined); + if (dtMode !== undefined) { + this.setDtMode(dtMode); + } + commands = commands + .filter(function (item) { + var dt = item[0]; + return !isNaN(dt); + }) + .map(function (item) { + var dt = item[0]; + if (typeof (dt) === 'string') { + item[0] = parseFloat(item[0]); + } + return item; + }); + + if (this.dtMode === 0) { + commands.sort(function (itemA, itemB) { + var dtA = itemA[0], + dtB = itemB[0]; + return (dtA > dtB) ? 1 : + (dtA < dtB) ? -1 : 0; + }); + } + + this.commands = commands; + this.scope = scope; + return this; + } + + start(startAt) { + if (startAt === undefined) { + startAt = 0; + } + + this.stop(); + + this.index = 0; + this.state = 1; + this.nextTime = this.getNextDt(0); + + this.clock.start(startAt); + this.update(startAt); + this.emit('start', this.parent, this); + return this; + } + + pause() { + this.clock.pause(); + this.emit('pause', this.parent, this); + return this; + } + + resume() { + this.clock.resume(); + this.emit('resume', this.parent, this); + return this; + } + + stop() { + this.clock.stop(); + this.state = 0; + this.emit('stop', this.parent, this); + return this; + } + + seek(time) { + this.clock.seek(time); + return this; + } + + seekToNext() { + this.seek(this.nextTime); + return this; + } + + get isPlaying() { + return this.clock.isRunning; + } + + get completed() { + return (this.state === 2); + } + + get timeScale() { + return this.clock.timeScale; + } + + set timeScale(timeScale) { + this.clock.timeScale = timeScale; + } + + setTimeScale(timeScale) { + this.timeScale = timeScale; + return this; + } + + get now() { + return this.clock.now; + } + + update(now) { + if (this.nextTime > now) { + return this; + } + var lastCommandIndex = this.commands.length - 1; + while (1) { + // Execute a command + var item = this.commands[this.index]; + var command = item[1]; + if (!IsArray(command)) { // [dt, fnName, param0, param1, ...] + command = ArrayCopy(CMD, item, 1); + } + RunCommands(command, this.scope); + this.emit('runcommand', command, this.scope); + // Execute a command + + if (this.index === lastCommandIndex) { + this.complete(); + return this; + } else { + // Get next time + this.index++; // Point to next command + this.nextTime = this.getNextDt(this.nextTime); + if (this.nextTime > now) { + return this; + } + // Get next time + } + + } + } + + complete() { + this.clock.stop(); + this.state = 2; + this.emit('complete', this.parent, this); + } + + getNextDt(currentDt) { + var time = this.commands[this.index][0]; + if (this.timeUnit === 1) { // Second mode + time = time * 1000; + } + + if (this.dtMode === 1) { + time += currentDt; + } + + return time; + } + + setDtMode(dtMode) { + if (typeof (dtMode) === 'string') { + dtMode = DTMODE[dtMode]; + } + this.dtMode = dtMode; + return this; + } + + setTimeUnit(timeUnit) { + if (typeof (timeUnit) === 'string') { + timeUnit = TIMEUNITMODE[timeUnit]; + } + this.timeUnit = timeUnit; + return this; + } +} + +var CMD = []; // reuse this array + +const TIMEUNITMODE = { + ms: 0, + s: 1, + sec: 1, +}; + +const DTMODE = { + abs: 0, + absolute: 0, + inc: 1, + increment: 1 +}; + +export default Player; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Recorder.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Recorder.d.ts new file mode 100644 index 000000000..ebb29d9c7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Recorder.d.ts @@ -0,0 +1,39 @@ +import ComponentBase from '../../../utils/componentbase/ComponentBase'; + +export default Recorder; + +declare namespace Recorder { + interface IConfig { + + } +} + +declare class Recorder extends ComponentBase { + constructor( + parent: Phaser.Scene | Phaser.GameObjects.GameObject, + ); + + start(startAt?: number): this; + + addCommand( + command: any[], + offset?: number + ): this; + + getCommands(isRef?: boolean): any[]; + + clear(): this; + + pause(): this; + resume(): this; + stop(): this; + + seek(time: number): this; + + readonly isRecording: boolean; + readonly now: boolean; + + setTimeScale(timeScale: number): this; + timeScale: number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Recorder.js b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Recorder.js new file mode 100644 index 000000000..ac23748b0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/runcommands/tcrp/Recorder.js @@ -0,0 +1,127 @@ +import ComponentBase from '../../../utils/componentbase/ComponentBase.js'; +import Clock from '../../../clock.js'; +import Clone from '../../../utils/object/Clone.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Recorder extends ComponentBase { + constructor(parent, config) { + super(parent, config); + + var clock = GetValue(config, 'clock', undefined); + if (!clock) { + clock = new Clock(parent); + } + this.clock = clock; + + this.resetFromJSON(config); // This function had been called in super(config) + } + + resetFromJSON(o) { + this.clock.resetFromJSON(GetValue(o, 'clock', undefined)); + this.commands = GetValue(o, 'commands', []); // [[time, cmd], [time, cmd], ...] + return this; + } + + toJSON() { + return { + clock: this.clock.toJSON(), + commands: this.commands + }; + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.commands = undefined; + this.clock.shutdown(fromScene); + + super.shutdown(fromScene); + } + + start(startAt) { + this.clear(); + this.clock.start(startAt); + this.emit('start', this.parent, this); + return this; + } + + pause() { + this.clock.pause(); + this.emit('pause', this.parent, this); + return this; + } + + resume() { + this.clock.resume(); + this.emit('resume', this.parent, this); + return this; + } + + stop() { + this.clock.stop(); + this.emit('stop', this.parent, this); + return this; + } + + seek(time) { + this.clock.seek(time); + return this; + } + + get isRecording() { + return this.clock.isRunning; + } + + get timeScale() { + return this.clock.timeScale; + } + + set timeScale(timeScale) { + this.clock.timeScale = timeScale; + } + + setTimeScale(timeScale) { + this.timeScale = timeScale; + return this; + } + + get now() { + return this.clock.now; + } + + addCommand(command, offset) { + if (!this.isRecording) { + return this; + } + if (offset === undefined) { + offset = 0; + } + var time = this.clock.now + offset; + this.commands.push([time, command]); + return this; + } + + getCommands(isRef) { + if (isRef === undefined) { + isRef = false; + } + var commands; + if (isRef) { + commands = this.commands; + } else { + commands = Clone(this.commands); + } + return commands; + } + + clear() { + this.commands.length = 0; + return this; + } +} + +export default Recorder; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManager.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManager.d.ts new file mode 100644 index 000000000..bd2977a27 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManager.d.ts @@ -0,0 +1,67 @@ +import StateManagerBase from './StateManagerBase'; + +export default StateManager; + +declare namespace StateManager { + interface IState extends StateManagerBase.IState { + update?: Function, + preupdate?: Function, + postupdate?: Function, + } + + interface IConfig extends StateManagerBase.IConfig { + scene?: Phaser.Scene; + } +} + +declare class StateManager extends StateManagerBase { + constructor(config?: StateManager.IConfig); + + addState( + name: string, + state: StateManager.IState + ): this; + addState( + state: StateManager.IState + ): this; + + addStates( + state: StateManager.IState[] + ): this; + addStates( + states: { [name: string]: StateManager.IState }, + ): this; + + readonly _scene: Phaser.Scene; + getScene(): Phaser.Scene; + + update( + time: number, + delta: number + ): void; + + preupdate( + time: number, + delta: number + ): void; + + postupdate( + time: number, + delta: number + ): void; + + startUpdate( + scene?: Phaser.Scene + ): this; + stopUpdate(): this; + + startPreUpdate( + scene?: Phaser.Scene + ): this; + stopPreUpdate(): this; + + startPostUpdate( + scene?: Phaser.Scene + ): this; + stopPostUpdate(): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManager.js b/ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManager.js new file mode 100644 index 000000000..7516d8b6c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManager.js @@ -0,0 +1,110 @@ +import StateManagerBase from './StateManagerBase.js'; +import GetValue from '../../utils/object/GetValue.js'; +import HasListener from '../../utils/eventemitter/HasListener.js'; + +class StateManager extends StateManagerBase { + constructor(config) { + super(config); + + this._scene = GetValue(config, 'scene', undefined); + } + + shutdown() { + this.stopUpdate(); + this.stopPreUpdate(); + this.stopPostUpdate(); + this._scene = undefined; + + super.shutdown(); + } + + getScene() { + return this._scene; + } + + update(time, delta) { + this.runMethod('update', time, delta); + } + + preupdate(time, delta) { + this.runMethod('preupdate', time, delta); + } + + postupdate(time, delta) { + this.runMethod('postupdate', time, delta); + } + + startUpdate(scene) { + if (!scene) { + scene = this._scene; + } + + var eventEmitter = scene.sys.events; + if (HasListener(eventEmitter, 'update', this.update, this)) { + return this; + } + + this._scene = scene; + eventEmitter.on('update', this.update, this); + return this; + } + + stopUpdate() { + if (!this._scene) { + return this; + } + + this._scene.sys.events.off('update', this.update, this); + return this; + } + + startPreUpdate(scene) { + if (!scene) { + scene = this._scene; + } + + var eventEmitter = scene.sys.events; + if (HasListener(eventEmitter, 'preupdate', this.preupdate, this)) { + return this; + } + + this._scene = scene; + eventEmitter.on('preupdate', this.preupdate, this); + return this; + } + + stopOreUpdate() { + if (!this._scene) { + return this; + } + + this._scene.sys.events.off('preupdate', this.preupdate, this); + return this; + } + + startPostUpdate(scene) { + if (!scene) { + scene = this._scene; + } + + var eventEmitter = scene.sys.events; + if (HasListener(eventEmitter, 'postupdate', this.postupdate, this)) { + return this; + } + + this._scene = scene; + eventEmitter.on('postupdate', this.postupdate, this); + return this; + } + + stopPostUpdate() { + if (!this._scene) { + return this; + } + + this._scene.sys.events.off('postupdate', this.postupdate, this); + return this; + } +} + +export default StateManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManagerBase.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManagerBase.d.ts new file mode 100644 index 000000000..3a3d0ee90 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManagerBase.d.ts @@ -0,0 +1,58 @@ +import EventEmitter from '../../utils/eventemitter/EventEmitter'; + +export default StateManagerBase; + +declare namespace StateManagerBase { + + interface IState { + name?: string, + next?: string | (() => string), + enter?: Function, + exit?: Function, + } + + interface IConfig { + eventEmitter?: EventEmitter | false, + } + + namespace Events { + type StateChangeCallbackType = (state: StateManagerBase) => void; + type ExitStateCallbackType = (state: StateManagerBase) => void; + type EnterStateCallbackType = (state: StateManagerBase) => void; + } +} + +declare class StateManagerBase extends EventEmitter { + constructor(config?: StateManagerBase.IConfig); + + start(newState: string): this; + next(): this; + goto(nextState: string): this; + state: string; + readonly prevState: string; + readonly stateList: string[]; + + setEnable(enable?: boolean): this; + toggleEnable(): this; + enable: boolean; + + addState( + name: string, + state: StateManagerBase.IState + ): this; + addState( + state: StateManagerBase.IState + ): this; + + addStates( + state: StateManagerBase.IState[] + ): this; + addStates( + states: { [name: string]: StateManagerBase.IState }, + ): this; + + runMethod( + methodName: string, + ...args: unknown[] + ): unknown; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManagerBase.js b/ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManagerBase.js new file mode 100644 index 000000000..9ceeff207 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/statemanager/StateManagerBase.js @@ -0,0 +1,199 @@ +import EventEmitterMethods from '../../utils/eventemitter/EventEmitterMethods.js'; +import GetValue from '../../utils/object/GetValue.js'; + +class StateManagerBase { + constructor(config) { + this._states = {}; + this._stateLock = false; + this.enable = true; + this._start = undefined; + this._state = undefined; + this._prevState = undefined; + + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + + } + + shutdown() { + this.destroyEventEmitter(); + } + + destroy() { + this.shutdown(); + } + + toJSON() { + return { + curState: this.state, + prevState: this.prevState, + + enable: this.enable, + start: this._start + }; + } + + setEnable(e) { + if (e === undefined) { + e = true; + } + this.enable = e; + return this; + } + + toggleEnable() { + this.setEnable(!this.enable); + return this; + } + + getState(name) { + return this._states[name]; + } + + addState(name, state) { + if (typeof (name) !== 'string') { + state = name; + name = state.name; + } + this._states[name] = state; + return this; + } + + addStates(states) { + if (Array.isArray(states)) { + for (var i = 0, cnt = states.length; i < cnt; i++) { + this.addState(states[i]); + } + } else { + for (var name in states) { + this.addState(name, states[name]); + } + } + return this; + } + + removeState(name) { + if (this._states.hasOwnProperty(name)) { + delete this._states[name]; + } + return this; + } + + removeAllStates() { + for (var name in this._states) { + delete this._states[name]; + } + return this; + } + + set state(newState) { + if (!this.enable || this._stateLock) { + return; + } + if (this._state === newState) { + return; + } + + this._prevState = this._state; + this._state = newState; + + this._stateLock = true; // Lock state + + this.emit('statechange', this); + + if (this._prevState != null) { + var state = this.getState(this._prevState); + if (state && state.exit) { + state.exit(this); + } + this.emit(`exit_${this._prevState}`, this); + } + + this._stateLock = false; + + if (this._state != null) { + var state = this.getState(this._state); + if (state && state.enter) { + state.enter(this); + } + this.emit(`enter_${this._state}`, this); + } + } + + get state() { + return this._state; + } + + get prevState() { + return this._prevState; + } + + get stateList() { + return Object.keys(this._states); + } + + start(state) { + this._start = state; + this._prevState = undefined; + this._state = state; // Won't fire statechange events + return this; + } + + goto(nextState) { + if (nextState != null) { + this.state = nextState; + } + return this; + } + + next() { + var state = this.getState(this.state); + if (!state || !state.next) { + return this; + } + + var nextState; + if (typeof (state.next) === 'string') { + nextState = state.next; + } else { + nextState = state.next(this); + } + this.goto(nextState); + return this; + } + + runMethod(methodName, a1, a2, a3, a4, a5) { + var state = this.getState(this.state); + if (!state) { + return undefined; + } + var fn = state[methodName]; + if (!fn) { + return undefined; + } + + // Copy from eventemitter3 + var len = arguments.length; + switch (len) { + case 1: return fn(this); + case 2: return fn(this, a1); + case 3: return fn(this, a1, a2); + case 4: return fn(this, a1, a2, a3); + case 5: return fn(this, a1, a2, a3, a4); + case 6: return fn(this, a1, a2, a3, a4, a5); + } + + var args = Array.prototype.slice.call(arguments); + args[0] = this; + return fn.apply(undefined, args); + } +} + +Object.assign( + StateManagerBase.prototype, + EventEmitterMethods +); + +export default StateManagerBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/waitevents/WaitEvents.d.ts b/ui/src/phaser3-rex-plugins/plugins/logic/waitevents/WaitEvents.d.ts new file mode 100644 index 000000000..492bee655 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/waitevents/WaitEvents.d.ts @@ -0,0 +1,32 @@ +export default WaitEvents; + +declare namespace WaitEvents { + type CompleteCallbackType = () => void; +} + +declare class WaitEvents { + constructor( + completeCallback?: WaitEvents.CompleteCallbackType, + scope?: object + ); + + setCompleteCallback( + completeCallback: WaitEvents.CompleteCallbackType, + scope?: object + ): this; + + waitCallback(): this; + + waitEvent( + eventEmitter: Phaser.Events.EventEmitter, + eventName: string + ): this; + + remove( + callback: WaitEvents.CompleteCallbackType, + ): this; + + clear(): this; + + readonly noWaitEvent: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/logic/waitevents/WaitEvents.js b/ui/src/phaser3-rex-plugins/plugins/logic/waitevents/WaitEvents.js new file mode 100644 index 000000000..649b0456f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/logic/waitevents/WaitEvents.js @@ -0,0 +1,62 @@ +const SetStruct = Phaser.Structs.Set; +class WaitEvents { + constructor(completeCallback, scope) { + this.setCompleteCallback(completeCallback, scope); + this.events = new SetStruct(); + } + + shutdown() { + this.setCompleteCallback(undefined, undefined); + this.events.clear(); + this.event = undefined; + return this; + } + + destroy() { + this.shutdown(); + return this; + } + + setCompleteCallback(callback, scope) { + this.completeCallback = callback; + this.scope = scope; + return this; + } + + waitCallback() { + var self = this; + var callback = function () { + self.remove(callback); + } + this.events.set(callback); + return callback; + } + + waitEvent(eventEmitter, eventName) { + eventEmitter.once(eventName, this.waitCallback()); + return this; + } + + remove(callback) { + this.events.delete(callback); + if (this.noWaitEvent) { + if (this.scope) { + this.completeCallback.call(this.scope); + } else { + this.completeCallback(); + } + } + return this; + } + + clear() { + this.events.clear(); + return this; + } + + get noWaitEvent() { + return this.events.size === 0; + } +} + +export default WaitEvents; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loopinticks-plugin.js b/ui/src/phaser3-rex-plugins/plugins/loopinticks-plugin.js new file mode 100644 index 000000000..9a77356c4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loopinticks-plugin.js @@ -0,0 +1,18 @@ +import LoopInTicks from './loopinticks.js' + +class LoopInTicksPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new LoopInTicks(scene, config); + } +} + +export default LoopInTicksPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/loopinticks.js b/ui/src/phaser3-rex-plugins/plugins/loopinticks.js new file mode 100644 index 000000000..ecde49dd3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/loopinticks.js @@ -0,0 +1,2 @@ +import LoopInTicks from './logic/loopinticks/LoopInTicks.js'; +export default LoopInTicks; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lzstring-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/lzstring-plugin.d.ts new file mode 100644 index 000000000..b5320db02 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lzstring-plugin.d.ts @@ -0,0 +1,8 @@ +import LZString from './lzstring'; + +export default class LZStringPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: LZString.IConfig + ): LZString; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lzstring-plugin.js b/ui/src/phaser3-rex-plugins/plugins/lzstring-plugin.js new file mode 100644 index 000000000..5738c7466 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lzstring-plugin.js @@ -0,0 +1,29 @@ +import LZString from './lzstring.js'; + +class LZStringPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + + this.lzstring = new LZString(); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + destroy() { + this.lzstring = null; + + + this.pluginManager = null; + this.game = null; + this.scene = null; + this.systems = null; + } + + add(config) { + return new LZString(config); + } +} +export default LZStringPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lzstring.d.ts b/ui/src/phaser3-rex-plugins/plugins/lzstring.d.ts new file mode 100644 index 000000000..dc2c6fe11 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lzstring.d.ts @@ -0,0 +1,2 @@ +import LZString from './string/lzstring/LZString'; +export default LZString; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/lzstring.js b/ui/src/phaser3-rex-plugins/plugins/lzstring.js new file mode 100644 index 000000000..7846c01e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/lzstring.js @@ -0,0 +1,2 @@ +import LZString from './string/lzstring/LZString.js'; +export default LZString; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/markedeventsheets-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/markedeventsheets-plugin.d.ts new file mode 100644 index 000000000..691294bfa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/markedeventsheets-plugin.d.ts @@ -0,0 +1,8 @@ +import MarkedEventSheets from './logic/eventsheets/markedeventsheets/MarkedEventSheets.js'; + +export default class MarkedEventSheetsPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: MarkedEventSheets.IConfig + ): MarkedEventSheets; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/markedeventsheets-plugin.js b/ui/src/phaser3-rex-plugins/plugins/markedeventsheets-plugin.js new file mode 100644 index 000000000..99f379ad5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/markedeventsheets-plugin.js @@ -0,0 +1,22 @@ +import MarkedEventSheets from './logic/eventsheets/markedeventsheets/MarkedEventSheets.js'; +import TaskHandlers from './logic/runcommands/managers/Managers.js'; +import SetValue from './utils/object/SetValue.js'; + +class MarkedEventSheetsPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new MarkedEventSheets(config); + } +} + +SetValue(window, 'RexPlugins.TaskHandlers', TaskHandlers); + +export default MarkedEventSheetsPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/markedeventsheets.d.ts b/ui/src/phaser3-rex-plugins/plugins/markedeventsheets.d.ts new file mode 100644 index 000000000..e84349c67 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/markedeventsheets.d.ts @@ -0,0 +1,2 @@ +import MarkedEventSheets from './logic/eventsheets/markedeventsheets/MarkedEventSheets'; +export default MarkedEventSheets; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/markedeventsheets.js b/ui/src/phaser3-rex-plugins/plugins/markedeventsheets.js new file mode 100644 index 000000000..e5898535e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/markedeventsheets.js @@ -0,0 +1,2 @@ +import MarkedEventSheets from './logic/eventsheets/markedeventsheets/MarkedEventSheets.js'; +export default MarkedEventSheets; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/ExpressionParser.d.ts b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/ExpressionParser.d.ts new file mode 100644 index 000000000..b44415ef5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/ExpressionParser.d.ts @@ -0,0 +1,32 @@ +export default ExpressionParser; + +declare namespace ExpressionParser { + type ExpressionCallbackType = ( + context: object + ) => number +} + +declare class ExpressionParser { + + compile( + expression: string + ): ExpressionParser.ExpressionCallbackType; + + exec( + expression: string, + context: object + ): number; + + exec( + expressionCallback: ExpressionParser.ExpressionCallbackType, + context: object + ): number; + + static GetProperty( + context: Object, + key: string | string[], + defaultValue: any, + dotMode?: boolean + ): any; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/ExpressionParser.js b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/ExpressionParser.js new file mode 100644 index 000000000..f24def55b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/ExpressionParser.js @@ -0,0 +1,85 @@ +import parser from './parser/parser.js'; +import GetProperty from './GetProperty.js'; + +class FormulaParser extends parser.Parser { + getProperty(context, name, defaultValue) { + var value = GetProperty(context, name, undefined, false); + if (value !== undefined) { + return value; + } + return GetProperty(this, name, defaultValue, false); + } + + getDotProperty(context, name, defaultValue) { + var value = GetProperty(context, name, undefined, true); + if (value !== undefined) { + return value; + } + return GetProperty(this, name, defaultValue, true); + } + + static GetProperty(context, key, defaultValue, dotMode) { + return GetProperty(context, key, defaultValue, dotMode); + } + + _add(a, b) { + return a + b; + } + + _subtract(a, b) { + return a - b; + } + + _multiply(a, b) { + return a * b; + } + + _divide(a, b) { + return a / b; + } + + _mod(a, b) { + return a % b; + } + + _pow(a, b) { + return Math.pow(a, b); + } + + _greaterThen(a, b) { + return a > b; + } + + _lessThen(a, b) { + return a < b; + } + + _equalTo(a, b) { + return a == b; + } + + _or(a, b) { + return a || b; + } + + _and(a, b) { + return a && b; + } + + defaultHandler(name, args) { + return 0; + } + + compile(input) { + return this.parse(input); + } + + exec(input, data) { + if (typeof (input) === 'string') { + input = this.compile(input); + } + return input(data); + } +} + +export default FormulaParser; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/GetProperty.js b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/GetProperty.js new file mode 100644 index 000000000..e559f6de9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/GetProperty.js @@ -0,0 +1,33 @@ +var GetProperty = function (context, key, defaultValue, dotMode) { + if (dotMode === undefined) { + dotMode = true; + } + + if (!context || typeof (context) === 'number' || typeof (context) === 'string') { + return defaultValue; + } else if (key in context) { + return context[key]; + } else if (dotMode && + ((Array.isArray(key) || (key.indexOf('.') !== -1))) + ) { + var keys = (Array.isArray(key)) ? key : key.split('.'); + var value = context; + // Use for loop here so we can break early + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + if (key in value) { + value = value[key]; + } + else { + value = defaultValue; + break; + } + } + + return value; + } else { + return defaultValue; + } +} + +export default GetProperty; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/export-parser.bat b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/export-parser.bat new file mode 100644 index 000000000..5ae2cb025 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/export-parser.bat @@ -0,0 +1 @@ +node export.js \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/export.js b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/export.js new file mode 100644 index 000000000..96d2d960a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/export.js @@ -0,0 +1,26 @@ +var fs = require('fs'); +var jison = require("jison"); + +console.log("In progress..."); + +var parser = new jison.Parser(fs.readFileSync("grammar.jison", "utf8")); + +// generate source, ready to be written to disk +var parserSource = parser.generate(); +// console.log('Source: ', parserSource) + +var replaceSource = `if (typeof module !== 'undefined' && require.main === module) {` +var replacedBy = `if (0) { // Ignore 'require.main'` +parserSource = parserSource.replace(replaceSource, replacedBy); + +var replaceSource = `exports.main(process.argv.slice(1));` +var replacedBy = `//exports.main(process.argv.slice(1));` +parserSource = parserSource.replace(replaceSource, replacedBy); + +try { + fs.writeFileSync("./parser.js", parserSource) + //file written successfully + console.log("Ok. The file parser was saved!"); +} catch (err) { + console.error(err) +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/grammar.jison b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/grammar.jison new file mode 100644 index 000000000..3166cc42f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/grammar.jison @@ -0,0 +1,229 @@ +/* description: Parses end executes mathematical expressions. */ + +/* lexical grammar */ +%lex +%% + +\s+ /* skip whitespace */ +[0-9]+("."[0-9]+)?\b return 'NUMBER' +\b0x[0-9A-Fa-f]+\b return 'HEXNUMBER' +"*" return '*' +"/" return '/' +"-" return '-' +"+" return '+' +"^" return '^' +"%" return '%' +">=" return ">=" +"<=" return "<=" +">" return '>' +"<" return '<' +"==" return "==" +"!=" return "!=" +"||" return "||" +"&&" return "&&" +"?" return "?" +":" return ":" +"(" return '(' +")" return ')' +"[" return '[' +"]" return ']' +"," return ',' +"." return '.' +'true' return 'true' +'false' return 'false' +[^\s\*\/\-\+\^\%\>\=\<\!\|\&\?\:\(\)\[\]\,\.]+ return 'NAME' +\"(\\.|[^\"\\])*\"|\'(\\.|[^\'\\])*\' return 'QUOTED_STRING' +<> return 'EOF' +. return 'INVALID' + +/lex + +%{ + function runFn(arg, ctx) { + return (typeof(arg) === 'function')? arg(ctx) : arg; + } + + function mapArgs(args, ctx) { + if (args) { + args = args.map(function(arg){ return runFn(arg, ctx); }); + } + return args; + } + + function runBuildInMethod(self, ctx, name, args) { + var callback = self[name]; + return callback.apply(self, mapArgs(args, ctx)); + } + + function runMethod(self, ctx, name, args, dotMode) { + var names; + if (typeof(name) === 'string') { + if (dotMode) { + names = name.split('.'); + } else { + names = [name]; + } + } else { + names = name; + } + + var callback, scope; + if (names.length > 1) { + var callbackName = names.pop(); + scope = self.getDotProperty(ctx, names); + callback = scope[callbackName]; + } else { + callback = self.getProperty(ctx, name); + scope = self; + } + + if (callback == null) { + callback = self.getProperty(ctx, 'defaultHandler'); + scope = self; + } + + return callback.apply(scope, mapArgs(args, ctx)); + } +%} + +/* operator associations and precedence */ + +%left '?' ':' +%left '||' '&&' +%left '>' '<' '==' '!=' '>=' '<=' +%left '+' '-' +%left '%' +%left '*' '/' +%left '^' +%left UMINUS + +%start expressions + +%% /* language grammar */ + +expressions + : e EOF + { + var result = $1; + if (typeof(result) === 'function') { + return result; + } else { + return function(ctx) { return result; } + } + } + ; + +expression_list + : expression_list ',' e + { $$ = $1.concat([$3]); } + | e + { $$ = [$1]; } + ; + +dot_name + : dot_name '.' NAME + { $$ = $1.concat([$3]); } + | dot_name '[' e ']' + { $$ = $1.concat([$3]); } + | NAME + { $$ = [$1]; } + ; + +e + : e '+' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_add', [$1, $3]); }; + } + | e '-' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_subtract', [$1, $3]); }; + } + | e '*' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_multiply', [$1, $3]); }; + } + | e '/' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_divide', [$1, $3]); }; + } + | e '%' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_mod', [$1, $3]); }; + } + | e '^' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_pow', [$1, $3]); }; + } + | e '>' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_greaterThen', [$1, $3]) == true; }; + } + | e '<' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_lessThen', [$1, $3]) == true; }; + } + | e '==' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_equalTo', [$1, $3]) == true; }; + } + | e '!=' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_equalTo', [$1, $3]) == false; }; + } + | e '>=' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_lessThen', [$1, $3]) == false; }; + } + | e '<=' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_greaterThen', [$1, $3]) == false; }; + } + | e '||' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_or', [$1, $3]) == true; }; + } + | e '&&' e + { + $$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_and', [$1, $3]) == true; }; + } + | '-' e %prec UMINUS + { + $$ = function(ctx) { return -runFn($2, ctx); }; + } + | '(' e ')' + { + $$ = function(ctx) { return runFn($2, ctx); }; + } + | '(' e ')' '?' e ':' e + { + $$ = function(ctx) { return runFn($2, ctx)? runFn($5, ctx) : runFn($7, ctx); }; + } + | 'true' + { $$ = true; } + | 'false' + { $$ = false; } + | dot_name + { + $$ = function(ctx) { + return yy.parser.getDotProperty(ctx, mapArgs($1, ctx), 0); + } + } + | dot_name '(' ')' + { + $$ = function(ctx) { + return runMethod(yy.parser, ctx, mapArgs($1, ctx), undefined, true); + } + } + | dot_name '(' expression_list ')' + { + $$ = function(ctx) { + return runMethod(yy.parser, ctx, mapArgs($1, ctx), $3, true); + } + } + | QUOTED_STRING + { $$ = yytext.slice(1,-1); } + | NUMBER + { $$ = Number(yytext); } + | HEXNUMBER + { $$ = parseInt(yytext, 16); } + ; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/parser.js b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/parser.js new file mode 100644 index 000000000..a8c82171c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/parser/parser.js @@ -0,0 +1,843 @@ +/* parser generated by jison 0.4.18 */ +/* + Returns a Parser object of the following structure: + + Parser: { + yy: {} + } + + Parser.prototype: { + yy: {}, + trace: function(), + symbols_: {associative list: name ==> number}, + terminals_: {associative list: number ==> name}, + productions_: [...], + performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), + table: [...], + defaultActions: {...}, + parseError: function(str, hash), + parse: function(input), + + lexer: { + EOF: 1, + parseError: function(str, hash), + setInput: function(input), + input: function(), + unput: function(str), + more: function(), + less: function(n), + pastInput: function(), + upcomingInput: function(), + showPosition: function(), + test_match: function(regex_match_array, rule_index), + next: function(), + lex: function(), + begin: function(condition), + popState: function(), + _currentRules: function(), + topState: function(), + pushState: function(condition), + + options: { + ranges: boolean (optional: true ==> token location info will include a .range[] member) + flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) + backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) + }, + + performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), + rules: [...], + conditions: {associative list: name ==> set}, + } + } + + + token location info (@$, _$, etc.): { + first_line: n, + last_line: n, + first_column: n, + last_column: n, + range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) + } + + + the parseError function receives a 'hash' object with these members for lexer and parser errors: { + text: (matched text) + token: (the produced terminal token, if any) + line: (yylineno) + } + while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { + loc: (yylloc) + expected: (string describing the set of expected tokens) + recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) + } +*/ +var parser = (function(){ +var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,11],$V1=[1,3],$V2=[1,4],$V3=[1,5],$V4=[1,6],$V5=[1,8],$V6=[1,9],$V7=[1,10],$V8=[1,13],$V9=[1,14],$Va=[1,15],$Vb=[1,16],$Vc=[1,17],$Vd=[1,18],$Ve=[1,19],$Vf=[1,20],$Vg=[1,21],$Vh=[1,22],$Vi=[1,23],$Vj=[1,24],$Vk=[1,25],$Vl=[1,26],$Vm=[5,7,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,28,30],$Vn=[5,7,9,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,30],$Vo=[5,7,12,13,14,19,20,21,22,23,24,25,26,28,30],$Vp=[5,7,12,13,14,15,16,17,19,20,21,22,23,24,25,26,28,30],$Vq=[5,7,12,19,20,21,22,23,24,25,26,28,30],$Vr=[5,7,12,25,26,28,30],$Vs=[7,28]; +var parser = {trace: function trace () { }, +yy: {}, +symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"expression_list":6,",":7,"dot_name":8,".":9,"NAME":10,"[":11,"]":12,"+":13,"-":14,"*":15,"/":16,"%":17,"^":18,">":19,"<":20,"==":21,"!=":22,">=":23,"<=":24,"||":25,"&&":26,"(":27,")":28,"?":29,":":30,"true":31,"false":32,"QUOTED_STRING":33,"NUMBER":34,"HEXNUMBER":35,"$accept":0,"$end":1}, +terminals_: {2:"error",5:"EOF",7:",",9:".",10:"NAME",11:"[",12:"]",13:"+",14:"-",15:"*",16:"/",17:"%",18:"^",19:">",20:"<",21:"==",22:"!=",23:">=",24:"<=",25:"||",26:"&&",27:"(",28:")",29:"?",30:":",31:"true",32:"false",33:"QUOTED_STRING",34:"NUMBER",35:"HEXNUMBER"}, +productions_: [0,[3,2],[6,3],[6,1],[8,3],[8,4],[8,1],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,2],[4,3],[4,7],[4,1],[4,1],[4,1],[4,3],[4,4],[4,1],[4,1],[4,1]], +performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) { +/* this == yyval */ + +var $0 = $$.length - 1; +switch (yystate) { +case 1: + + var result = $$[$0-1]; + if (typeof(result) === 'function') { + return result; + } else { + return function(ctx) { return result; } + } + +break; +case 2: case 4: + this.$ = $$[$0-2].concat([$$[$0]]); +break; +case 3: case 6: + this.$ = [$$[$0]]; +break; +case 5: + this.$ = $$[$0-3].concat([$$[$0-1]]); +break; +case 7: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_add', [$$[$0-2], $$[$0]]); }; + +break; +case 8: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_subtract', [$$[$0-2], $$[$0]]); }; + +break; +case 9: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_multiply', [$$[$0-2], $$[$0]]); }; + +break; +case 10: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_divide', [$$[$0-2], $$[$0]]); }; + +break; +case 11: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_mod', [$$[$0-2], $$[$0]]); }; + +break; +case 12: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_pow', [$$[$0-2], $$[$0]]); }; + +break; +case 13: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_greaterThen', [$$[$0-2], $$[$0]]) == true; }; + +break; +case 14: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_lessThen', [$$[$0-2], $$[$0]]) == true; }; + +break; +case 15: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_equalTo', [$$[$0-2], $$[$0]]) == true; }; + +break; +case 16: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_equalTo', [$$[$0-2], $$[$0]]) == false; }; + +break; +case 17: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_lessThen', [$$[$0-2], $$[$0]]) == false; }; + +break; +case 18: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_greaterThen', [$$[$0-2], $$[$0]]) == false; }; + +break; +case 19: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_or', [$$[$0-2], $$[$0]]) == true; }; + +break; +case 20: + + this.$ = function(ctx) { return runBuildInMethod(yy.parser, ctx, '_and', [$$[$0-2], $$[$0]]) == true; }; + +break; +case 21: + + this.$ = function(ctx) { return -runFn($$[$0], ctx); }; + +break; +case 22: + + this.$ = function(ctx) { return runFn($$[$0-1], ctx); }; + +break; +case 23: + + this.$ = function(ctx) { return runFn($$[$0-5], ctx)? runFn($$[$0-2], ctx) : runFn($$[$0], ctx); }; + +break; +case 24: + this.$ = true; +break; +case 25: + this.$ = false; +break; +case 26: + + this.$ = function(ctx) { + return yy.parser.getDotProperty(ctx, mapArgs($$[$0], ctx), 0); + } + +break; +case 27: + + this.$ = function(ctx) { + return runMethod(yy.parser, ctx, mapArgs($$[$0-2], ctx), undefined, true); + } + +break; +case 28: + + this.$ = function(ctx) { + return runMethod(yy.parser, ctx, mapArgs($$[$0-3], ctx), $$[$0-1], true); + } + +break; +case 29: + this.$ = yytext.slice(1,-1); +break; +case 30: + this.$ = Number(yytext); +break; +case 31: + this.$ = parseInt(yytext, 16); +break; +} +}, +table: [{3:1,4:2,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{1:[3]},{5:[1,12],13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd,19:$Ve,20:$Vf,21:$Vg,22:$Vh,23:$Vi,24:$Vj,25:$Vk,26:$Vl},{4:27,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:28,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},o($Vm,[2,24]),o($Vm,[2,25]),o($Vm,[2,26],{9:[1,30],11:[1,31],27:[1,29]}),o($Vm,[2,29]),o($Vm,[2,30]),o($Vm,[2,31]),o($Vn,[2,6]),{1:[2,1]},{4:32,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:33,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:34,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:35,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:36,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:37,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:38,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:39,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:40,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:41,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:42,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:43,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:44,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{4:45,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},o($Vm,[2,21]),{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd,19:$Ve,20:$Vf,21:$Vg,22:$Vh,23:$Vi,24:$Vj,25:$Vk,26:$Vl,28:[1,46]},{4:49,6:48,8:7,10:$V0,14:$V1,27:$V2,28:[1,47],31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},{10:[1,50]},{4:51,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},o($Vo,[2,7],{15:$Va,16:$Vb,17:$Vc,18:$Vd}),o($Vo,[2,8],{15:$Va,16:$Vb,17:$Vc,18:$Vd}),o($Vp,[2,9],{18:$Vd}),o($Vp,[2,10],{18:$Vd}),o([5,7,12,13,14,17,19,20,21,22,23,24,25,26,28,30],[2,11],{15:$Va,16:$Vb,18:$Vd}),o($Vm,[2,12]),o($Vq,[2,13],{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd}),o($Vq,[2,14],{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd}),o($Vq,[2,15],{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd}),o($Vq,[2,16],{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd}),o($Vq,[2,17],{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd}),o($Vq,[2,18],{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd}),o($Vr,[2,19],{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd,19:$Ve,20:$Vf,21:$Vg,22:$Vh,23:$Vi,24:$Vj}),o($Vr,[2,20],{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd,19:$Ve,20:$Vf,21:$Vg,22:$Vh,23:$Vi,24:$Vj}),o($Vm,[2,22],{29:[1,52]}),o($Vm,[2,27]),{7:[1,54],28:[1,53]},o($Vs,[2,3],{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd,19:$Ve,20:$Vf,21:$Vg,22:$Vh,23:$Vi,24:$Vj,25:$Vk,26:$Vl}),o($Vn,[2,4]),{12:[1,55],13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd,19:$Ve,20:$Vf,21:$Vg,22:$Vh,23:$Vi,24:$Vj,25:$Vk,26:$Vl},{4:56,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},o($Vm,[2,28]),{4:57,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},o($Vn,[2,5]),{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd,19:$Ve,20:$Vf,21:$Vg,22:$Vh,23:$Vi,24:$Vj,25:$Vk,26:$Vl,30:[1,58]},o($Vs,[2,2],{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd,19:$Ve,20:$Vf,21:$Vg,22:$Vh,23:$Vi,24:$Vj,25:$Vk,26:$Vl}),{4:59,8:7,10:$V0,14:$V1,27:$V2,31:$V3,32:$V4,33:$V5,34:$V6,35:$V7},o([5,7,12,28,30],[2,23],{13:$V8,14:$V9,15:$Va,16:$Vb,17:$Vc,18:$Vd,19:$Ve,20:$Vf,21:$Vg,22:$Vh,23:$Vi,24:$Vj,25:$Vk,26:$Vl})], +defaultActions: {12:[2,1]}, +parseError: function parseError (str, hash) { + if (hash.recoverable) { + this.trace(str); + } else { + var error = new Error(str); + error.hash = hash; + throw error; + } +}, +parse: function parse(input) { + var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; + var args = lstack.slice.call(arguments, 1); + var lexer = Object.create(this.lexer); + var sharedState = { yy: {} }; + for (var k in this.yy) { + if (Object.prototype.hasOwnProperty.call(this.yy, k)) { + sharedState.yy[k] = this.yy[k]; + } + } + lexer.setInput(input, sharedState.yy); + sharedState.yy.lexer = lexer; + sharedState.yy.parser = this; + if (typeof lexer.yylloc == 'undefined') { + lexer.yylloc = {}; + } + var yyloc = lexer.yylloc; + lstack.push(yyloc); + var ranges = lexer.options && lexer.options.ranges; + if (typeof sharedState.yy.parseError === 'function') { + this.parseError = sharedState.yy.parseError; + } else { + this.parseError = Object.getPrototypeOf(this).parseError; + } + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + _token_stack: + var lex = function () { + var token; + token = lexer.lex() || EOF; + if (typeof token !== 'number') { + token = self.symbols_[token] || token; + } + return token; + }; + var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == 'undefined') { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === 'undefined' || !action.length || !action[0]) { + var errStr = ''; + expected = []; + for (p in table[state]) { + if (this.terminals_[p] && p > TERROR) { + expected.push('\'' + this.terminals_[p] + '\''); + } + } + if (lexer.showPosition) { + errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\''; + } else { + errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\''); + } + this.parseError(errStr, { + text: lexer.match, + token: this.terminals_[symbol] || symbol, + line: lexer.yylineno, + loc: yyloc, + expected: expected + }); + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(lexer.yytext); + lstack.push(lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = lexer.yyleng; + yytext = lexer.yytext; + yylineno = lexer.yylineno; + yyloc = lexer.yylloc; + if (recovering > 0) { + recovering--; + } + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = { + first_line: lstack[lstack.length - (len || 1)].first_line, + last_line: lstack[lstack.length - 1].last_line, + first_column: lstack[lstack.length - (len || 1)].first_column, + last_column: lstack[lstack.length - 1].last_column + }; + if (ranges) { + yyval._$.range = [ + lstack[lstack.length - (len || 1)].range[0], + lstack[lstack.length - 1].range[1] + ]; + } + r = this.performAction.apply(yyval, [ + yytext, + yyleng, + yylineno, + sharedState.yy, + action[1], + vstack, + lstack + ].concat(args)); + if (typeof r !== 'undefined') { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; +}}; + + function runFn(arg, ctx) { + return (typeof(arg) === 'function')? arg(ctx) : arg; + } + + function mapArgs(args, ctx) { + if (args) { + args = args.map(function(arg){ return runFn(arg, ctx); }); + } + return args; + } + + function runBuildInMethod(self, ctx, name, args) { + var callback = self[name]; + return callback.apply(self, mapArgs(args, ctx)); + } + + function runMethod(self, ctx, name, args, dotMode) { + var names; + if (typeof(name) === 'string') { + if (dotMode) { + names = name.split('.'); + } else { + names = [name]; + } + } else { + names = name; + } + + var callback, scope; + if (names.length > 1) { + var callbackName = names.pop(); + scope = self.getDotProperty(ctx, names); + callback = scope[callbackName]; + } else { + callback = self.getProperty(ctx, name); + scope = self; + } + + if (callback == null) { + callback = self.getProperty(ctx, 'defaultHandler'); + scope = self; + } + + return callback.apply(scope, mapArgs(args, ctx)); + } +/* generated by jison-lex 0.3.4 */ +var lexer = (function(){ +var lexer = ({ + +EOF:1, + +parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, + +// resets the lexer, sets new input +setInput:function (input, yy) { + this.yy = yy || this.yy || {}; + this._input = input; + this._more = this._backtrack = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = { + first_line: 1, + first_column: 0, + last_line: 1, + last_column: 0 + }; + if (this.options.ranges) { + this.yylloc.range = [0,0]; + } + this.offset = 0; + return this; + }, + +// consumes and returns one char from the input +input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) { + this.yylloc.range[1]++; + } + + this._input = this._input.slice(1); + return ch; + }, + +// unshifts one char (or a string) into the input +unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length - len); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length - 1); + this.matched = this.matched.substr(0, this.matched.length - 1); + + if (lines.length - 1) { + this.yylineno -= lines.length - 1; + } + var r = this.yylloc.range; + + this.yylloc = { + first_line: this.yylloc.first_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + + oldLines[oldLines.length - lines.length].length - lines[0].length : + this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + this.yyleng = this.yytext.length; + return this; + }, + +// When called from action, caches matched text and appends it on next action +more:function () { + this._more = true; + return this; + }, + +// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead. +reject:function () { + if (this.options.backtrack_lexer) { + this._backtrack = true; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), { + text: "", + token: null, + line: this.yylineno + }); + + } + return this; + }, + +// retain first n characters of the match +less:function (n) { + this.unput(this.match.slice(n)); + }, + +// displays already matched input, i.e. for error messages +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, + +// displays upcoming input, i.e. for error messages +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); + }, + +// displays the character position where the lexing error occurred, i.e. for error messages +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c + "^"; + }, + +// test the lexed token: return FALSE when not a match, otherwise return token +test_match:function(match, indexed_rule) { + var token, + lines, + backup; + + if (this.options.backtrack_lexer) { + // save context + backup = { + yylineno: this.yylineno, + yylloc: { + first_line: this.yylloc.first_line, + last_line: this.last_line, + first_column: this.yylloc.first_column, + last_column: this.yylloc.last_column + }, + yytext: this.yytext, + match: this.match, + matches: this.matches, + matched: this.matched, + yyleng: this.yyleng, + offset: this.offset, + _more: this._more, + _input: this._input, + yy: this.yy, + conditionStack: this.conditionStack.slice(0), + done: this.done + }; + if (this.options.ranges) { + backup.yylloc.range = this.yylloc.range.slice(0); + } + } + + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno += lines.length; + } + this.yylloc = { + first_line: this.yylloc.last_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.last_column, + last_column: lines ? + lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : + this.yylloc.last_column + match[0].length + }; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._backtrack = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]); + if (this.done && this._input) { + this.done = false; + } + if (token) { + return token; + } else if (this._backtrack) { + // recover context + for (var k in backup) { + this[k] = backup[k]; + } + return false; // rule action called reject() implying the next rule should be tested instead. + } + return false; + }, + +// return next match in input +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) { + this.done = true; + } + + var token, + match, + tempMatch, + index; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i = 0; i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (this.options.backtrack_lexer) { + token = this.test_match(tempMatch, rules[i]); + if (token !== false) { + return token; + } else if (this._backtrack) { + match = false; + continue; // rule action called reject() implying a rule MISmatch. + } else { + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return false; + } + } else if (!this.options.flex) { + break; + } + } + } + if (match) { + token = this.test_match(match, rules[index]); + if (token !== false) { + return token; + } + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return false; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { + text: "", + token: null, + line: this.yylineno + }); + } + }, + +// return next match that has a token +lex:function lex () { + var r = this.next(); + if (r) { + return r; + } else { + return this.lex(); + } + }, + +// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack) +begin:function begin (condition) { + this.conditionStack.push(condition); + }, + +// pop the previously active lexer condition state off the condition stack +popState:function popState () { + var n = this.conditionStack.length - 1; + if (n > 0) { + return this.conditionStack.pop(); + } else { + return this.conditionStack[0]; + } + }, + +// produce the lexer rule set which is active for the currently active lexer condition state +_currentRules:function _currentRules () { + if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) { + return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; + } else { + return this.conditions["INITIAL"].rules; + } + }, + +// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available +topState:function topState (n) { + n = this.conditionStack.length - 1 - Math.abs(n || 0); + if (n >= 0) { + return this.conditionStack[n]; + } else { + return "INITIAL"; + } + }, + +// alias for begin(condition) +pushState:function pushState (condition) { + this.begin(condition); + }, + +// return the number of states currently on the stack +stateStackSize:function stateStackSize() { + return this.conditionStack.length; + }, +options: {}, +performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { +var YYSTATE=YY_START; +switch($avoiding_name_collisions) { +case 0:/* skip whitespace */ +break; +case 1:return 34 +break; +case 2:return 35 +break; +case 3:return 15 +break; +case 4:return 16 +break; +case 5:return 14 +break; +case 6:return 13 +break; +case 7:return 18 +break; +case 8:return 17 +break; +case 9:return ">=" +break; +case 10:return "<=" +break; +case 11:return 19 +break; +case 12:return 20 +break; +case 13:return "==" +break; +case 14:return "!=" +break; +case 15:return "||" +break; +case 16:return "&&" +break; +case 17:return "?" +break; +case 18:return ":" +break; +case 19:return 27 +break; +case 20:return 28 +break; +case 21:return 11 +break; +case 22:return 12 +break; +case 23:return 7 +break; +case 24:return 9 +break; +case 25:return 31 +break; +case 26:return 32 +break; +case 27:return 10 +break; +case 28:return 33 +break; +case 29:return 5 +break; +case 30:return 'INVALID' +break; +} +}, +rules: [/^(?:\s+)/,/^(?:[0-9]+(\.[0-9]+)?\b)/,/^(?:\b0x[0-9A-Fa-f]+\b)/,/^(?:\*)/,/^(?:\/)/,/^(?:-)/,/^(?:\+)/,/^(?:\^)/,/^(?:%)/,/^(?:>=)/,/^(?:<=)/,/^(?:>)/,/^(?:<)/,/^(?:==)/,/^(?:!=)/,/^(?:\|\|)/,/^(?:&&)/,/^(?:\?)/,/^(?::)/,/^(?:\()/,/^(?:\))/,/^(?:\[)/,/^(?:\])/,/^(?:,)/,/^(?:\.)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:[^\s\*\/\-\+\^\%\>\=\<\!\|\&\?\:\(\)\[\]\,\.]+)/,/^(?:"(\\.|[^\"\\])*"|'(\\.|[^\'\\])*')/,/^(?:$)/,/^(?:.)/], +conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],"inclusive":true}} +}); +return lexer; +})(); +parser.lexer = lexer; +function Parser () { + this.yy = {}; +} +Parser.prototype = parser;parser.Parser = Parser; +return new Parser; +})(); + + +if (typeof require !== 'undefined' && typeof exports !== 'undefined') { +exports.parser = parser; +exports.Parser = parser.Parser; +exports.parse = function () { return parser.parse.apply(parser, arguments); }; +exports.main = function commonjsMain (args) { + if (!args[1]) { + console.log('Usage: '+args[0]+' FILE'); + process.exit(1); + } + var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8"); + return exports.parser.parse(source); +}; +if (0) { // Ignore 'require.main' + //exports.main(process.argv.slice(1)); +} +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/utils/Compile.d.ts b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/utils/Compile.d.ts new file mode 100644 index 000000000..175a6df4a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/utils/Compile.d.ts @@ -0,0 +1,7 @@ +import ExpressionParser from '../ExpressionParser'; + +export default Compile; + +declare var Compile: ( + expression: string +) => ExpressionParser.ExpressionCallbackType; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/utils/Complile.js b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/utils/Complile.js new file mode 100644 index 000000000..339e7d1b0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/expressionparser/utils/Complile.js @@ -0,0 +1,8 @@ +import ExpressionParser from '../ExpressionParser.js'; + +var parser = new ExpressionParser(); +var Compile = function(expression) { + return parser.compile(expression); +} + +export default Compile; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/BuildFuzzyModule.d.ts b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/BuildFuzzyModule.d.ts new file mode 100644 index 000000000..13d5ddce8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/BuildFuzzyModule.d.ts @@ -0,0 +1,21 @@ +import FuzzyModule from './FuzzyModule'; +export default BuildFuzzyModule; + +declare namespace BuildFuzzyModule { + type FuzzySetConfig = + [string, number, number, number, string] | + [string, number, number, number]; + + interface IConfig { + variables: string | string[] | { [varName: string]: FuzzySetConfig[] }, + rules: string | string[] + } +} + +declare function BuildFuzzyModule( + config: string +): FuzzyModule; + +declare function BuildFuzzyModule( + config: BuildFuzzyModule.IConfig +): FuzzyModule; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/BuildFuzzyModule.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/BuildFuzzyModule.js new file mode 100644 index 000000000..967bd6960 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/BuildFuzzyModule.js @@ -0,0 +1,36 @@ +import FuzzyModule from './FuzzyModule.js'; +import BuildFuzzyVariables from './variables/BuildFuzzyVariables.js'; +import GetAllFuzzySets from './variables/GetAllFuzzySets.js'; +import BuildFuzzyRules from './rules/BuildFuzzyRules.js'; +import IsInvalidLine from './utils/IsInvalidLine.js'; + +var BuildFuzzyModule = function (config) { + if (typeof (config) === 'string') { + var variables = []; + var rules = []; + var lines = config.split('\n'); + for (var i = 0, cnt = lines.length; i < cnt; i++) { + var line = lines[i]; + if (IsInvalidLine(line)) { + continue; + } + if (line.indexOf('=>') !== -1) { + rules.push(line); + } else { + variables.push(line); + } + } + config = { + variables: variables, + rules: rules + } + } + + var fuzzyModule = new FuzzyModule(); + BuildFuzzyVariables(fuzzyModule, config.variables); + BuildFuzzyRules(fuzzyModule, config.rules, GetAllFuzzySets(fuzzyModule)); + + return fuzzyModule; +} + +export default BuildFuzzyModule; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/FuzzyModule.d.ts b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/FuzzyModule.d.ts new file mode 100644 index 000000000..6c76cf852 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/FuzzyModule.d.ts @@ -0,0 +1,10 @@ +export default FuzzyModule; + +declare class FuzzyModule { + + fuzzify(name: string, value: number): this; + fuzzify(names: { [name: string]: number }): this; + + defuzzify(name: string, type?: string): this; + defuzzify(name?: string[], type?: string): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/FuzzyModule.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/FuzzyModule.js new file mode 100644 index 000000000..71b7b7c08 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/FuzzyModule.js @@ -0,0 +1,156 @@ +import GetVariableName from './utils/GetVariableName.js'; + +// https://github.com/Mugen87/yuka +/** +* @author {@link https://github.com/Mugen87|Mugen87} +*/ +class FuzzyModule { + constructor() { + this.rules = []; + this.flvs = {}; + + } + + addFLV(name, flv) { + this.flvs[name] = flv; + return this; + + } + + removeFLV(name) { + delete this.flvs[name]; + return this; + + } + + hasFLV(name) { + return this.flvs.hasOwnProperty(name); + } + + addRule(rule) { + this.rules.push(rule); + return this; + + } + + removeRule(rule) { + const rules = this.rules; + const index = rules.indexOf(rule); + rules.splice(index, 1); + return this; + + } + + fuzzify(name, value) { + if (typeof (name) === 'string') { + this._fuzzify(name, value); + + } else { + let names = name; + for (name in names) { + this._fuzzify(name, names[name]); + } + } + + this.dirty = true; + return this; + } + + _fuzzify(name, value) { + if (!this.hasFLV(name)) { + return; + } + + this.flvs[name].fuzzify(value); + } + + defuzzify(name, type = FuzzyModule.DEFUZ_TYPE.MAXAV) { + + this._evaluate(); + + let result; + if (typeof (name) === 'string') { + result = this._defuzzify(name, type); + + } else if (Array.isArray(name)) { + result = {}; + let names = name; + for (let i = 0, cnt = names.length; i < cnt; i++) { + name = names[i]; + result[name] = this._defuzzify(name, type); + } + } else { + // Get all variable names of consequence + let names = []; + let rules = this.rules; + for (let i = 0, cnt = rules.length; i < cnt; i++) { + let consequence = rules[i].consequence; + let name = GetVariableName(consequence.name); + + if (names.indexOf(name) === -1) { + names.push(name); + } + } + result = this.defuzzify(names, type); + + } + + return result; + } + + _defuzzify(name, type = FuzzyModule.DEFUZ_TYPE.MAXAV) { + if (!this.hasFLV(name)) { + return; + } + + const flv = this.flvs[name]; + + let value; + switch (type) { + case FuzzyModule.DEFUZ_TYPE.MAXAV: + value = flv.defuzzifyMaxAv(); + break; + + case FuzzyModule.DEFUZ_TYPE.CENTROID: + value = flv.defuzzifyCentroid(); + break; + + default: + value = flv.defuzzifyMaxAv(); // use MaxAv as fallback + } + + return value; + } + + _evaluate() { + if (!this.dirty) { + return; + } + + const rules = this.rules; + this._initConsequences(); + for (let i = 0, l = rules.length; i < l; i++) { + rules[i].evaluate(); + } + + this.dirty = false; + } + + _initConsequences() { + const rules = this.rules; + // initializes the consequences of all rules. + for (let i = 0, l = rules.length; i < l; i++) { + const rule = rules[i]; + rule.initConsequence(); + } + return this; + + } +} + +FuzzyModule.DEFUZ_TYPE = Object.freeze({ + MAXAV: 0, + CENTROID: 1 +}); + +export default FuzzyModule; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/BuildFuzzyRule.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/BuildFuzzyRule.js new file mode 100644 index 000000000..7aee3b49d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/BuildFuzzyRule.js @@ -0,0 +1,44 @@ +import FuzzyRule from './FuzzyRule.js'; +import FuzzyAND from './operators/FuzzyAND.js'; +import FuzzyOR from './operators/FuzzyOR.js'; +import FuzzyFAIRLY from './operators/FuzzyFAIRLY.js' +import FuzzyVERY from './operators/FuzzyVERY.js' +import Parse from '../utils/parser/Parse'; + +var BuildFuzzyRule = function (ruleInput, fuzzySets) { + var ruleJson = Parse(ruleInput); + var antecedent = BuildFuzzyCompositeTerm(ruleJson[1], fuzzySets); + var consequence = fuzzySets[ruleJson[2]]; + var rule = new FuzzyRule(antecedent, consequence); + return rule; +} + +var BuildFuzzyCompositeTerm = function (terms, fuzzySets) { + // terms: undefined, string, or array + if (!terms) { + return null; + } else if (typeof (terms) === 'string') { + if (!fuzzySets.hasOwnProperty(terms)) { + throw `Can't find fuzzy set ${terms}`; + } + return fuzzySets[terms]; + } + + // Array + var operations = []; + for (var i = 1, cnt = terms.length; i < cnt; i++) { + operations.push(BuildFuzzyCompositeTerm(terms[i], fuzzySets)); + } + var operatorClass = OperatorClasses[terms[0]]; + var operator = new operatorClass(...operations); + return operator; +} + +const OperatorClasses = { + and: FuzzyAND, + or: FuzzyOR, + fairly: FuzzyFAIRLY, + very: FuzzyVERY +} + +export default BuildFuzzyRule; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/BuildFuzzyRules.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/BuildFuzzyRules.js new file mode 100644 index 000000000..da0eff10d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/BuildFuzzyRules.js @@ -0,0 +1,17 @@ +import BuildFuzzyRule from './BuildFuzzyRule'; +import IsInvalidLine from '../utils/IsInvalidLine'; + +var BuildFuzzyRules = function (fuzzyModule, rules, fuzzySets) { + if (typeof (rules) === 'string') { + rules = rules.split('\n'); + } + for (var i = 0, cnt = rules.length; i < cnt; i++) { + var rule = rules[i]; + if (IsInvalidLine(rule)) { + continue; + } + fuzzyModule.addRule(BuildFuzzyRule(rule, fuzzySets)); + } +} + +export default BuildFuzzyRules; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/FuzzyRule.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/FuzzyRule.js new file mode 100644 index 000000000..b0e27964b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/FuzzyRule.js @@ -0,0 +1,23 @@ +/** +* @author {@link https://github.com/Mugen87|Mugen87} +*/ +class FuzzyRule { + constructor(antecedent, consequence) { + this.antecedent = antecedent; + this.consequence = consequence; + + } + + initConsequence() { + this.consequence.clearDegreeOfMembership(); + return this; + } + + evaluate() { + this.consequence.updateDegreeOfMembership(this.antecedent.getDegreeOfMembership()); + return this; + + } +} + +export default FuzzyRule; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyAND.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyAND.js new file mode 100644 index 000000000..f701c4967 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyAND.js @@ -0,0 +1,30 @@ +import FuzzyCompositeTerm from './FuzzyCompositeTerm.js'; + +/** +* @author {@link https://github.com/Mugen87|Mugen87} +* @augments FuzzyCompositeTerm +*/ +class FuzzyAND extends FuzzyCompositeTerm { + constructor() { + const terms = Array.from(arguments); + super(terms); + } + + getDegreeOfMembership() { + const terms = this.terms; + let minDOM = Infinity; + for (let i = 0, l = terms.length; i < l; i++) { + const currentDOM = terms[i].getDegreeOfMembership(); + if (currentDOM < minDOM) { + minDOM = currentDOM; + } + + } + + return minDOM; + + } + +} + +export default FuzzyAND; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyCompositeTerm.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyCompositeTerm.js new file mode 100644 index 000000000..82e41d505 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyCompositeTerm.js @@ -0,0 +1,34 @@ +/** +* @author {@link https://github.com/Mugen87|Mugen87} +* @augments FuzzyTerm +*/ +class FuzzyCompositeTerm { + + constructor(terms) { + this.terms = terms; + } + + clearDegreeOfMembership() { + const terms = this.terms; + for (let i = 0, l = terms.length; i < l; i++) { + terms[i].clearDegreeOfMembership(); + + } + + return this; + } + + updateDegreeOfMembership(value) { + const terms = this.terms; + for (let i = 0, l = terms.length; i < l; i++) { + terms[i].updateDegreeOfMembership(value); + + } + + return this; + + } + +} + +export default FuzzyCompositeTerm; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyFAIRLY.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyFAIRLY.js new file mode 100644 index 000000000..9f98e51ad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyFAIRLY.js @@ -0,0 +1,30 @@ +import FuzzyCompositeTerm from './FuzzyCompositeTerm.js'; + +/** +* @author {@link https://github.com/Mugen87|Mugen87} +* @augments FuzzyCompositeTerm +*/ +class FuzzyFAIRLY extends FuzzyCompositeTerm { + + constructor(fuzzyTerm) { + super([fuzzyTerm]); + } + + clearDegreeOfMembership() { + this.terms[0].clearDegreeOfMembership(); + return this; + } + + getDegreeOfMembership() { + const dom = this.terms[0].getDegreeOfMembership(); + return Math.sqrt(dom); + } + + updateDegreeOfMembership(value) { + this.terms[0].updateDegreeOfMembership(Math.sqrt(value)); + return this; + } + +} + +export default FuzzyFAIRLY; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyOR.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyOR.js new file mode 100644 index 000000000..08de759fb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyOR.js @@ -0,0 +1,29 @@ +import FuzzyCompositeTerm from './FuzzyCompositeTerm.js'; + +/** +* @author {@link https://github.com/Mugen87|Mugen87} +* @augments FuzzyCompositeTerm +*/ +class FuzzyOR extends FuzzyCompositeTerm { + constructor() { + const terms = Array.from(arguments); + super(terms); + } + + getDegreeOfMembership() { + const terms = this.terms; + let maxDOM = -Infinity; + for (let i = 0, l = terms.length; i < l; i++) { + const currentDOM = terms[i].getDegreeOfMembership(); + if (currentDOM > maxDOM) { + maxDOM = currentDOM; + } + + } + + return maxDOM; + } + +} + +export default FuzzyOR; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyVERY.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyVERY.js new file mode 100644 index 000000000..3fb8b5f20 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/rules/operators/FuzzyVERY.js @@ -0,0 +1,32 @@ +import FuzzyCompositeTerm from './FuzzyCompositeTerm.js'; + +/** +* @author {@link https://github.com/Mugen87|Mugen87} +* @augments FuzzyCompositeTerm +*/ +class FuzzyVERY extends FuzzyCompositeTerm { + + constructor(fuzzyTerm = null) { + super([fuzzyTerm]); + } + + clearDegreeOfMembership() { + this.terms[0].clearDegreeOfMembership(); + return this; + + } + + getDegreeOfMembership() { + const dom = this.terms[0].getDegreeOfMembership(); + return dom * dom; + + } + + updateDegreeOfMembership(value) { + this.terms[0].updateDegreeOfMembership(value * value); + return this; + } + +} + +export default FuzzyVERY; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/GetVariableName.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/GetVariableName.js new file mode 100644 index 000000000..ffb7a0114 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/GetVariableName.js @@ -0,0 +1,9 @@ +var GetVariableName = function (setName) { + if (setName.indexOf('.') !== -1) { + return setName.split('.')[0]; + } else { + return setName.replace(/[+-]*/g, '') + } +} + +export default GetVariableName; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/IsInvalidLine.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/IsInvalidLine.js new file mode 100644 index 000000000..d7d1ddfff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/IsInvalidLine.js @@ -0,0 +1,12 @@ +var IsInvalidLine = function (line) { + // Is empty line + if (line.length === 0 || !line.trim()) { + return true; + } + // Is comment line + if (line.trimStart().substring(0, 2) === '//') { + return true; + } +} + +export default IsInvalidLine; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/Parse.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/Parse.js new file mode 100644 index 000000000..9100cb553 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/Parse.js @@ -0,0 +1,8 @@ +import parser from './parser.js'; + +const Parser = new parser.Parser(); +var Parse = function (input) { + return Parser.parse(input); +} + +export default Parse; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/export-parser.bat b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/export-parser.bat new file mode 100644 index 000000000..5ae2cb025 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/export-parser.bat @@ -0,0 +1 @@ +node export.js \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/export.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/export.js new file mode 100644 index 000000000..96d2d960a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/export.js @@ -0,0 +1,26 @@ +var fs = require('fs'); +var jison = require("jison"); + +console.log("In progress..."); + +var parser = new jison.Parser(fs.readFileSync("grammar.jison", "utf8")); + +// generate source, ready to be written to disk +var parserSource = parser.generate(); +// console.log('Source: ', parserSource) + +var replaceSource = `if (typeof module !== 'undefined' && require.main === module) {` +var replacedBy = `if (0) { // Ignore 'require.main'` +parserSource = parserSource.replace(replaceSource, replacedBy); + +var replaceSource = `exports.main(process.argv.slice(1));` +var replacedBy = `//exports.main(process.argv.slice(1));` +parserSource = parserSource.replace(replaceSource, replacedBy); + +try { + fs.writeFileSync("./parser.js", parserSource) + //file written successfully + console.log("Ok. The file parser was saved!"); +} catch (err) { + console.error(err) +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/grammar.jison b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/grammar.jison new file mode 100644 index 000000000..ffd3d5c2d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/grammar.jison @@ -0,0 +1,154 @@ +/* description: Parses end executes mathematical expressions. */ + +/* lexical grammar */ +%lex +%% + +\s+ /* skip whitespace */ +// Vairable +":" return ":" +"," return "," +// Rule +"=>" return '=>' +"or" return 'OR' +"OR" return 'OR' +"and" return 'AND' +"AND" return 'AND' +"very" return 'VERY' +"VERY" return 'VERY' +"fairly" return 'FAIRLY' +"FAIRLY" return 'FAIRLY' +"(" return '(' +")" return ')' +// Vairable +[0-9]+("."[0-9]+)?\b return 'NUMBER' +// Common +[0-9a-zA-Z_.]+[+-]* return 'NAME' +<> return 'EOF' +. return 'INVALID' + +/lex + +%{ + function GetOperator1(operator, op1) { + operator = operator.toLowerCase(); + return [operator, op1]; + } + + function GetOperator2(operator, op1, op2) { + operator = operator.toLowerCase(); + var result = [operator]; + if (Array.isArray(op1) && (op1[0] === operator)) { + for(var i=1, cnt=op1.length; i' +%left 'VERY' 'FAIRLY' +%left 'AND' +%left 'OR' +%start expressions + +%% /* language grammar */ + +expressions + : varExp EOF + {return $1;} + | ruleExp EOF + {return $1;} + ; + +varExp + : NAME ':' NUMBER ',' NUMBER ',' NUMBER ',' NUMBER ',' NAME + { + var setName = $1, setType = $11; + var left = Number($3), middle = Number($5), right = Number($7), + arg0 = Number($9); + $$ = { + name: setName, type: setType, + parameters: [left, middle, right, arg0], + } + } + | NAME ':' NUMBER ',' NUMBER ',' NUMBER ',' NAME + { + var setName = $1, setType = $9; + var left = Number($3), middle = Number($5), right = Number($7); + $$ = { + name: setName, type: setType, + parameters: [left, middle, right], + } + } + | NAME ':' NUMBER ',' NUMBER ',' NUMBER + { + var setName = $1, setType = undefined; + var left = Number($3), middle = Number($5), right = Number($7); + $$ = { + name: setName, type: setType, + parameters: [left, middle, right], + } + } + | NAME ':' NUMBER ',' NUMBER + { + var setName = $1, setType = undefined; + var left = Number($3), right = Number($5), middle = (left+right)/2; + $$ = { + name: setName, type: setType, + parameters: [left, middle, right], + } + } + | NAME ':' NUMBER ',' NUMBER ',' NAME + { + var setName = $1, setType = $7; + var left = Number($3), right = Number($5), middle = (left+right)/2; + $$ = { + name: setName, type: setType, + parameters: [left, middle, right], + } + } + ; + +ruleExp + : VERY ruleExp + { + $$ = GetOperator1($1, $2) + } + | FAIRLY ruleExp + { + $$ = GetOperator1($1, $2) + } + | ruleExp AND ruleExp + { + $$ = GetOperator2($2, $1, $3); + } + | ruleExp OR ruleExp + { + $$ = GetOperator2($2, $1, $3); + } + | '(' ruleExp ')' + { + $$ = $2 + } + | ruleExp '=>' NAME + { + $$ = ['=>', $1, $3]; + } + | NAME + { + $$ = $1; + } + ; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/parser.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/parser.js new file mode 100644 index 000000000..c79ea4b51 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/utils/parser/parser.js @@ -0,0 +1,735 @@ +/* parser generated by jison 0.4.18 */ +/* + Returns a Parser object of the following structure: + + Parser: { + yy: {} + } + + Parser.prototype: { + yy: {}, + trace: function(), + symbols_: {associative list: name ==> number}, + terminals_: {associative list: number ==> name}, + productions_: [...], + performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), + table: [...], + defaultActions: {...}, + parseError: function(str, hash), + parse: function(input), + + lexer: { + EOF: 1, + parseError: function(str, hash), + setInput: function(input), + input: function(), + unput: function(str), + more: function(), + less: function(n), + pastInput: function(), + upcomingInput: function(), + showPosition: function(), + test_match: function(regex_match_array, rule_index), + next: function(), + lex: function(), + begin: function(condition), + popState: function(), + _currentRules: function(), + topState: function(), + pushState: function(condition), + + options: { + ranges: boolean (optional: true ==> token location info will include a .range[] member) + flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) + backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) + }, + + performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), + rules: [...], + conditions: {associative list: name ==> set}, + } + } + + + token location info (@$, _$, etc.): { + first_line: n, + last_line: n, + first_column: n, + last_column: n, + range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) + } + + + the parseError function receives a 'hash' object with these members for lexer and parser errors: { + text: (matched text) + token: (the produced terminal token, if any) + line: (yylineno) + } + while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { + loc: (yylloc) + expected: (string describing the set of expected tokens) + recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) + } +*/ +var parser = (function(){ +var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,5],$V1=[1,6],$V2=[1,7],$V3=[1,10],$V4=[1,11],$V5=[1,12],$V6=[2,14],$V7=[1,15],$V8=[5,16,17],$V9=[5,13,14,16,17]; +var parser = {trace: function trace () { }, +yy: {}, +symbols_: {"error":2,"expressions":3,"varExp":4,"EOF":5,"ruleExp":6,"NAME":7,":":8,"NUMBER":9,",":10,"VERY":11,"FAIRLY":12,"AND":13,"OR":14,"(":15,")":16,"=>":17,"$accept":0,"$end":1}, +terminals_: {2:"error",5:"EOF",7:"NAME",8:":",9:"NUMBER",10:",",11:"VERY",12:"FAIRLY",13:"AND",14:"OR",15:"(",16:")",17:"=>"}, +productions_: [0,[3,2],[3,2],[4,11],[4,9],[4,7],[4,5],[4,7],[6,2],[6,2],[6,3],[6,3],[6,3],[6,3],[6,1]], +performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) { +/* this == yyval */ + +var $0 = $$.length - 1; +switch (yystate) { +case 1: case 2: +return $$[$0-1]; +break; +case 3: + + var setName = $$[$0-10], setType = $$[$0]; + var left = Number($$[$0-8]), middle = Number($$[$0-6]), right = Number($$[$0-4]), + arg0 = Number($$[$0-2]); + this.$ = { + name: setName, type: setType, + parameters: [left, middle, right, arg0], + } + +break; +case 4: + + var setName = $$[$0-8], setType = $$[$0]; + var left = Number($$[$0-6]), middle = Number($$[$0-4]), right = Number($$[$0-2]); + this.$ = { + name: setName, type: setType, + parameters: [left, middle, right], + } + +break; +case 5: + + var setName = $$[$0-6], setType = undefined; + var left = Number($$[$0-4]), middle = Number($$[$0-2]), right = Number($$[$0]); + this.$ = { + name: setName, type: setType, + parameters: [left, middle, right], + } + +break; +case 6: + + var setName = $$[$0-4], setType = undefined; + var left = Number($$[$0-2]), right = Number($$[$0]), middle = (left+right)/2; + this.$ = { + name: setName, type: setType, + parameters: [left, middle, right], + } + +break; +case 7: + + var setName = $$[$0-6], setType = $$[$0]; + var left = Number($$[$0-4]), right = Number($$[$0-2]), middle = (left+right)/2; + this.$ = { + name: setName, type: setType, + parameters: [left, middle, right], + } + +break; +case 8: case 9: + + this.$ = GetOperator1($$[$0-1], $$[$0]) + +break; +case 10: case 11: + + this.$ = GetOperator2($$[$0-1], $$[$0-2], $$[$0]); + +break; +case 12: + + this.$ = $$[$0-1] + +break; +case 13: + + this.$ = ['=>', $$[$0-2], $$[$0]]; + +break; +case 14: + + this.$ = $$[$0]; + +break; +} +}, +table: [{3:1,4:2,6:3,7:[1,4],11:$V0,12:$V1,15:$V2},{1:[3]},{5:[1,8]},{5:[1,9],13:$V3,14:$V4,17:$V5},o([5,13,14,17],$V6,{8:[1,13]}),{6:14,7:$V7,11:$V0,12:$V1,15:$V2},{6:16,7:$V7,11:$V0,12:$V1,15:$V2},{6:17,7:$V7,11:$V0,12:$V1,15:$V2},{1:[2,1]},{1:[2,2]},{6:18,7:$V7,11:$V0,12:$V1,15:$V2},{6:19,7:$V7,11:$V0,12:$V1,15:$V2},{7:[1,20]},{9:[1,21]},o($V8,[2,8],{13:$V3,14:$V4}),o($V9,$V6),o($V8,[2,9],{13:$V3,14:$V4}),{13:$V3,14:$V4,16:[1,22],17:$V5},o([5,13,16,17],[2,10],{14:$V4}),o($V9,[2,11]),o($V9,[2,13]),{10:[1,23]},o($V9,[2,12]),{9:[1,24]},{5:[2,6],10:[1,25]},{7:[1,27],9:[1,26]},{5:[2,5],10:[1,28]},{5:[2,7]},{7:[1,30],9:[1,29]},{10:[1,31]},{5:[2,4]},{7:[1,32]},{5:[2,3]}], +defaultActions: {8:[2,1],9:[2,2],27:[2,7],30:[2,4],32:[2,3]}, +parseError: function parseError (str, hash) { + if (hash.recoverable) { + this.trace(str); + } else { + var error = new Error(str); + error.hash = hash; + throw error; + } +}, +parse: function parse(input) { + var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; + var args = lstack.slice.call(arguments, 1); + var lexer = Object.create(this.lexer); + var sharedState = { yy: {} }; + for (var k in this.yy) { + if (Object.prototype.hasOwnProperty.call(this.yy, k)) { + sharedState.yy[k] = this.yy[k]; + } + } + lexer.setInput(input, sharedState.yy); + sharedState.yy.lexer = lexer; + sharedState.yy.parser = this; + if (typeof lexer.yylloc == 'undefined') { + lexer.yylloc = {}; + } + var yyloc = lexer.yylloc; + lstack.push(yyloc); + var ranges = lexer.options && lexer.options.ranges; + if (typeof sharedState.yy.parseError === 'function') { + this.parseError = sharedState.yy.parseError; + } else { + this.parseError = Object.getPrototypeOf(this).parseError; + } + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + _token_stack: + var lex = function () { + var token; + token = lexer.lex() || EOF; + if (typeof token !== 'number') { + token = self.symbols_[token] || token; + } + return token; + }; + var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == 'undefined') { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === 'undefined' || !action.length || !action[0]) { + var errStr = ''; + expected = []; + for (p in table[state]) { + if (this.terminals_[p] && p > TERROR) { + expected.push('\'' + this.terminals_[p] + '\''); + } + } + if (lexer.showPosition) { + errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\''; + } else { + errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\''); + } + this.parseError(errStr, { + text: lexer.match, + token: this.terminals_[symbol] || symbol, + line: lexer.yylineno, + loc: yyloc, + expected: expected + }); + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(lexer.yytext); + lstack.push(lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = lexer.yyleng; + yytext = lexer.yytext; + yylineno = lexer.yylineno; + yyloc = lexer.yylloc; + if (recovering > 0) { + recovering--; + } + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = { + first_line: lstack[lstack.length - (len || 1)].first_line, + last_line: lstack[lstack.length - 1].last_line, + first_column: lstack[lstack.length - (len || 1)].first_column, + last_column: lstack[lstack.length - 1].last_column + }; + if (ranges) { + yyval._$.range = [ + lstack[lstack.length - (len || 1)].range[0], + lstack[lstack.length - 1].range[1] + ]; + } + r = this.performAction.apply(yyval, [ + yytext, + yyleng, + yylineno, + sharedState.yy, + action[1], + vstack, + lstack + ].concat(args)); + if (typeof r !== 'undefined') { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; +}}; + + function GetOperator1(operator, op1) { + operator = operator.toLowerCase(); + return [operator, op1]; + } + + function GetOperator2(operator, op1, op2) { + operator = operator.toLowerCase(); + var result = [operator]; + if (Array.isArray(op1) && (op1[0] === operator)) { + for(var i=1, cnt=op1.length; i 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, + +// displays upcoming input, i.e. for error messages +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); + }, + +// displays the character position where the lexing error occurred, i.e. for error messages +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c + "^"; + }, + +// test the lexed token: return FALSE when not a match, otherwise return token +test_match:function(match, indexed_rule) { + var token, + lines, + backup; + + if (this.options.backtrack_lexer) { + // save context + backup = { + yylineno: this.yylineno, + yylloc: { + first_line: this.yylloc.first_line, + last_line: this.last_line, + first_column: this.yylloc.first_column, + last_column: this.yylloc.last_column + }, + yytext: this.yytext, + match: this.match, + matches: this.matches, + matched: this.matched, + yyleng: this.yyleng, + offset: this.offset, + _more: this._more, + _input: this._input, + yy: this.yy, + conditionStack: this.conditionStack.slice(0), + done: this.done + }; + if (this.options.ranges) { + backup.yylloc.range = this.yylloc.range.slice(0); + } + } + + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno += lines.length; + } + this.yylloc = { + first_line: this.yylloc.last_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.last_column, + last_column: lines ? + lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : + this.yylloc.last_column + match[0].length + }; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._backtrack = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]); + if (this.done && this._input) { + this.done = false; + } + if (token) { + return token; + } else if (this._backtrack) { + // recover context + for (var k in backup) { + this[k] = backup[k]; + } + return false; // rule action called reject() implying the next rule should be tested instead. + } + return false; + }, + +// return next match in input +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) { + this.done = true; + } + + var token, + match, + tempMatch, + index; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i = 0; i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (this.options.backtrack_lexer) { + token = this.test_match(tempMatch, rules[i]); + if (token !== false) { + return token; + } else if (this._backtrack) { + match = false; + continue; // rule action called reject() implying a rule MISmatch. + } else { + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return false; + } + } else if (!this.options.flex) { + break; + } + } + } + if (match) { + token = this.test_match(match, rules[index]); + if (token !== false) { + return token; + } + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return false; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { + text: "", + token: null, + line: this.yylineno + }); + } + }, + +// return next match that has a token +lex:function lex () { + var r = this.next(); + if (r) { + return r; + } else { + return this.lex(); + } + }, + +// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack) +begin:function begin (condition) { + this.conditionStack.push(condition); + }, + +// pop the previously active lexer condition state off the condition stack +popState:function popState () { + var n = this.conditionStack.length - 1; + if (n > 0) { + return this.conditionStack.pop(); + } else { + return this.conditionStack[0]; + } + }, + +// produce the lexer rule set which is active for the currently active lexer condition state +_currentRules:function _currentRules () { + if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) { + return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; + } else { + return this.conditions["INITIAL"].rules; + } + }, + +// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available +topState:function topState (n) { + n = this.conditionStack.length - 1 - Math.abs(n || 0); + if (n >= 0) { + return this.conditionStack[n]; + } else { + return "INITIAL"; + } + }, + +// alias for begin(condition) +pushState:function pushState (condition) { + this.begin(condition); + }, + +// return the number of states currently on the stack +stateStackSize:function stateStackSize() { + return this.conditionStack.length; + }, +options: {}, +performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { +var YYSTATE=YY_START; +switch($avoiding_name_collisions) { +case 0:/* skip whitespace */ +break; +case 1:return ":" +break; +case 2:return "," +break; +case 3:return 17 +break; +case 4:return 14 +break; +case 5:return 14 +break; +case 6:return 13 +break; +case 7:return 13 +break; +case 8:return 11 +break; +case 9:return 11 +break; +case 10:return 12 +break; +case 11:return 12 +break; +case 12:return 15 +break; +case 13:return 16 +break; +case 14:return 9 +break; +case 15:return 7 +break; +case 16:return 5 +break; +case 17:return 'INVALID' +break; +} +}, +rules: [/^(?:\s+)/,/^(?::)/,/^(?:,)/,/^(?:=>)/,/^(?:or\b)/,/^(?:OR\b)/,/^(?:and\b)/,/^(?:AND\b)/,/^(?:very\b)/,/^(?:VERY\b)/,/^(?:fairly\b)/,/^(?:FAIRLY\b)/,/^(?:\()/,/^(?:\))/,/^(?:[0-9]+(\.[0-9]+)?\b)/,/^(?:[0-9a-zA-Z_.]+[+-]*)/,/^(?:$)/,/^(?:.)/], +conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}} +}); +return lexer; +})(); +parser.lexer = lexer; +function Parser () { + this.yy = {}; +} +Parser.prototype = parser;parser.Parser = Parser; +return new Parser; +})(); + + +if (typeof require !== 'undefined' && typeof exports !== 'undefined') { +exports.parser = parser; +exports.Parser = parser.Parser; +exports.parse = function () { return parser.parse.apply(parser, arguments); }; +exports.main = function commonjsMain (args) { + if (!args[1]) { + console.log('Usage: '+args[0]+' FILE'); + process.exit(1); + } + var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8"); + return exports.parser.parse(source); +}; +if (0) { // Ignore 'require.main' + //exports.main(process.argv.slice(1)); +} +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/BuildFuzzySet.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/BuildFuzzySet.js new file mode 100644 index 000000000..c8fa008bf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/BuildFuzzySet.js @@ -0,0 +1,33 @@ +import LeftShoulderFuzzySet from './sets/LeftShoulderFuzzySet.js'; +import LeftSCurveFuzzySet from './sets/LeftSCurveFuzzySet.js'; +import RightShoulderFuzzySet from './sets/RightShoulderFuzzySet.js'; +import RightSCurveFuzzySet from './sets/RightSCurveFuzzySet.js'; +import TriangularFuzzySet from './sets/TriangularFuzzySet.js'; +import SingletonFuzzySet from './sets/SingletonFuzzySet.js'; +import NormalDistFuzzySet from './sets/NormalDistFuzzySet.js'; + +const FuzzySetClasses = { + leftShoulder: LeftShoulderFuzzySet, + leftSCurve: LeftSCurveFuzzySet, + + rightShoulder: RightShoulderFuzzySet, + rightSCurve: RightSCurveFuzzySet, + + triangular: TriangularFuzzySet, + singleton: SingletonFuzzySet, + normal: NormalDistFuzzySet +} + +var BuildFuzzySet = function (config, partType) { + var setType = config.type; + if (setType === undefined) { + setType = (partType === 0) ? 'leftShoulder' : // Left part + (partType === 2) ? 'rightShoulder' : // Right part + 'triangular'; // Middle part + } + + var fuzzySet = new FuzzySetClasses[setType](...config.parameters); + return fuzzySet; +} + +export default BuildFuzzySet; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/BuildFuzzyVariable.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/BuildFuzzyVariable.js new file mode 100644 index 000000000..54b646be2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/BuildFuzzyVariable.js @@ -0,0 +1,18 @@ +import FuzzyVariable from './FuzzyVariable.js'; +import BuildFuzzySet from './BuildFuzzySet'; + +var BuildFuzzyVariable = function (setsConfig) { + var flv = new FuzzyVariable(); + for (var i = 0, cnt = setsConfig.length; i < cnt; i++) { + var flvConfig = setsConfig[i]; // [setName, setType, left, middle, right, arg0] + var fuzzySet = BuildFuzzySet( + flvConfig, + (i === 0) ? 0 : ((i == cnt - 1) ? 2 : 3) + ); + fuzzySet.name = flvConfig.name; + flv.add(fuzzySet); + } + return flv; +} + +export default BuildFuzzyVariable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/BuildFuzzyVariables.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/BuildFuzzyVariables.js new file mode 100644 index 000000000..d40b8476f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/BuildFuzzyVariables.js @@ -0,0 +1,52 @@ +import BuildFuzzyVariable from './BuildFuzzyVariable.js'; +import Parse from '../utils/parser/Parse.js'; +import IsInvalidLine from '../utils/IsInvalidLine.js'; +import GetVariableName from '../utils/GetVariableName.js' + +var BuildFuzzyVariables = function (fuzzyModule, variables) { + // String -> FuzzySets array + if (typeof (variables) === 'string') { + variables = variables.split('\n'); + } + + // FuzzySets array -> Variables dictionary + if (Array.isArray(variables)) { // Fuzzy sets in array + var lines = variables; + variables = []; + for (var i = 0, cnt = lines.length; i < cnt; i++) { + var line = lines[i]; + if (typeof (line) !== 'string') { + variables.push(line); + continue; + } + + // Fuzzy set might be string + if (IsInvalidLine(line)) { + continue; + } + variables.push(Parse(line)); + } + // Bind fuzzy set to variables + variables = BindFuzzySets(variables); + } + + for (var name in variables) { + var flv = BuildFuzzyVariable(variables[name]); + fuzzyModule.addFLV(name, flv); + } +} + +var BindFuzzySets = function (fuzzySets) { + var variables = {}; + for (var i = 0, cnt = fuzzySets.length; i < cnt; i++) { + var fuzzySet = fuzzySets[i]; + var variableName = GetVariableName(fuzzySet.name); + if (!variables.hasOwnProperty(variableName)) { + variables[variableName] = []; + } + variables[variableName].push(fuzzySet); + } + return variables; +} + +export default BuildFuzzyVariables; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/FuzzyVariable.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/FuzzyVariable.js new file mode 100644 index 000000000..8156c2f91 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/FuzzyVariable.js @@ -0,0 +1,116 @@ +/** +* @author {@link https://github.com/Mugen87|Mugen87} +*/ +class FuzzyVariable { + constructor() { + + this.fuzzySets = []; + + this.minRange = Infinity; + this.maxRange = - Infinity; + + } + + add(fuzzySet) { + this.fuzzySets.push(fuzzySet); + + // adjust range + if (fuzzySet.left < this.minRange) { + this.minRange = fuzzySet.left + }; + if (fuzzySet.right > this.maxRange) { + this.maxRange = fuzzySet.right; + } + + return this; + } + + remove(fuzzySet) { + + const fuzzySets = this.fuzzySets; + + const index = fuzzySets.indexOf(fuzzySet); + fuzzySets.splice(index, 1); + + // iterate over all fuzzy sets to recalculate the min/max range + this.minRange = Infinity; + this.maxRange = - Infinity; + + for (let i = 0, l = fuzzySets.length; i < l; i++) { + + const fuzzySet = fuzzySets[i]; + + if (fuzzySet.left < this.minRange) { + this.minRange = fuzzySet.left; + } + if (fuzzySet.right > this.maxRange) { + this.maxRange = fuzzySet.right; + } + + } + + return this; + } + + fuzzify(value) { + + if (value < this.minRange || value > this.maxRange) { + // Logger.warn('YUKA.FuzzyVariable: Value for fuzzification out of range.'); + return; + } + + const fuzzySets = this.fuzzySets; + for (let i = 0, l = fuzzySets.length; i < l; i++) { + const fuzzySet = fuzzySets[i]; + fuzzySet.degreeOfMembership = fuzzySet.computeDegreeOfMembership(value); + + } + + return this; + } + + + defuzzifyMaxAv() { + + // the average of maxima (MaxAv for short) defuzzification method scales the + // representative value of each fuzzy set by its DOM and takes the average + const fuzzySets = this.fuzzySets; + + let bottom = 0; + let top = 0; + + for (let i = 0, l = fuzzySets.length; i < l; i++) { + const fuzzySet = fuzzySets[i]; + bottom += fuzzySet.degreeOfMembership; + top += fuzzySet.representativeValue * fuzzySet.degreeOfMembership; + } + + return (bottom === 0) ? 0 : (top / bottom); + } + + defuzzifyCentroid(samples = 10) { + + const fuzzySets = this.fuzzySets; + const stepSize = (this.maxRange - this.minRange) / samples; + + let totalArea = 0; + let sumOfMoments = 0; + + for (let s = 1; s <= samples; s++) { + const sample = this.minRange + (s * stepSize); + + for (let i = 0, l = fuzzySets.length; i < l; i++) { + const fuzzySet = fuzzySets[i]; + const contribution = Math.min(fuzzySet.degreeOfMembership, fuzzySet.computeDegreeOfMembership(sample)); + totalArea += contribution; + sumOfMoments += (sample * contribution); + } + + } + + return (totalArea === 0) ? 0 : (sumOfMoments / totalArea); + } + +} + +export default FuzzyVariable; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/GetAllFuzzySets.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/GetAllFuzzySets.js new file mode 100644 index 000000000..0a24438f4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/GetAllFuzzySets.js @@ -0,0 +1,14 @@ +var GetAllFuzzySets = function (fuzzyModule) { + var allFuzzySets = {}; + var flvs = fuzzyModule.flvs; + for (var name in flvs) { + var FLVFuzzySets = flvs[name].fuzzySets; + for (var i = 0, cnt = FLVFuzzySets.length; i < cnt; i++) { + var fuzzySet = FLVFuzzySets[i]; + allFuzzySets[fuzzySet.name] = fuzzySet; + } + } + return allFuzzySets; +} + +export default GetAllFuzzySets; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/FuzzySet.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/FuzzySet.js new file mode 100644 index 000000000..43df98d3e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/FuzzySet.js @@ -0,0 +1,43 @@ +/** +* @author {@link https://github.com/Mugen87|Mugen87} +* @augments FuzzyTerm +*/ + +class FuzzySet { + + constructor(representativeValue = 0) { + this.degreeOfMembership = 0; + this.representativeValue = representativeValue; + + this.left = 0; + this.right = 0; + + } + + computeDegreeOfMembership() { } + + clearDegreeOfMembership() { + + this.degreeOfMembership = 0; + + return this; + + } + + getDegreeOfMembership() { + return this.degreeOfMembership; + + } + + updateDegreeOfMembership(value) { + if (value > this.degreeOfMembership) { + this.degreeOfMembership = value; + } + + return this; + + } + +} + +export default FuzzySet; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/LeftSCurveFuzzySet.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/LeftSCurveFuzzySet.js new file mode 100644 index 000000000..544df8eca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/LeftSCurveFuzzySet.js @@ -0,0 +1,52 @@ +import FuzzySet from './FuzzySet.js'; + +/** +* @author {@link https://github.com/robp94|robp94} +* @augments FuzzySet +*/ +class LeftSCurveFuzzySet extends FuzzySet { + constructor(left, midpoint, right) { + // the representative value is the midpoint of the plateau of the shoulder + const representativeValue = (midpoint + left) / 2; + super(representativeValue); + + this.left = left; + this.midpoint = midpoint; + this.right = right; + + } + + computeDegreeOfMembership(value) { + + const midpoint = this.midpoint; + const left = this.left; + const right = this.right; + + // find DOM if the given value is left of the center or equal to the center + + if ((value >= left) && (value <= midpoint)) { + + return 1; + + } + + // find DOM if the given value is right of the midpoint + + if ((value > midpoint) && (value <= right)) { + if (value >= ((midpoint + right) / 2)) { + return 2 * (Math.pow((value - right) / (midpoint - right), 2)); + + } else { //todo test + return 1 - (2 * (Math.pow((value - midpoint) / (midpoint - right), 2))); + + } + + } + + // out of range + return 0; + + } +} + +export default LeftSCurveFuzzySet; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/LeftShoulderFuzzySet.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/LeftShoulderFuzzySet.js new file mode 100644 index 000000000..257815c22 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/LeftShoulderFuzzySet.js @@ -0,0 +1,43 @@ +import FuzzySet from './FuzzySet.js'; + +/** +* @author {@link https://github.com/Mugen87|Mugen87} +* @augments FuzzySet +*/ +class LeftShoulderFuzzySet extends FuzzySet { + constructor(left, midpoint, right) { + // the representative value is the midpoint of the plateau of the shoulder + const representativeValue = (midpoint + left) / 2; + super(representativeValue); + + this.left = left; + this.midpoint = midpoint; + this.right = right; + + } + + computeDegreeOfMembership(value) { + const midpoint = this.midpoint; + const left = this.left; + const right = this.right; + + // find DOM if the given value is left of the center or equal to the center + if ((value >= left) && (value <= midpoint)) { + return 1; + + } + + // find DOM if the given value is right of the midpoint + if ((value > midpoint) && (value <= right)) { + const grad = 1 / (right - midpoint); + return grad * (right - value); + + } + + // out of range + return 0; + + } +} + +export default LeftShoulderFuzzySet; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/NormalDistFuzzySet.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/NormalDistFuzzySet.js new file mode 100644 index 000000000..9b7ddf83b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/NormalDistFuzzySet.js @@ -0,0 +1,64 @@ +import FuzzySet from './FuzzySet.js'; + +/** +* @author {@link https://github.com/robp94|robp94} +* @augments FuzzySet +*/ +class NormalDistFuzzySet extends FuzzySet { + + constructor(left, midpoint, right, standardDeviation) { + super(midpoint); + + this.left = left; + this.midpoint = midpoint; + this.right = right; + this.standardDeviation = standardDeviation; + this._cache = {}; + + } + + computeDegreeOfMembership(value) { + + this._updateCache(); + + if (value >= this.right || value <= this.left) { + return 0; + } + + return ProbabilityDensity(value, this.midpoint, this._cache.variance) / this._cache.normalizationFactor; + + } + + _updateCache() { + + const cache = this._cache; + const midpoint = this.midpoint; + const standardDeviation = this.standardDeviation; + + if (midpoint !== cache.midpoint || standardDeviation !== cache.standardDeviation) { + + const variance = standardDeviation * standardDeviation; + + cache.midpoint = midpoint; + cache.standardDeviation = standardDeviation; + cache.variance = variance; + + // this value is used to ensure the DOM lies in the range of [0,1] + + cache.normalizationFactor = ProbabilityDensity(midpoint, midpoint, variance); + + } + + return this; + + } + +} + +var ProbabilityDensity = function (x, mean, variance) { + + return (1 / Math.sqrt(2 * Math.PI * variance)) * Math.exp(- (Math.pow((x - mean), 2)) / (2 * variance)); + +} + +export default NormalDistFuzzySet; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/RightSCurveFuzzySet.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/RightSCurveFuzzySet.js new file mode 100644 index 000000000..f463814a1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/RightSCurveFuzzySet.js @@ -0,0 +1,50 @@ +import FuzzySet from './FuzzySet.js'; + +/** +* @author {@link https://github.com/robp94|robp94} +* @augments FuzzySet +*/ +class RightSCurveFuzzySet extends FuzzySet { + constructor(left, midpoint, right) { + + // the representative value is the midpoint of the plateau of the shoulder + const representativeValue = (midpoint + right) / 2; + super(representativeValue); + + this.left = left; + this.midpoint = midpoint; + this.right = right; + + } + + computeDegreeOfMembership(value) { + + const midpoint = this.midpoint; + const left = this.left; + const right = this.right; + + // find DOM if the given value is left of the center or equal to the center + + if ((value >= left) && (value <= midpoint)) { + if (value <= ((left + midpoint) / 2)) { + return 2 * (Math.pow((value - left) / (midpoint - left), 2)); + + } else { + return 1 - (2 * (Math.pow((value - midpoint) / (midpoint - left), 2))); + + } + } + + // find DOM if the given value is right of the midpoint + if ((value > midpoint) && (value <= right)) { + return 1; + + } + + // out of range + return 0; + + } +} + +export default RightSCurveFuzzySet; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/RightShoulderFuzzySet.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/RightShoulderFuzzySet.js new file mode 100644 index 000000000..9ccdc5f9a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/RightShoulderFuzzySet.js @@ -0,0 +1,43 @@ +import FuzzySet from './FuzzySet.js'; + +/** +* @author {@link https://github.com/Mugen87|Mugen87} +* @augments FuzzySet +*/ +class RightShoulderFuzzySet extends FuzzySet { + constructor(left, midpoint, right) { + // the representative value is the midpoint of the plateau of the shoulder + const representativeValue = (midpoint + right) / 2; + super(representativeValue); + + this.left = left; + this.midpoint = midpoint; + this.right = right; + + } + + computeDegreeOfMembership(value) { + const midpoint = this.midpoint; + const left = this.left; + const right = this.right; + + // find DOM if the given value is left of the center or equal to the center + if ((value >= left) && (value <= midpoint)) { + const grad = 1 / (midpoint - left); + return grad * (value - left); + + } + + // find DOM if the given value is right of the midpoint + if ((value > midpoint) && (value <= right)) { + return 1; + + } + + // out of range + return 0; + + } +} + +export default RightShoulderFuzzySet; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/SingletonFuzzySet.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/SingletonFuzzySet.js new file mode 100644 index 000000000..5ecca5d77 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/SingletonFuzzySet.js @@ -0,0 +1,26 @@ +import FuzzySet from './FuzzySet.js'; + +/** +* @author {@link https://github.com/Mugen87|Mugen87} +* @augments FuzzySet +*/ +class SingletonFuzzySet extends FuzzySet { + constructor(left, midpoint, right) { + + super(midpoint); + + this.left = left; + this.midpoint = midpoint; + this.right = right; + + } + + computeDegreeOfMembership(value) { + const left = this.left; + const right = this.right; + return (value >= left && value <= right) ? 1 : 0; + + } +} + +export default SingletonFuzzySet; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/TriangularFuzzySet.js b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/TriangularFuzzySet.js new file mode 100644 index 000000000..4e3322823 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/fuzzy/variables/sets/TriangularFuzzySet.js @@ -0,0 +1,45 @@ +import FuzzySet from './FuzzySet.js'; + +/** +* @author {@link https://github.com/Mugen87|Mugen87} +* @augments FuzzySet +*/ +class TriangularFuzzySet extends FuzzySet { + constructor(left, midpoint, right) { + + super(midpoint); + + this.left = left; + this.midpoint = midpoint; + this.right = right; + + } + + computeDegreeOfMembership(value) { + + const midpoint = this.midpoint; + const left = this.left; + const right = this.right; + + // find DOM if the given value is left of the center or equal to the center + + if ((value >= left) && (value <= midpoint)) { + const grad = 1 / (midpoint - left); + return grad * (value - left); + + } + + // find DOM if the given value is right of the center + if ((value > midpoint) && (value <= right)) { + const grad = 1 / (right - midpoint); + return grad * (right - value); + + } + + // out of range + return 0; + + } +} + +export default TriangularFuzzySet; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/gashapon/Gashapon.d.ts b/ui/src/phaser3-rex-plugins/plugins/math/gashapon/Gashapon.d.ts new file mode 100644 index 000000000..08c6c9e7e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/gashapon/Gashapon.d.ts @@ -0,0 +1,87 @@ +export default Gashapon; + +declare namespace Gashapon { + + type ModeType = 0 | 1 | 'shuffle' | 'random'; + + type ItemsType = { [name: string]: number }; + + type ForEachItemCallbackType = (name: string, count: number) => void; + + interface IConfig { + mode?: ModeType, + items?: ItemsType, + reload?: boolean, + rnd?: Phaser.Math.RandomDataGenerator, + } + + interface IState { + mode: ModeType, + items: ItemsType, + reload: boolean, + rnd: Phaser.Math.RandomDataGenerator, + + remainder: ItemsType, + result: string, + restart: boolean + } +} + +declare class Gashapon { + constructor( + config?: Gashapon.IConfig + ); + + constructor( + config?: Gashapon.IState + ); + + destroy(): void; + + next( + name?: string + ): string | null; + + readonly result: string; + + setItem(name: string, count: number): this; + + addItem(name: string, count: number): this; + + putItemBack(name: string, count: number): this; + + removeItem(name: string): this; + + removeAllItems(): this; + + toJSON( + + ): Gashapon.IState; + + resetFromJSON( + state: Gashapon.IState + ): this; + + getItems(): Gashapon.ItemsType; + + getRemain(): Gashapon.ItemsType; + + forEachItem( + callback: Gashapon.ForEachItemCallbackType, + scope?: object, + ): this; + + forEachRemain( + callback: Gashapon.ForEachItemCallbackType, + scope?: object, + ): this; + + getItemCount(name: string): number; + + getRemainCount(name: string): number; + + setRND( + rnd?: Phaser.Math.RandomDataGenerator | null + ): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/gashapon/Gashapon.js b/ui/src/phaser3-rex-plugins/plugins/math/gashapon/Gashapon.js new file mode 100644 index 000000000..db38395bb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/gashapon/Gashapon.js @@ -0,0 +1,339 @@ +import Clone from '../../utils/object/Clone.js'; +import IsEmpty from '../../utils/object/IsEmpty.js'; +import Clear from '../../utils/object/Clear.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class Gashapon { + constructor(config) { + this.resetFromJSON(config); + } + + destroy() { + this.items = undefined; + this.remainder = undefined; + this._list = undefined; + } + + resetFromJSON(o) { + if (this.items == undefined) { + this.items = {}; + } + if (this.remainder == undefined) { + this.remainder = {}; + } + if (this._list == undefined) { + this._list = []; + } + + this.setMode(GetValue(o, 'mode', 0)); + this.setReload(GetValue(o, 'reload', true)); + this.setRND(GetValue(o, 'rnd', undefined)); + + // data + + this.items = Clone(GetValue(o, 'items', {}), this.items); + this._list.length = 0; + + // result + this.result = GetValue(o, 'result', null); + + // flags + this._restartFlag = true; // force restart to rebuild this._list + + // initialize + if (this._restartFlag) { + this.startGen(); + } + var remainder = GetValue(o, 'remainder', undefined); + if (remainder) { + this.remainder = Clone(remainder, this.remainder); + } + + return this; + } + + toJSON() { + return { + // configuration + mode: this.mode, + reload: this.reload, + rnd: this.rnd, + + // data + items: Clone(this.items), + remainder: Clone(this.remainder), + + // result + result: this.result, + + // flags + restart: true // force restart to rebuild this._list + }; + }; + + startGen() { + var name; + // clear remainder items + for (name in this.remainder) { + if (!this.items.hasOwnProperty(name)) { + delete this.remainder[name]; + } + } + // init remainder items + for (name in this.items) { + var count = this.items[name]; + if (count > 0) { + this.remainder[name] = count; + } + } + + if (this.mode === 1) { // random mode + this.resetItemList(this.remainder); + } + this._restartFlag = false; + + return this; + } + + setMode(m) { + if (typeof (m) === 'string') { + m = MODE[m]; + } + this._restartFlag = (this.mode !== m); + this.mode = m; + return this; + } + + setReload(isReload) { + this.reload = !!isReload; + return this; + } + + setRND(rnd) { + this.rnd = rnd; + return this; + } + + setItem(name, count) { + this._restartFlag = (this.items[name] !== count); + this.items[name] = count; + return this; + } + + removeItem(name) { + if (this.items.hasOwnProperty(name)) { + delete this.items[name]; + this._restartFlag = true; + } + return this; + } + + removeAllItems() { + for (var name in this.items) { + delete this.items[name]; + } + this._restartFlag = true; + return this; + } + + getItems() { + return Clone(this.items); + } + + getRemain() { + return Clone(this.remainder); + } + + getItemCount(name) { + return this.items[name] || 0; + } + + getRemainCount(name) { + return this.remainder[name] || 0; + } + + forEachItem(callback, scope) { + var args = [null, undefined]; + + for (var i = 2, len = arguments.length; i < len; i++) { + args.push(arguments[i]); + } + for (var name in this.items) { + args[0] = name; + args[1] = this.items[name]; + + if (scope) { + callback.apply(scope, args); + } else { + callback(args); + } + + } + + return this; + } + + forEachRemain(callback, scope) { + var args = [null, undefined]; + + for (var i = 1; i < arguments.length; i++) { + args.push(arguments[i]); + } + for (var name in this.remainder) { + args[1] = name; + args[2] = this.remainder[name]; + if (scope) { + callback.apply(scope, args); + } else { + callback(args); + } + + } + + return this; + } + + addItem(name, count) { + if (!this.items.hasOwnProperty(name)) { + this.items[name] = 0; + } + this.items[name] += count; + + if (this._restartFlag) + return; + + if (this.mode === 0) { // shuffle mode + this.addRemainItem(name, count); + } else { // random mode + this.resetItemList(this.remainder); + } + return this; + } + + putItemBack(name, count) { + if (this.mode === 1) // random mode + return; + + if (!this.items.hasOwnProperty(name)) { + return; + } + + if ((this.mode === 2) && this.restartGenFlg) { + return; + } + + // generator had started + if (!this.remainder.hasOwnProperty(name)) { + this.remainder[name] = 0; + } + + this.addShadowPattern(name, count, this.items[name]); + return this; + }; + + next(name) { + var result = null; + if (this._restartFlag) { + this.startGen(); + } + + if (name == null) { + if (this.mode === 0) { // shuffle mode + this.resetItemList(this.remainder); + result = this.getRndItem(this._list); + this.addRemainItem(result, -1); + } else { // random mode + result = this.getRndItem(this._list); + } + + } else { // force pick + if (!this.remainder.hasOwnProperty(name)) { + result = null; // can not pick that result + } else { + if (this.mode === 0) { + this.addRemainItem(name, -1); + } + result = name; + } + } + + this.result = result; + return result; + } + + /** @private */ + resetItemList(items) { + // clear list + this._list.length = 0; + var name, count, totalCount = 0; + // get total count + for (name in items) { + count = items[name]; + if (count > 0) + totalCount += count; + } + // set percentage + for (name in items) { + count = items[name]; + if (count > 0) { + this._list.push([ + name, + count / totalCount + ]); + } + } + return this; + } + + /** @private */ + addRemainItem(name, inc, maxCount) { + if ((name == null) || (inc === 0)) { + return this; + } + + if (!this.remainder.hasOwnProperty(name)) { + this.remainder[name] = 0; + } + + this.remainder[name] += inc; + if ((maxCount != null) && (this.remainder[name] > maxCount)) { + this.remainder[name] = maxCount + } + + if (this.remainder[name] <= 0) { + delete this.remainder[name]; + } + + if ((this.mode === 0) && this.reload && IsEmpty(this.remainder)) { + this._restartFlag = true; + } + + return this; + } + + /** @private */ + getRndItem(list) { + var value = (this.rnd) ? this.rnd.frac() : Math.random(); + var result = null, + i, cnt = list.length, + item + for (i = 0; i < cnt; i++) { + item = list[i]; + value -= item[1]; + if (value < 0) { + result = item[0]; + break; + } + } + return result; + } + +} + +const MODE = { + shuffle: 0, + random: 1 +}; + +export default Gashapon; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/raycaster/GetLineToPoints.js b/ui/src/phaser3-rex-plugins/plugins/math/raycaster/GetLineToPoints.js new file mode 100644 index 000000000..f40c34a1b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/raycaster/GetLineToPoints.js @@ -0,0 +1,57 @@ +const GetLineToLine = Phaser.Geom.Intersects.GetLineToLine; +const PointToLine = Phaser.Geom.Intersects.PointToLine + +var GetLineToPoints = function (line, points, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globResult; + } + /* + out: { + x,y, // intersection point + d, // intersection distance + segIndex // intersection segment + } + */ + + var closestIntersect = false; + + startPoint.setTo(line.x1, line.y1); + out.d = Infinity; + tempIntersect.set(); + + var prev = points[0]; + + for (var i = 1; i < points.length; i++) { + var current = points[i]; + + segment.setTo(prev.x, prev.y, current.x, current.y); + prev = current; + + // Ignore case: start point of line is at segment + if (PointToLine(startPoint, segment)) { + continue; + } + + if (GetLineToLine(line, segment, false, tempIntersect)) { + if (tempIntersect.z < out.d) { + out.x = tempIntersect.x; + out.y = tempIntersect.y; + out.d = tempIntersect.z; + out.segIndex = i - 1; + + closestIntersect = true; + } + } + } + + return (closestIntersect) ? out : null; +}; + +var globResult = {}; +var startPoint = new Phaser.Geom.Point(); +var segment = new Phaser.Geom.Line(); +var tempIntersect = new Phaser.Math.Vector3(); + +export default GetLineToPoints; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/raycaster/GetLineToPolygon.js b/ui/src/phaser3-rex-plugins/plugins/math/raycaster/GetLineToPolygon.js new file mode 100644 index 000000000..1db01905e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/raycaster/GetLineToPolygon.js @@ -0,0 +1,57 @@ +import GetLineToPoints from './GetLineToPoints.js'; +import Clone from '../../utils/object/Clone.js'; + +const GetAABB = Phaser.Geom.Polygon.GetAABB; +const LineToRectangle = Phaser.Geom.Intersects.LineToRectangle; + +var GetLineToPolygon = function (line, polygons, out) { + if (out === undefined) { + out = {}; + } else if (out === true) { + out = globResult; + } + /* + out: { + x,y, // intersection point + d, // intersection distance + segIndex, // index of intersection segment + shapeIndex // index of intersection polygon + } + */ + + if (!Array.isArray(polygons)) { + polygons = [polygons]; + } + + var closestIntersect = false; + out.d = Infinity; + + // Reset our vec4s + + for (var i = 0; i < polygons.length; i++) { + var polygon = polygons[i]; + + // Run AABBTest when polygon is more than 8 edges + if ((polygon.points.length > 9) && + !LineToRectangle(line, GetAABB(polygon, AABBRect))) { + continue; + } + + var intersectionResult = GetLineToPoints(line, polygon.points, true); + if (intersectionResult) { + if (intersectionResult.d < out.d) { + Clone(intersectionResult, out); // x,y,d,segIndex + out.shapeIndex = i; + + closestIntersect = true; + } + } + } + + return (closestIntersect) ? out : null; +}; + +var globResult = {}; +var AABBRect = new Phaser.Geom.Rectangle(); + +export default GetLineToPolygon; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/raycaster/Obstacles.js b/ui/src/phaser3-rex-plugins/plugins/math/raycaster/Obstacles.js new file mode 100644 index 000000000..c37eb867f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/raycaster/Obstacles.js @@ -0,0 +1,112 @@ +import IsGameObject from '../../utils/system/IsGameObject.js'; +import BoundsToPolygon from '../../utils/bounds/BoundsToPolygon.js'; + +const Polygon = Phaser.Geom.Polygon; +const SpliceOne = Phaser.Utils.Array.SpliceOne; + +class Obstacles { + constructor() { + this.gameObjects = []; + this.polygons = []; + } + + contains(gameObject) { + return (this.gameObjects.indexOf(gameObject) !== (-1)); + } + + get(index) { + Obstacle.gameObject = this.gameObjects[index]; + Obstacle.polygon = this.polygons[index]; + return Obstacle; + } + + addDestroyCallback(gameObject) { + if (Array.isArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + this.addDestroyCallback(gameObjects[i]); + } + return this; + } + + if (gameObject.on) { + gameObject.once('destroy', this.onChildDestroy, this); + } + return this; + } + + removeDestroyCallback(gameObject) { + if (Array.isArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + this.removeDestroyCallback(gameObjects[i]); + } + return this; + } + + if (gameObject.off) { + gameObject.off('destroy', this.onChildDestroy, this); + } + return this; + } + + clear() { + this.removeDestroyCallback(this.gameObjects); + this.gameObjects.length = 0; + this.polygons.length = 0; + return this; + } + + add(gameObject, polygon) { + if (this.contains(gameObject)) { + return this; + } + + if (IsGameObject(gameObject)) { + if (polygon === undefined) { + polygon = BoundsToPolygon(gameObject); + } + } else if (gameObject instanceof (Polygon)) { + polygon = gameObject; + } + + this.gameObjects.push(gameObject); + this.polygons.push(polygon); + + this.addDestroyCallback(gameObject); + return this; + } + + remove(gameObject) { + var index = this.gameObjects.indexOf(gameObject); + if (index === (-1)) { + return this; + } + + SpliceOne(this.gameObjects, index); + SpliceOne(this.polygons, index); + + this.removeDestroyCallback(gameObject); + return this; + } + + onChildDestroy(child, fromScene) { + this.remove(child); + } + + update(gameObject, polygon) { + var index = this.gameObjects.indexOf(gameObject); + if (index === (-1)) { + return this; + } + if (polygon === undefined) { + polygon = BoundsToPolygon(gameObject, this.polygons[index]); + } + this.polygons[index] = polygon; + return this; + } +} + +var Obstacle = {}; + +export default Obstacles; diff --git a/ui/src/phaser3-rex-plugins/plugins/math/raycaster/Raycaster.d.ts b/ui/src/phaser3-rex-plugins/plugins/math/raycaster/Raycaster.d.ts new file mode 100644 index 000000000..ecae7a1de --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/raycaster/Raycaster.d.ts @@ -0,0 +1,44 @@ +export default Raycaster; + +declare namespace Raycaster { + interface IConfig { + maxRayLength?: number + } + + interface IResult { + gameObject: Phaser.GameObjects.GameObject, + polygon: Phaser.Geom.Polygon, + segment: Phaser.Geom.Line, + x: number, + y: number, + reflectAngle: number, + } +} + +declare class Raycaster { + addObstacle( + gameObject: Phaser.GameObjects.GameObject, + polygon?: Phaser.Geom.Polygon + ): this; + + addObstacle( + gameObjects: Phaser.GameObjects.GameObject[] + ): this; + + removeObstacle( + gameObject: Phaser.GameObjects.GameObject + ): this; + + clearObstacle(): this; + + updateObstacle( + gameObject: Phaser.GameObjects.GameObject, + polygon?: Phaser.Geom.Polygon + ): this; + + rayToward( + x: number, y: number, + angle: number + ): Raycaster.IResult | false; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/math/raycaster/Raycaster.js b/ui/src/phaser3-rex-plugins/plugins/math/raycaster/Raycaster.js new file mode 100644 index 000000000..3a4eccd32 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/math/raycaster/Raycaster.js @@ -0,0 +1,111 @@ +import Obstacles from './Obstacles.js'; +import GetLineToPolygon from './GetLineToPolygon.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const Line = Phaser.Geom.Line; +const SetToAngle = Phaser.Geom.Line.SetToAngle; +const ReflectAngle = Phaser.Geom.Line.ReflectAngle; + +class Reflection { + constructor(config) { + this.obstacles = new Obstacles(); + this.ray = new Line(); + this.setMaxRayLength(GetValue(config, 'maxRayLength', 10000)); + this.result = { + hit: false, + x: 0, y: 0, + segment: new Line(), + polygon: null, + gameObject: null, + reflectAngle: 0 + }; + + } + + destroy() { + this.obstacles.clear(); + this.obstacles = null; + this.ray = null; + this.result = null; + } + + setMaxRayLength(length) { + this.maxRayLength = length; + return this; + } + + addObstacle(gameObject, polygon) { + if (Array.isArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + this.obstacles.add(gameObjects[i]); + } + } else { + this.obstacles.add(gameObject, polygon); + } + return this; + } + + clearObstacle() { + this.obstacles.clear(); + return this; + } + + removeObstacle(gameObject) { + if (Array.isArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + this.obstacles.remove(gameObjects[i]); + } + } else { + this.obstacles.remove(gameObject); + } + return this; + } + + updateObstacle(gameObject, polygon) { + if (Array.isArray(gameObject)) { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + this.obstacles.update(gameObjects[i]); + } + } else { + this.obstacles.update(gameObject, polygon); + } + return this; + } + + hitTest() { + var result = GetLineToPolygon(this.ray, this.obstacles.polygons, true); + if (result) { + this.ray.x2 = result.x; + this.ray.y2 = result.y; + + this.result.hit = true; + this.result.x = result.x; + this.result.y = result.y; + + var obstacle = this.obstacles.get(result.shapeIndex); + this.result.polygon = obstacle.polygon; + this.result.gameObject = obstacle.gameObject; + + var points = this.result.polygon.points, + segIndex = result.segIndex, + p0 = points[segIndex], + p1 = points[segIndex + 1]; + var segment = this.result.segment; + segment.setTo(p0.x, p0.y, p1.x, p1.y); + this.result.reflectAngle = ReflectAngle(this.ray, segment); + } else { + this.result.hit = false; + } + return (result) ? this.result : false; + } + + rayToward(x, y, angle) { + SetToAngle(this.ray, x, y, angle, this.maxRayLength); + return this.hitTest(); + } +} + +export default Reflection; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/maxdelta-plugin.js b/ui/src/phaser3-rex-plugins/plugins/maxdelta-plugin.js new file mode 100644 index 000000000..d52c49096 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/maxdelta-plugin.js @@ -0,0 +1,39 @@ +class MaxDeltaPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + + eventEmitter.on('step', this.update, this); + } + + destroy() { + this.game.events.off('step', this.update, this); + super.destroy(); + } + + reset() { + this.prevTime = undefined; + this.maxDelta = undefined; + } + + update(time, delta) { + if (this.prevTime === undefined) { + this.prevTime = time; + this.maxDelta = 0; + } else { + var dt = time - this.prevTime; + this.prevTime = time; + if (this.maxDelta < dt) { + this.maxDelta = dt; + console.log(`Max delta: ${dt}`); + } + } + } +} + +export default MaxDeltaPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/modal-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/modal-plugin.d.ts new file mode 100644 index 000000000..329884f93 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/modal-plugin.d.ts @@ -0,0 +1,12 @@ +import { ModalBehavoir, Modal, ModalPromise } from './modal' + +export default class ModalPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: ModalBehavoir.IConfig + ): ModalBehavoir; + + modal: typeof Modal; + + promise: typeof ModalPromise; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/modal-plugin.js b/ui/src/phaser3-rex-plugins/plugins/modal-plugin.js new file mode 100644 index 000000000..9183ba284 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/modal-plugin.js @@ -0,0 +1,30 @@ +import { ModalBehavoir, Modal, ModalPromise, ModalClose } from './modal.js' + +class ModalPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new ModalBehavoir(gameObject, config); + } + + modal(gameObject, config) { + return Modal(gameObject, config); + } + + promise(gameObject, config) { + return ModalPromise(gameObject, config); + } + + close(gameObject, closeEventData) { + return ModalClose(gameObject, closeEventData); + } +} + +export default ModalPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/modal.d.ts b/ui/src/phaser3-rex-plugins/plugins/modal.d.ts new file mode 100644 index 000000000..48af7a6ad --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/modal.d.ts @@ -0,0 +1,4 @@ +import ModalBehavoir from './behaviors/modal/Modal'; +import { Modal, ModalPromise, ModalClose } from './behaviors/modal/ModalPromise'; + +export { ModalBehavoir, Modal, ModalPromise, ModalClose }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/modal.js b/ui/src/phaser3-rex-plugins/plugins/modal.js new file mode 100644 index 000000000..c149282a0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/modal.js @@ -0,0 +1,4 @@ +import ModalBehavoir from './behaviors/modal/Modal.js'; +import { Modal, ModalPromise, ModalClose } from './behaviors/modal/ModalPromise.js'; + +export { ModalBehavoir, Modal, ModalPromise, ModalClose }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/mousewheelscroller-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/mousewheelscroller-plugin.d.ts new file mode 100644 index 000000000..af0b0c7d3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/mousewheelscroller-plugin.d.ts @@ -0,0 +1,9 @@ +import MouseWheelScroller from './mousewheelscroller'; + +export default class MouseWheelScrollerPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: MouseWheelScroller.IConfig + ): MouseWheelScroller; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/mousewheelscroller-plugin.js b/ui/src/phaser3-rex-plugins/plugins/mousewheelscroller-plugin.js new file mode 100644 index 000000000..994ac8f28 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/mousewheelscroller-plugin.js @@ -0,0 +1,20 @@ +import MouseWheelScroller from './mousewheelscroller.js'; + +class MouseWheelScrollerPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new MouseWheelScroller(gameObject, config); + } + +} + +export default MouseWheelScrollerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/mousewheelscroller.d.ts b/ui/src/phaser3-rex-plugins/plugins/mousewheelscroller.d.ts new file mode 100644 index 000000000..48d6b7782 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/mousewheelscroller.d.ts @@ -0,0 +1,2 @@ +import MouseWheelScroller from './input/mousewheelscroller/MouseWheelScroller'; +export default MouseWheelScroller; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/mousewheelscroller.js b/ui/src/phaser3-rex-plugins/plugins/mousewheelscroller.js new file mode 100644 index 000000000..e4367b65b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/mousewheelscroller.js @@ -0,0 +1,2 @@ +import MouseWheelScroller from './input/mousewheelscroller/MouseWheelScroller.js'; +export default MouseWheelScroller; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown-plugin.d.ts new file mode 100644 index 000000000..490b24ff3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown-plugin.d.ts @@ -0,0 +1,9 @@ +import MouseWheelToUpDown from './mousewheeltoupdown'; + +export default class MouseWheelToUpDownPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: MouseWheelToUpDown.IConfig + ): MouseWheelToUpDown; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown-plugin.js b/ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown-plugin.js new file mode 100644 index 000000000..18f250b6b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown-plugin.js @@ -0,0 +1,20 @@ +import MouseWheelToUpDown from './mousewheeltoupdown.js'; + +class MouseWheelToUpDownPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new MouseWheelToUpDown(scene, config); + } + +} + +export default MouseWheelToUpDownPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown.d.ts b/ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown.d.ts new file mode 100644 index 000000000..0668fb624 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown.d.ts @@ -0,0 +1,2 @@ +import MouseWheelToUpDown from './input/mousewheeltoupdown/MouseWheelToUpDown'; +export default MouseWheelToUpDown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown.js b/ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown.js new file mode 100644 index 000000000..ec338a65d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/mousewheeltoupdown.js @@ -0,0 +1,2 @@ +import MouseWheelToUpDown from './input/mousewheeltoupdown/MouseWheelToUpDown.js'; +export default MouseWheelToUpDown; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/moveto-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/moveto-plugin.d.ts new file mode 100644 index 000000000..bb12e9c1d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/moveto-plugin.d.ts @@ -0,0 +1,9 @@ +import MoveTo from './moveto'; + +export default class MoveToPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: MoveTo.IConfig + ): MoveTo; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/moveto-plugin.js b/ui/src/phaser3-rex-plugins/plugins/moveto-plugin.js new file mode 100644 index 000000000..dfdb051c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/moveto-plugin.js @@ -0,0 +1,19 @@ +import MoveTo from './moveto.js'; + +class MoveToPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new MoveTo(gameObject, config); + } +} + +export default MoveToPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/moveto.d.ts b/ui/src/phaser3-rex-plugins/plugins/moveto.d.ts new file mode 100644 index 000000000..c3292e79b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/moveto.d.ts @@ -0,0 +1,2 @@ +import MoveTo from './behaviors/moveto/MoveTo'; +export default MoveTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/moveto.js b/ui/src/phaser3-rex-plugins/plugins/moveto.js new file mode 100644 index 000000000..d6eb2c816 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/moveto.js @@ -0,0 +1,2 @@ +import MoveTo from './behaviors/moveto/MoveTo.js'; +export default MoveTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/ninepatch-plugin.js b/ui/src/phaser3-rex-plugins/plugins/ninepatch-plugin.js new file mode 100644 index 000000000..581de71e0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/ninepatch-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/rendertexture/ninepatch/Factory.js'; +import Creator from './gameobjects/rendertexture/ninepatch/Creator.js'; +import NinePatch from './gameobjects/rendertexture/ninepatch/NinePatch.js'; +import SetValue from './utils/object/SetValue.js'; + +class NinePatchPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexNinePatch', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.NinePatch', NinePatch); + +export default NinePatchPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/ninepatch.d.ts b/ui/src/phaser3-rex-plugins/plugins/ninepatch.d.ts new file mode 100644 index 000000000..830d4dc2b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/ninepatch.d.ts @@ -0,0 +1,2 @@ +import NinePatch from './gameobjects/rendertexture/ninepatch/NinePatch'; +export default NinePatch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/ninepatch.js b/ui/src/phaser3-rex-plugins/plugins/ninepatch.js new file mode 100644 index 000000000..b312ffb33 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/ninepatch.js @@ -0,0 +1,2 @@ +import NinePatch from './gameobjects/rendertexture/ninepatch/NinePatch.js'; +export default NinePatch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/ninepatch2-plugin.js b/ui/src/phaser3-rex-plugins/plugins/ninepatch2-plugin.js new file mode 100644 index 000000000..760abe609 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/ninepatch2-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/blitter/ninepatch/Factory.js'; +import Creator from './gameobjects/blitter/ninepatch/Creator.js'; +import NinePatch from './gameobjects/blitter/ninepatch/NinePatch.js'; +import SetValue from './utils/object/SetValue.js'; + +class NinePatchPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexNinePatch2', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.NinePatch2', NinePatch); + +export default NinePatchPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/ninepatch2.d.ts b/ui/src/phaser3-rex-plugins/plugins/ninepatch2.d.ts new file mode 100644 index 000000000..74ee94f0e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/ninepatch2.d.ts @@ -0,0 +1,2 @@ +import NinePatch from './gameobjects/blitter/ninepatch/NinePatch'; +export default NinePatch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/ninepatch2.js b/ui/src/phaser3-rex-plugins/plugins/ninepatch2.js new file mode 100644 index 000000000..f52972d7f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/ninepatch2.js @@ -0,0 +1,2 @@ +import NinePatch from './gameobjects/blitter/ninepatch/NinePatch.js'; +export default NinePatch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/objectpool-plugin.js b/ui/src/phaser3-rex-plugins/plugins/objectpool-plugin.js new file mode 100644 index 000000000..fc5d13711 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/objectpool-plugin.js @@ -0,0 +1,18 @@ +import ObjectPool from './objectpool.js'; + +class ObjectPoolPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add() { + return new ObjectPool(); + } +} + +export default ObjectPoolPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/objectpool.js b/ui/src/phaser3-rex-plugins/plugins/objectpool.js new file mode 100644 index 000000000..a2d7fae61 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/objectpool.js @@ -0,0 +1,2 @@ +import ObjectPool from './data/pool/ObjectPool.js'; +export default ObjectPool; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/outlineeffectlayer-plugin.js b/ui/src/phaser3-rex-plugins/plugins/outlineeffectlayer-plugin.js new file mode 100644 index 000000000..d0c519994 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/outlineeffectlayer-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/shader/effectlayer/outline/Factory.js'; +import Creator from './gameobjects/shader/effectlayer/outline/Creator.js'; +import OutlineEffectLayer from './gameobjects/shader/effectlayer/outline/OutlineEffectLayer.js'; +import SetValue from './utils/object/SetValue.js'; + +class OutlineEffectLayerPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexOutlineEffectLayer', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.OutlineEffectLayer', OutlineEffectLayer); + +export default OutlineEffectLayerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/outlineeffectlayer.js b/ui/src/phaser3-rex-plugins/plugins/outlineeffectlayer.js new file mode 100644 index 000000000..d0a9b4412 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/outlineeffectlayer.js @@ -0,0 +1,2 @@ +import OutlineEffectLayer from './gameobjects/shader/effectlayer/outline/OutlineEffectLayer.js'; +export default OutlineEffectLayer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/outlinepipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/outlinepipeline-plugin.d.ts new file mode 100644 index 000000000..4903b6a77 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/outlinepipeline-plugin.d.ts @@ -0,0 +1,37 @@ +// import * as Phaser from 'phaser'; +import OutlinePostFxPipeline from './outlinepipeline'; + + +export default OutlinePipelinePlugin; + +declare namespace OutlinePipelinePlugin { + + interface IConfig { + thickness?: number, + outlineColor?: number, + quality?: number, + + name?: string, + } + +} + +declare class OutlinePipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: OutlinePipelinePlugin.IConfig + ): OutlinePostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): OutlinePostFxPipeline | OutlinePostFxPipeline[]; + + setQuality(value: number): this; + quality: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/outlinepipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/outlinepipeline-plugin.js new file mode 100644 index 000000000..297a021df --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/outlinepipeline-plugin.js @@ -0,0 +1,34 @@ +import OutlinePostFxPipeline from './outlinepipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class OutlinePipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(OutlinePostFxPipeline, 'rexOutlinePostFx'); + } + + add(gameObject, config) { + this.setQuality(GetValue(config, 'quality', this.quality)); + return super.add(gameObject, config); + } + + setQuality(value) { + OutlinePostFxPipeline.setQuality(value); + return this; + } + + set quality(value) { + this.setQuality(value); + } + + get quality() { + return OutlinePostFxPipeline.getQuality(); + } +} + +SetValue(window, 'RexPlugins.Pipelines.OutlinePostFx', OutlinePostFxPipeline); + +export default OutlinePipelinePlugin; diff --git a/ui/src/phaser3-rex-plugins/plugins/outlinepipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/outlinepipeline.d.ts new file mode 100644 index 000000000..60c9dbfc7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/outlinepipeline.d.ts @@ -0,0 +1,2 @@ +import OutlinePostFxPipeline from './shaders/outline/OutlinePostFxPipeline'; +export default OutlinePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/outlinepipeline.js b/ui/src/phaser3-rex-plugins/plugins/outlinepipeline.js new file mode 100644 index 000000000..e78711eee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/outlinepipeline.js @@ -0,0 +1,2 @@ +import OutlinePostFxPipeline from './shaders/outline/OutlinePostFxPipeline.js'; +export default OutlinePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse-plugin.js b/ui/src/phaser3-rex-plugins/plugins/parse-plugin.js new file mode 100644 index 000000000..0b0f63a36 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse-plugin.js @@ -0,0 +1,34 @@ +import LoaderCallback from './parse/utils/preload/LoaderCallback.js'; +import ObjectFactory from './parse/ObjectFactory.js'; +import PageLoaderFactory from './parse/pageloader/Factory.js' +import ItemTableFactory from './parse/itemtable/Factory.js'; +import LeaderBoardFactory from './parse/leaderboard/Factory.js'; +import QuickLogin from './parse/quicklogin/QuickLogin.js'; + +class ParsePlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + + this.add = new ObjectFactory(); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + preload(scene, url) { + LoaderCallback.call(scene.sys.load, url); + return this; + } +} + +var methods = { + quickLogin: QuickLogin +} +Object.assign( + ParsePlugin.prototype, + methods +); + +export default ParsePlugin; diff --git a/ui/src/phaser3-rex-plugins/plugins/parse.js b/ui/src/phaser3-rex-plugins/plugins/parse.js new file mode 100644 index 000000000..912ce27fd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse.js @@ -0,0 +1,27 @@ +import ObjectFactory from './parse/ObjectFactory.js'; +import Preload from './parse/utils/preload/Preload.js'; +import PageLoaderFactory from './parse/pageloader/Factory.js' +import ItemTableFactory from './parse/itemtable/Factory.js'; +import LeaderBoardFactory from './parse/leaderboard/Factory.js'; +import QuickLogin from './parse/quicklogin/QuickLogin.js'; + +class ParsePlugin { + constructor() { + this.add = new ObjectFactory(); + } + + preload(url) { + return Preload(url); + } +} + +var methods = { + quickLogin: QuickLogin +} +Object.assign( + ParsePlugin.prototype, + methods +); + + +export default ParsePlugin; diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/ObjectFactory.js b/ui/src/phaser3-rex-plugins/plugins/parse/ObjectFactory.js new file mode 100644 index 000000000..63bc67288 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/ObjectFactory.js @@ -0,0 +1,15 @@ +class ObjectFactory { + constructor() { + } + + initializeApp(config) { + firebase.initializeApp(config); + return this; + } + + static register(type, callback) { + ObjectFactory.prototype[type] = callback; + } +}; + +export default ObjectFactory; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/DeleteMethods.js b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/DeleteMethods.js new file mode 100644 index 000000000..34d683463 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/DeleteMethods.js @@ -0,0 +1,16 @@ +import Delete from '../utils/query/Delete.js'; + +var Methods = { + deleteItem(itemId) { + return this.createItem().set('id', itemId).destroy(); + }, + + delete(query) { + if (query === undefined) { + query = this.baseQuery; + } + return Delete(query); + } +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/Factory.js b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/Factory.js new file mode 100644 index 000000000..e8ecfa3f1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/Factory.js @@ -0,0 +1,11 @@ +import ItemTable from './ItemTable.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../utils/object/SetValue.js'; + +ObjectFactory.register('itemTable', function (config) { + return new ItemTable(config); +}); + +SetValue(window, 'RexPlugins.Parse.ItemTable', ItemTable); + +export default ItemTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/GetItemCount.js b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/GetItemCount.js new file mode 100644 index 000000000..eae013a72 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/GetItemCount.js @@ -0,0 +1,7 @@ +var GetItemCount = function (query) { + if (query === undefined) { + query = this.baseQuery; + } + return query.count(); +} +export default GetItemCount; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/GetQuery.js b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/GetQuery.js new file mode 100644 index 000000000..51dd06a61 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/GetQuery.js @@ -0,0 +1,13 @@ +var GetQuery = function (data) { + var query = this.baseQuery; + var isItem = (data instanceof this.customClass); + var key, value; + for (var i = 0, cnt = this.primaryKeys.length; i < cnt; i++) { + key = this.primaryKeys[i]; + value = (isItem) ? data.get(key) : data[key]; + query.equalTo(key, value); + } + return query; +} + +export default GetQuery; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/ItemTable.js b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/ItemTable.js new file mode 100644 index 000000000..7cb4f7312 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/ItemTable.js @@ -0,0 +1,103 @@ +import GetValue from '../../utils/object/GetValue.js'; +import PageLoader from '../pageloader/PageLoader.js'; +import GetQuery from './GetQuery.js'; +import LoadMethods from './LoadMethods.js'; +import DeleteMethods from './DeleteMethods.js'; +import Copy from '../../utils/array/Copy.js'; +import Save from './Save.js'; +import SaveItems from './SaveItems.js'; +import GetItemCount from './GetItemCount.js'; + +class ItemTable { + constructor(config) { + this.pageLoader = new PageLoader(); + + this.setClassName(GetValue(config, 'className', 'Item')); + this.setItemCount(GetValue(config, 'itemCount', 100)); + this.setQuery(); // Reset to base query + this.primaryKeys = []; + var primaryKeys = GetValue(config, 'primaryKeys', undefined); + if (primaryKeys) { + this.setPrimaryKey(primaryKeys); + } + + this.setOwnerReadMode(GetValue(config, 'ownerRead', undefined)); + this.setOwnerWriteMode(GetValue(config, 'ownerWrite', undefined)); + + } + + setClassName(className) { + this.customClass = Parse.Object.extend(className); + return this; + } + + setPrimaryKey(key) { + if (!key) { + this.primaryKeys.length = 0; + } else if (typeof (key) === 'string') { + this.primaryKeys.length = 1; + this.primaryKeys[0] = key; + } else { + Copy(this.primaryKeys, key); + } + return this; + } + + setOwnerReadMode(mode) { + this.ownerRead = mode; + return this; + } + + setOwnerWriteMode(mode) { + this.ownerWrite = mode; + return this; + } + + createItem() { + return new this.customClass(); + } + + setItemCount(itemCount) { + this.pageLoader.setItemCount(itemCount); + return this; + } + + setQuery(query) { + if (query === undefined) { + query = this.baseQuery; + } + this.pageLoader.setQuery(query); + return this; + } + + get baseQuery() { + return new Parse.Query(this.customClass); + } + + get startIndex() { + return this.pageLoader.startIndex; + } + + get pageIndex() { + return this.pageLoader.pageIndex; + } + + get isLastPage() { + return this.pageLoader.isLastPage; + } +} + +var methods = { + getQuery: GetQuery, + save: Save, + saveItems: SaveItems, + getItemCount: GetItemCount, +} +Object.assign( + ItemTable.prototype, + LoadMethods, + DeleteMethods, + methods +); + +export default ItemTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/LoadMethods.js b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/LoadMethods.js new file mode 100644 index 000000000..61ba65720 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/LoadMethods.js @@ -0,0 +1,54 @@ +import Load from '../utils/query/Load.js'; +import LoadRandomItems from './LoadRandomItems.js'; + +var Methods = { + loadItem(itemId, select) { + if (typeof (itemId) === 'string') { + var query = this.baseQuery; + if (select) { + query = query.select(select); + } + return query.get(itemId); + } else { // Query by primary keys + var query = this.getQuery(itemId).limit(1); + if (select) { + query = query.select(select); + } + return query.find() + .then(function (result) { + return Promise.resolve(result[0]); + }) + } + }, + + loadPage(pageIndex) { + return this.pageLoader.loadPage(pageIndex); + }, + + loadCurrentPage() { + return this.pageLoader.loadCurrentPage(); + }, + + loadNextPage() { + return this.pageLoader.loadNextPage(); + }, + + loadPreviousPage() { + return this.pageLoader.loadPreviousPage(); + }, + + loadItems(startIndex, itemCount) { + return this.pageLoader.loadItems(startIndex, itemCount); + }, + + load(query) { + if (query === undefined) { + query = this.baseQuery; + } + return Load(query); + }, + + loadRandomItems: LoadRandomItems +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/LoadRandomItems.js b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/LoadRandomItems.js new file mode 100644 index 000000000..ab9126747 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/LoadRandomItems.js @@ -0,0 +1,34 @@ +import Load from '../utils/query/Load.js'; +import Shuffle from '../../utils/array/Shuffle.js'; + +var LoadRandomItems = function (query, count) { + if (typeof (query) === 'number') { + count = query; + query = undefined; + } + if (query === undefined) { + query = this.baseQuery; + } + if (count === undefined) { + count = 1; + } + + // Load all item Id + query.select('id'); + var self = this; + return Load(query) + .then(function (items) { + // Shuffle items + Shuffle(items); + count = Math.min(count, items.length); + var itemIds = []; + for (var i = 0; i < count; i++) { + itemIds.push(items[i].id); + } + // Load first N items by item Id + query = self.baseQuery.containedIn('objectId', itemIds); + return Load(query) + }) +} + +export default LoadRandomItems; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/Save.js b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/Save.js new file mode 100644 index 000000000..21c3506a8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/Save.js @@ -0,0 +1,26 @@ +import IsArray from '../../utils/object/IsArray.js'; +import DataToItem from '../utils/DataToItem.js'; +import SetOwnerAccessMode from '../utils/SetOwnerAccessMode.js'; + +var Save = function (data) { // JSON data, or parse object + if (IsArray(data)) { + return this.saveItems(data); + } + + var self = this; + return new Promise(function (resolve, reject) { + if (self.primaryKeys.length > 0) { + self.loadItem(data, 'id') + .then(resolve, reject); + } else { + return resolve(); + } + }) + .then(function (item) { + item = DataToItem(data, self.customClass, item); + SetOwnerAccessMode(item, self.ownerRead, self.ownerWrite); + return item.save(); + }) +} + +export default Save; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/SaveItems.js b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/SaveItems.js new file mode 100644 index 000000000..8552ecef7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/itemtable/SaveItems.js @@ -0,0 +1,39 @@ +import DataToItem from '../utils/DataToItem.js'; +import SetOwnerAccessMode from '../utils/SetOwnerAccessMode.js'; + +var SaveItems = function (dataArray) { + var self = this; + return new Promise(function (resolve, reject) { + var items = []; + if (self.primaryKeys.length > 0) { + var promises = [], promise; + for (var i = 0, cnt = dataArray.length; i < cnt; i++) { + let data = dataArray[i]; + promise = self.loadItem(data, 'id') + .then(function (item) { + item = DataToItem(data, self.customClass, item); + SetOwnerAccessMode(item, self.ownerRead, self.ownerWrite); + items.push(item); + }) + promises.push(promise) + } + Promise.all(promises) + .then(function () { + return resolve(items); + }) + .catch(reject) + + } else { + for (var i = 0, cnt = dataArray.length; i < cnt; i++) { + var item = DataToItem(dataArray[i], self.customClass); + SetOwnerAccessMode(item, self.ownerRead, self.ownerWrite); + items.push(item); + } + return resolve(items); + } + }) + .then(function (items) { + return Parse.Object.saveAll(items); + }) +} +export default SaveItems; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/Const.js b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/Const.js new file mode 100644 index 000000000..37bade5e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/Const.js @@ -0,0 +1,26 @@ +var TimeTagKeys = { + d: 'tagD', + w: 'tagW', + m: 'tagM', + y: 'tagY' +} + +var ScoreKeys = { + d: 'scoreD', + w: 'scoreW', + m: 'scoreM', + y: 'scoreY' +} + +var FullTimeName = { + d: 'day', + w: 'week', + m: 'month', + y: 'year' +} + +export { + TimeTagKeys, + ScoreKeys, + FullTimeName +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/DeleteMethods.js b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/DeleteMethods.js new file mode 100644 index 000000000..11e0f6fbe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/DeleteMethods.js @@ -0,0 +1,25 @@ +import Delete from '../utils/query/Delete.js' + +var Methods = { + deleteUser(userID) { + if (userID === undefined) { + userID = this.userID; + } + + var query = this.getRecordQuery(undefined, undefined, userID, undefined); + return Delete(query); + }, + + deleteBoard(boardId, tag) { + if (boardId === undefined) { + boardId = this.boardID; + } + if (tag === undefined) { + tag = this.tag; + } + + var query = this.getRecordQuery(boardId, tag, undefined, undefined); + return Delete(query); + } +} +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/Factory.js b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/Factory.js new file mode 100644 index 000000000..8f89141e5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/Factory.js @@ -0,0 +1,11 @@ +import Leaderboard from './Leaderboard.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../utils/object/SetValue.js'; + +ObjectFactory.register('leaderBoard', function (config) { + return new Leaderboard(config); +}); + +SetValue(window, 'RexPlugins.Parse.Leaderboard', Leaderboard); + +export default Leaderboard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetQueryMethods.js b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetQueryMethods.js new file mode 100644 index 000000000..d88aed276 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetQueryMethods.js @@ -0,0 +1,41 @@ +import { TimeTagKeys, ScoreKeys } from './Const.js'; +import GetTime from './GetTime.js'; + +var Methods = { + getRecordQuery(boardID, customTag, userID, timeTagKey) { + var query = this.baseQuery; + query = (boardID !== undefined) ? query.equalTo('boardID', boardID) : query; + query = (customTag !== undefined) ? query.equalTo('tag', customTag) : query; + query = (userID !== undefined) ? query.equalTo('userID', userID) : query; + + if (timeTagKey !== undefined) { + query = query.equalTo(timeTagKey[0], timeTagKey[1]); + } + return query; + }, + + getMyRecordQuery(userID) { + if (userID === undefined) { + userID = this.userID; + } + return this.getRecordQuery(this.boardID, this.tag, userID, undefined).limit(1); + }, + + getPageQuery() { + var timeTagKey, scoreKey; + if (this.timeFilters !== false) { + var t = this.timeFilterType[0]; + timeTagKey = [TimeTagKeys[t], GetTime()[t]]; + scoreKey = ScoreKeys[t]; + } else { // No time filters + timeTagKey = undefined; + scoreKey = 'score'; + } + + var query = this.getRecordQuery(this.boardID, this.tag, undefined, timeTagKey); + query = query.descending(scoreKey); + return query; + } +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetRank.js b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetRank.js new file mode 100644 index 000000000..332027843 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetRank.js @@ -0,0 +1,19 @@ +import FindFirst from '../utils/query/FindFirst.js'; + +var GetRank = function (userID) { + if (userID === undefined) { + userID = this.userID; + } + + var query = this.getPageQuery(); + var testCallback = function (item) { + return (item.get('userID') === userID); + } + return FindFirst(query, testCallback) + .then(function (result) { + return Promise.resolve({ userID: userID, rank: result.index }); + }) +}; + + +export default GetRank; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetScore.js b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetScore.js new file mode 100644 index 000000000..56143a851 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetScore.js @@ -0,0 +1,15 @@ +import { TimeTagKeys, ScoreKeys, FullTimeName } from './Const.js'; + +var GetScore = function (userID) { + var self = this; + return this.getMyRecordQuery(userID).find() + .then(function (results) { + var myRecord = results[0]; + if (myRecord) { + myRecord = myRecord.toJSON(); + } + return Promise.resolve(myRecord); + }); +} + +export default GetScore; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetTime.js b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetTime.js new file mode 100644 index 000000000..f464cd008 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/GetTime.js @@ -0,0 +1,16 @@ +var GetTime = function (timeStamp) { + var date = (timeStamp) ? (new Date(timeStamp)) : (new Date()); + var y = date.getFullYear(); + var m = date.getMonth() + 1; + var d = date.getDate(); + var Jan1st = new Date(date.getFullYear(), 0, 1); + var w = Math.ceil((((date - Jan1st) / 86400000) + Jan1st.getDay() + 1) / 7); + return { + d: `${y}-${m}-${d}`, + w: `${y}-${w}`, + m: `${y}-${m}`, + y: `${y}` + }; +} + +export default GetTime; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/LeaderBoard.js b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/LeaderBoard.js new file mode 100644 index 000000000..728d6954c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/LeaderBoard.js @@ -0,0 +1,135 @@ +import GetValue from '../../utils/object/GetValue.js'; +import IsPlainObject from '../../utils/object/IsPlainObject.js'; +import Post from './Post.js';; +import LoadMethods from './LoadMethods.js'; +import GetScore from './GetScore.js'; +import GetRank from './GetRank.js'; +import DeleteMethods from './DeleteMethods.js'; +import GetQueryMethods from './GetQueryMethods.js'; +import PageLoader from '../pageloader/PageLoader.js'; + +class LeaderBoard { + constructor(config) { + this.setClassName(GetValue(config, 'className', 'Item')); + + this.userInfo = { userID: undefined, userName: undefined }; + this.setUser(GetValue(config, 'userID', ''), GetValue(config, 'userName', undefined)); + this.setBoardID(GetValue(config, 'boardID', undefined)); + this.setTag(GetValue(config, 'tag', undefined)); + this.setTimeFilters(GetValue(config, 'timeFilters', false)); + this.setTimeFilterType(GetValue(config, 'timeFilterType', 'year')); + + this.page = new PageLoader({ + itemCount: GetValue(config, 'pageItemCount', 100) + }); + this.resetQueryFlag = true; + } + + shutdown() { + } + + destroy() { + this.shutdown(); + } + + get userID() { + return this.userInfo.userID; + } + + set userID(value) { + this.userInfo.userID = value; + } + + get userName() { + return this.userInfo.userName; + } + + set userName(value) { + this.userInfo.userName = value; + } + + setClassName(className) { + this.resetQueryFlag = true; + this.customClass = Parse.Object.extend(className); + return this; + } + + setUser(userID, userName) { + if (IsPlainObject(userID)) { + this.userInfo = userID; + } else { + this.userID = userID; + this.userName = userName; + } + return this; + } + + setBoardID(boardID) { + this.resetQueryFlag |= (this.boardID !== boardID); + this.boardID = boardID; + return this; + } + + setTag(tag) { + this.resetQueryFlag |= (this.tag !== tag); + this.tag = tag; + return this; + } + + setTimeFilters(filters) { + if (filters === false) { + this.timeFilters = false; + } else { // filters is true, or a plain object + this.timeFilters = { + d: GetValue(filters, 'day', true), + w: GetValue(filters, 'week', true), + m: GetValue(filters, 'month', true), + y: GetValue(filters, 'year', true) + } + } + return this; + } + + setTimeFilterType(type) { + this.resetQueryFlag |= (this.timeFilterType !== type); + this.timeFilterType = type; + return this; + } + + setPageItemCount(count) { + this.page.setItemCount(count); + return this; + } + + get baseQuery() { + return new Parse.Query(this.customClass); + } + + get pageIndex() { + return this.page.pageIndex; + } + + get isFirstPage() { + return (this.page.pageIndex === 0); + } + + get isLastPage() { + return (this.page.isFullPage === false); + } +} + +var methods = { + post: Post, + getScore: GetScore, + getRank: GetRank +} + +Object.assign( + LeaderBoard.prototype, + methods, + GetQueryMethods, + LoadMethods, + DeleteMethods +); + +export default LeaderBoard; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/LoadMethods.js b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/LoadMethods.js new file mode 100644 index 000000000..b9125d952 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/LoadMethods.js @@ -0,0 +1,86 @@ +import { TimeTagKeys, ScoreKeys } from './Const.js'; + +var Methods = { + loadFirstPage() { + this.resetPageQuery(); + + var self = this; + return this.page.loadFirstPage() + .then(function (items) { + return Promise.resolve(ItemsToDataArray.call(self, items)); + }) + }, + + loadNextPage() { + this.resetPageQuery(); + + var self = this; + return this.page.loadNextPage() + .then(function (items) { + return Promise.resolve(ItemsToDataArray.call(self, items)); + }) + }, + + loadPreviousPage() { + this.resetPageQuery(); + + var self = this; + return this.page.loadPreviousPage() + .then(function (items) { + return Promise.resolve(ItemsToDataArray.call(self, items)); + }) + }, + + loadCurrentPage() { + this.resetPageQuery(); + + var self = this; + return this.page.loadCurrentPage() + .then(function (items) { + return Promise.resolve(ItemsToDataArray.call(self, items)); + }) + }, + + load(count, skip) { + this.resetPageQuery(); + + var self = this; + return this.page.load(count, skip) + .then(function (items) { + return Promise.resolve(ItemsToDataArray.call(self, items)); + }) + }, + + resetPageQuery() { + if (!this.resetQueryFlag) { + return this; + } + + this.resetQueryFlag = false; + this.page.setQuery(this.getPageQuery()); + return this; + } +} + +var ItemsToDataArray = function (items) { + var dataArray = [], + data; + + var scoreKey = ScoreKeys[this.timeFilterType[0]]; + for (var i = 0, cnt = items.length; i < cnt; i++) { + data = items[i].toJSON(); + + if (this.timeFilters !== false) { + data.score = data[scoreKey]; + // Remove timeFilterKeys, and scoreKeys + for (var t in this.timeFilters) { + delete data[TimeTagKeys[t]]; + delete data[ScoreKeys[t]]; + } + } + dataArray.push(data); + } + return dataArray; +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/Post.js b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/Post.js new file mode 100644 index 000000000..549799143 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/leaderboard/Post.js @@ -0,0 +1,60 @@ +import GetTime from './GetTime.js'; +import { TimeTagKeys, ScoreKeys } from './Const.js'; +import DataToItem from '../utils/DataToItem.js'; + +var Post = function (score, extraData, timeStamp) { + var newRecord = { + userID: this.userID + }; + if (this.boardID !== undefined) { + newRecord.boardID = this.boardID; + } + if (this.userName) { + newRecord.userName = this.userName; + } + var curTimeData = GetTime(timeStamp); + if (this.timeFilters !== false) { + for (var t in this.timeFilters) { + if (!this.timeFilters[t]) { + continue; + } + newRecord[TimeTagKeys[t]] = curTimeData[t]; + newRecord[ScoreKeys[t]] = score; + } + } else { // No time filters + newRecord.score = score; + } + if (this.tag) { + newRecord.tag = this.tag; + } + if (extraData) { + Object.assign(newRecord, extraData); + } + var curTimeData = GetTime(); + var self = this; + return this.getMyRecordQuery().find() + .then(function (results) { + var prevRecord = results[0]; + if (prevRecord) { + if (self.timeFilters !== false) { + for (var t in self.timeFilters) { + if (!self.timeFilters[t]) { + continue; + } + + var timeTagKey = TimeTagKeys[t]; + if (prevRecord.get(timeTagKey) === newRecord[timeTagKey]) { + var scoreKey = ScoreKeys[t]; + newRecord[scoreKey] = Math.max(prevRecord.get(scoreKey), newRecord[scoreKey]); + } + } + } else { // No time filters + newRecord.score = Math.max(prevRecord.get('score'), newRecord.score); + } + } + var item = DataToItem(newRecord, self.customClass, prevRecord); + return item.save(); + }); +} + +export default Post; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/pageloader/Factory.js b/ui/src/phaser3-rex-plugins/plugins/parse/pageloader/Factory.js new file mode 100644 index 000000000..c7d46b85d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/pageloader/Factory.js @@ -0,0 +1,11 @@ +import PageLoader from './PageLoader.js'; +import ObjectFactory from '../ObjectFactory.js'; +import SetValue from '../../utils/object/SetValue.js'; + +ObjectFactory.register('pageLoader', function (config) { + return new PageLoader(config); +}); + +SetValue(window, 'RexPlugins.Parse.PageLoader', PageLoader); + +export default PageLoader; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/pageloader/LoadMethods.js b/ui/src/phaser3-rex-plugins/plugins/parse/pageloader/LoadMethods.js new file mode 100644 index 000000000..d17cbae48 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/pageloader/LoadMethods.js @@ -0,0 +1,53 @@ +import Load from '../utils/query/Load.js'; + +var Methods = { + loadItems(startIndex, itemCount) { + if (startIndex === undefined) { + startIndex = 0; + } + if (itemCount === undefined) { + itemCount = Infinity; + } + + this.items.length = 0; + + var self = this; + return Load(this.query, startIndex, itemCount) + .then(function (items) { + self.items = items; + self.startIndex = startIndex; + self.pageIndex = Math.floor(startIndex / self.itemCount); + self.isFullPage = (itemCount === Infinity) ? true : (itemCount === items.length); + return Promise.resolve(items); + }) + .catch(function (error) { + self.isFullPage = false; + return Promise.reject(error); + }) + }, + + loadPage(pageIndex) { + var startIndex = pageIndex * this.itemCount; + return this.loadItems(startIndex, this.itemCount); + }, + + loadFirstPage() { + return this.loadItems(0, this.itemCount); + }, + + loadCurrentPage() { + return this.loadItems(this.startIndex, this.itemCount); + }, + + loadNextPage() { + var startIndex = this.startIndex + this.itemCount; + return this.loadItems(startIndex, this.itemCount); + }, + + loadPreviousPage() { + var startIndex = this.startIndex - this.itemCount; + return this.loadItems(startIndex, this.itemCount); + } +} + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/pageloader/PageLoader.js b/ui/src/phaser3-rex-plugins/plugins/parse/pageloader/PageLoader.js new file mode 100644 index 000000000..be3ae7306 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/pageloader/PageLoader.js @@ -0,0 +1,45 @@ +import GetValue from '../../utils/object/GetValue.js'; +import LoadMethods from './LoadMethods.js'; + +class PageLoader { + constructor(config) { + this.items = []; + this.startIndex = 0; + this.pageIndex = 0; + this.isFullPage = false; + this.setItemCount(GetValue(config, 'itemCount', 100)); + this.setQuery(GetValue(config, 'query', undefined)); + } + + setItemCount(itemCount) { + this.itemCount = itemCount; + this.pageIndex = Math.floor(this.startIndex / itemCount); + return this; + } + + setQuery(query) { + this.query = query; + return this; + } + + getItem(i) { + return this.items[i - this.startIndex]; + } + + findFirst(key, value) { + for (var i, cnt = this.items.length; i < cnt; i++) { + if (this.items[i].get(key) === value) { + return i + this.startIndex; + } + } + return -1; + } + +} + +Object.assign( + PageLoader.prototype, + LoadMethods +); + +export default PageLoader; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/quicklogin/QuickLogin.js b/ui/src/phaser3-rex-plugins/plugins/parse/quicklogin/QuickLogin.js new file mode 100644 index 000000000..72169f56f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/quicklogin/QuickLogin.js @@ -0,0 +1,23 @@ +var QuickLogin = function (userName, password) { + return Parse.User.logOut() // // Log-out first + .then(function () { + return Parse.User.logIn(userName, password); // Try login + }) + .catch(function () { // Login fail, try sign-up, then login again + return SignUpThenLogin(userName, password); + }) +} + +var SignUpThenLogin = function (userName, password) { + var user = new Parse.User(); + user + .set('username', userName) + .set('password', password); + + return user.signUp() + .then(function () { // Sign up success, try login again + return Parse.User.logIn(userName, password); + }) +} + +export default QuickLogin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/utils/DataToItem.js b/ui/src/phaser3-rex-plugins/plugins/parse/utils/DataToItem.js new file mode 100644 index 000000000..938d86f3e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/utils/DataToItem.js @@ -0,0 +1,9 @@ +var DataToItem = function (data, itemClass, item) { + if (!item) { + item = new itemClass(); + } + item.set(data); + return item; +} + +export default DataToItem; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/utils/InitialTable.js b/ui/src/phaser3-rex-plugins/plugins/parse/utils/InitialTable.js new file mode 100644 index 000000000..94c149f9d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/utils/InitialTable.js @@ -0,0 +1,8 @@ +var InitialTable = function (item) { + return item.save() + .then(function (result) { + return result.destroy(); + }) +} + +export default InitialTable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/utils/SetOwnerAccessMode.js b/ui/src/phaser3-rex-plugins/plugins/parse/utils/SetOwnerAccessMode.js new file mode 100644 index 000000000..f73133a89 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/utils/SetOwnerAccessMode.js @@ -0,0 +1,21 @@ +var SetOwnerAccessMode = function (item, ownerRead, ownerWrite) { + if (!ownerRead && !ownerWrite) { + return item; + } + var currentUser = Parse.User.current(); + if (!currentUser) { + return item; + } + var acl = new Parse.ACL(currentUser); + if (!ownerWrite) { + acl.setPublicWriteAccess(true); + } + + if (!ownerRead) { + acl.setPublicReadAccess(true); + } + item.setACL(acl); + return item; +} + +export default SetOwnerAccessMode \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/utils/preload/LoaderCallback.js b/ui/src/phaser3-rex-plugins/plugins/parse/utils/preload/LoaderCallback.js new file mode 100644 index 000000000..8f6c59f1f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/utils/preload/LoaderCallback.js @@ -0,0 +1,19 @@ +import Preload from './Preload.js'; +import AwaitFile from '../../../loader/awaitloader/AwaitFile.js'; + +const LoaderCallback = function (url) { + var callback = function (successCallback, failureCallback) { + return Preload(url) + .then(function () { + setTimeout(successCallback, 0); + }) + .catch(failureCallback) + } + + this.addFile(new AwaitFile(this, { + config: { callback: callback } + })); + return this; +} + +export default LoaderCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/utils/preload/Preload.js b/ui/src/phaser3-rex-plugins/plugins/parse/utils/preload/Preload.js new file mode 100644 index 000000000..f6a8139e7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/utils/preload/Preload.js @@ -0,0 +1,14 @@ +import LoadScriptPromise from '../../../utils/loader/LoadScriptPromise.js'; + +const VERSION = '2.11.0'; +const CDNURL = `https://npmcdn.com/parse@${VERSION}/dist/parse.min.js`; + +var Preload = function (url) { + if (url === undefined) { + url = CDNURL; + } + + return LoadScriptPromise(url); +} + +export default Preload; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/utils/query/Delete.js b/ui/src/phaser3-rex-plugins/plugins/parse/utils/query/Delete.js new file mode 100644 index 000000000..3857ee727 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/utils/query/Delete.js @@ -0,0 +1,15 @@ +import Load from './Load.js'; + +var Delete = function (query) { + query.select('id'); + + return Load(query) + .then(function (items) { + if (items.length === 0) { + return Promise.resolve(); + } + return Parse.Object.destroyAll(items); + }); +} + +export default Delete; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/utils/query/FindFirst.js b/ui/src/phaser3-rex-plugins/plugins/parse/utils/query/FindFirst.js new file mode 100644 index 000000000..edaae071a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/utils/query/FindFirst.js @@ -0,0 +1,29 @@ +import Query from './Query'; + +var FindFirst = function (query, testCallback) { + var out = { + item: undefined, + index: undefined + } + var startIndex = 0; + return Query({ + query: query, + forEachPageCallback: function (items) { + var item; + for (var i = 0, cnt = items.length; i < cnt; i++) { + item = items[i]; + if (testCallback(item)) { + out.item = item; + out.index = startIndex + i; + return true; + } + } + startIndex += items.length; + }, + resolveCallback: function () { + return out; + } + }); +} + +export default FindFirst; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/utils/query/Load.js b/ui/src/phaser3-rex-plugins/plugins/parse/utils/query/Load.js new file mode 100644 index 000000000..973843f29 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/utils/query/Load.js @@ -0,0 +1,17 @@ +import Query from './Query.js'; + +var Load = function (query, startIndex, totalLines) { + var out = []; + return Query({ + query: query, + startIndex: startIndex, + totalLines: totalLines, + forEachPageCallback: function (items) { + out.push(...items); + }, + resolveCallback: function () { + return out; + } + }) +}; +export default Load; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/parse/utils/query/Query.js b/ui/src/phaser3-rex-plugins/plugins/parse/utils/query/Query.js new file mode 100644 index 000000000..eabf7bbda --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/parse/utils/query/Query.js @@ -0,0 +1,39 @@ +var Query = function (config) { + if (config.startIndex === undefined) { + config.startIndex = 0; + } + if (config.totalLines === undefined) { + config.totalLines = Infinity; + } + if (config.linesPerPage === undefined) { + config.linesPerPage = 1000; + } + config.remainderLines = config.totalLines; + + return QueryNextPage(config); +}; + +var QueryNextPage = function (config) { + var query = config.query; + var lineCount = Math.min(config.remainderLines, config.linesPerPage); + config.remainderLines -= lineCount; + return query.skip(config.startIndex).limit(lineCount).find() + .then(function (items) { + var done = (config.remainderLines === 0) || (items.length < lineCount); // Is last page + if (config.forEachPageCallback) { + done |= !!config.forEachPageCallback(items); + } + + if (done) { + var out; + if (config.resolveCallback) { + out = config.resolveCallback(); + } + return Promise.resolve(out); + } else { + config.startIndex += items.length; + return QueryNextPage(config); + } + }) +} +export default Query; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/particlesalongbounds-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/particlesalongbounds-plugin.d.ts new file mode 100644 index 000000000..154204091 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/particlesalongbounds-plugin.d.ts @@ -0,0 +1,5 @@ +import ParticlesAlongBounds from './particlesalongbounds'; + +export default class ParticlesAlongBoundsPlugin extends Phaser.Plugins.BasePlugin { + startEffect: typeof ParticlesAlongBounds; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/particlesalongbounds-plugin.js b/ui/src/phaser3-rex-plugins/plugins/particlesalongbounds-plugin.js new file mode 100644 index 000000000..5c3523692 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/particlesalongbounds-plugin.js @@ -0,0 +1,18 @@ +import ParticlesAlongBounds from './particlesalongbounds.js' + +class ParticlesAlongBoundsPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + startEffect(gamObject, config) { + return ParticlesAlongBounds(gamObject, config); + } +} + +export default ParticlesAlongBoundsPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/particlesalongbounds.d.ts b/ui/src/phaser3-rex-plugins/plugins/particlesalongbounds.d.ts new file mode 100644 index 000000000..323499a82 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/particlesalongbounds.d.ts @@ -0,0 +1,2 @@ +import ParticlesAlongBounds from './behaviors/particlesalongbounds/ParticlesAlongBounds'; +export default ParticlesAlongBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/particlesalongbounds.js b/ui/src/phaser3-rex-plugins/plugins/particlesalongbounds.js new file mode 100644 index 000000000..5dfeb7837 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/particlesalongbounds.js @@ -0,0 +1,2 @@ +import ParticlesAlongBounds from './behaviors/particlesalongbounds/ParticlesAlongBounds.js'; +export default ParticlesAlongBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pathfollower-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/pathfollower-plugin.d.ts new file mode 100644 index 000000000..cbe8b7098 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pathfollower-plugin.d.ts @@ -0,0 +1,9 @@ +import PathFollower from './pathfollower'; + +export default class PathFollowerPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: PathFollower.IConfig + ): PathFollower; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pathfollower-plugin.js b/ui/src/phaser3-rex-plugins/plugins/pathfollower-plugin.js new file mode 100644 index 000000000..152806e9d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pathfollower-plugin.js @@ -0,0 +1,20 @@ +import PathFollower from './pathfollower.js'; + +class PathFollowerPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new PathFollower(gameObject, config); + } + +} + +export default PathFollowerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pathfollower.d.ts b/ui/src/phaser3-rex-plugins/plugins/pathfollower.d.ts new file mode 100644 index 000000000..971d0e58f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pathfollower.d.ts @@ -0,0 +1,2 @@ +import PathFollower from './behaviors/pathfollower/PathFollower'; +export default PathFollower; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pathfollower.js b/ui/src/phaser3-rex-plugins/plugins/pathfollower.js new file mode 100644 index 000000000..7f5cf83ed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pathfollower.js @@ -0,0 +1,2 @@ +import PathFollower from './behaviors/pathfollower/PathFollower.js'; +export default PathFollower; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/perlin-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/perlin-plugin.d.ts new file mode 100644 index 000000000..6be007ee1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/perlin-plugin.d.ts @@ -0,0 +1,6 @@ +import Perlin from './perlin'; + +export default class PerlinPlugin extends Phaser.Plugins.BasePlugin { + add(seed?: number): Perlin; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/perlin-plugin.js b/ui/src/phaser3-rex-plugins/plugins/perlin-plugin.js new file mode 100644 index 000000000..203194f62 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/perlin-plugin.js @@ -0,0 +1,20 @@ +import Perlin from './perlin.js'; + +class PerlinPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(seed) { + return new Perlin(seed); + } + +} + +export default PerlinPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/perlin.d.ts b/ui/src/phaser3-rex-plugins/plugins/perlin.d.ts new file mode 100644 index 000000000..4c2d7144b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/perlin.d.ts @@ -0,0 +1,2 @@ +import Perlin from './utils/math/noise/Perlin'; +export default Perlin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/perlin.js b/ui/src/phaser3-rex-plugins/plugins/perlin.js new file mode 100644 index 000000000..de0156982 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/perlin.js @@ -0,0 +1,2 @@ +import Perlin from './utils/math/noise/Perlin.js'; +export default Perlin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/perlingrivatywell-plugin.js b/ui/src/phaser3-rex-plugins/plugins/perlingrivatywell-plugin.js new file mode 100644 index 000000000..0a6907336 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/perlingrivatywell-plugin.js @@ -0,0 +1,20 @@ +import PerlinGrivatyWell from './perlingrivatywell.js'; + +class PerlinGrivatyWellPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new PerlinGrivatyWell(config); + } + +} + +export default PerlinGrivatyWellPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/perlingrivatywell.js b/ui/src/phaser3-rex-plugins/plugins/perlingrivatywell.js new file mode 100644 index 000000000..bae99f5f5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/perlingrivatywell.js @@ -0,0 +1,2 @@ +import PerlinGrivatyWell from './behaviors/perlingrivatywell/PerlinGrivatyWell.js'; +export default PerlinGrivatyWell; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/persistenceeffect-plugin.js b/ui/src/phaser3-rex-plugins/plugins/persistenceeffect-plugin.js new file mode 100644 index 000000000..41520c35e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/persistenceeffect-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/blitter/persistenceeffect/Factory.js'; +import Creator from './gameobjects/blitter/persistenceeffect/Creator.js'; +import PersistenceEffect from './gameobjects/blitter/persistenceeffect/PersistenceEffect.js'; +import SetValue from './utils/object/SetValue.js'; + +class PersistenceEffectPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexPersistenceEffect', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.PersistenceEffect', PersistenceEffect); + +export default PersistenceEffectPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/persistenceeffect.js b/ui/src/phaser3-rex-plugins/plugins/persistenceeffect.js new file mode 100644 index 000000000..9b16d5a8e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/persistenceeffect.js @@ -0,0 +1,2 @@ +import PersistenceEffect from './gameobjects/blitter/persistenceeffect/PersistenceEffect.js'; +export default PersistenceEffect; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/perspectiveimage-plugin.js b/ui/src/phaser3-rex-plugins/plugins/perspectiveimage-plugin.js new file mode 100644 index 000000000..f9909c984 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/perspectiveimage-plugin.js @@ -0,0 +1,62 @@ +import PerspectiveImageFactory from './gameobjects/mesh/perspective/image/Factory.js'; +import PerspectiveImageCreator from './gameobjects/mesh/perspective/image/Creator.js'; +import PerspectiveImage from './gameobjects/mesh/perspective/image/Image.js'; + +import PerspectiveRenderTextureFactory from './gameobjects/mesh/perspective/rendertexture/Factory.js'; +import PerspectiveRenderTextureCreator from './gameobjects/mesh/perspective/rendertexture/Creator.js'; +import PerspectiveRenderTexture from './gameobjects/mesh/perspective/rendertexture/RenderTexture.js'; + +import PerspectiveSpriteFactory from './gameobjects/mesh/perspective/sprite/Factory.js'; +import PerspectiveSpriteCreator from './gameobjects/mesh/perspective/sprite/Creator.js'; +import PerspectiveSprite from './gameobjects/mesh/perspective/sprite/Sprite.js'; + +import PerspectiveCardFactory from './gameobjects/mesh/perspective/card/Factory.js'; +import PerspectiveCardCreator from './gameobjects/mesh/perspective/card/Creator.js'; +import PerspectiveCard from './gameobjects/mesh/perspective/card/Card.js'; + +import PerspectiveCarouselFactory from './gameobjects/mesh/perspective/carousel/Factory.js'; +import PerspectiveCarouselCreator from './gameobjects/mesh/perspective/carousel/Creator.js'; +import PerspectiveCarousel from './gameobjects/mesh/perspective/carousel/Carousel.js'; + +import PerspectiveImageCarouselFactory from './gameobjects/mesh/perspective/imagecarousel/Factory'; +import PerspectiveImageCarouselCreator from './gameobjects/mesh/perspective/imagecarousel/Creator.js'; +import PerspectiveImageCarousel from './gameobjects/mesh/perspective/imagecarousel/ImageCarousel.js'; + +import ContainerPerspective from './behaviors/containerperspective/ContainerPerspective.js'; + +import SetValue from './utils/object/SetValue.js'; + +class PerspectiveImagePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexPerspectiveImage', PerspectiveImageFactory, PerspectiveImageCreator); + pluginManager.registerGameObject('rexPerspectiveRenderTexture', PerspectiveRenderTextureFactory, PerspectiveRenderTextureCreator); + pluginManager.registerGameObject('rexPerspectiveSprite', PerspectiveSpriteFactory, PerspectiveSpriteCreator); + pluginManager.registerGameObject('rexPerspectiveCard', PerspectiveCardFactory, PerspectiveCardCreator); + pluginManager.registerGameObject('rexPerspectiveCarousel', PerspectiveCarouselFactory, PerspectiveCarouselCreator); + pluginManager.registerGameObject('rexPerspectiveImageCarousel', PerspectiveImageCarouselFactory, PerspectiveImageCarouselCreator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + addContainerPerspective(parentContainer, config) { + return new ContainerPerspective(parentContainer, config); + } +} + +SetValue(window, 'RexPlugins.GameObjects.PerspectiveImage', PerspectiveImage); +SetValue(window, 'RexPlugins.GameObjects.PerspectiveRenderTexture', PerspectiveRenderTexture); +SetValue(window, 'RexPlugins.GameObjects.PerspectiveSprite', PerspectiveSprite); +SetValue(window, 'RexPlugins.GameObjects.PerspectiveCard', PerspectiveCard); +SetValue(window, 'RexPlugins.GameObjects.PerspectiveCarousel', PerspectiveCarousel); +SetValue(window, 'RexPlugins.GameObjects.PerspectiveImageCarousel', PerspectiveImageCarousel); + +SetValue(window, 'RexPlugins.GameObjects.ContainerPerspective', ContainerPerspective); + +export default PerspectiveImagePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/perspectiveimage.d.ts b/ui/src/phaser3-rex-plugins/plugins/perspectiveimage.d.ts new file mode 100644 index 000000000..f20de04fd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/perspectiveimage.d.ts @@ -0,0 +1,17 @@ +import PerspectiveImage from './gameobjects/mesh/perspective/image/Image'; +import PerspectiveRenderTexture from './gameobjects/mesh/perspective/rendertexture/RenderTexture'; +import PerspectiveSprite from './gameobjects/mesh/perspective/sprite/Sprite'; +import PerspectiveCard from './gameobjects/mesh/perspective/card/Card'; +import PerspectiveCarousel from './gameobjects/mesh/perspective/carousel/Carousel'; +import PerspectiveImageCarousel from './gameobjects/mesh/perspective/imagecarousel/ImageCarousel'; +import ContainerPerspective from './behaviors/containerperspective/ContainerPerspective'; + +export { + PerspectiveImage, + PerspectiveRenderTexture, + PerspectiveSprite, + PerspectiveCard, + PerspectiveCarousel, + PerspectiveImageCarousel, + ContainerPerspective +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/perspectiveimage.js b/ui/src/phaser3-rex-plugins/plugins/perspectiveimage.js new file mode 100644 index 000000000..e4286600b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/perspectiveimage.js @@ -0,0 +1,17 @@ +import PerspectiveImage from './gameobjects/mesh/perspective/image/Image.js'; +import PerspectiveRenderTexture from './gameobjects/mesh/perspective/rendertexture/RenderTexture.js'; +import PerspectiveSprite from './gameobjects/mesh/perspective/sprite/Sprite.js'; +import PerspectiveCard from './gameobjects/mesh/perspective/card/Card.js'; +import PerspectiveCarousel from './gameobjects/mesh/perspective/carousel/Carousel.js'; +import PerspectiveImageCarousel from './gameobjects/mesh/perspective/imagecarousel/ImageCarousel.js'; +import ContainerPerspective from './behaviors/containerperspective/ContainerPerspective.js'; + +export { + PerspectiveImage, + PerspectiveRenderTexture, + PerspectiveSprite, + PerspectiveCard, + PerspectiveCarousel, + PerspectiveImageCarousel, + ContainerPerspective +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pinch-plugin.js b/ui/src/phaser3-rex-plugins/plugins/pinch-plugin.js new file mode 100644 index 000000000..5fbd7fe3b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pinch-plugin.js @@ -0,0 +1,20 @@ +import Pinch from './pinch.js'; + +class PinchPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new Pinch(scene, config); + } + +} + +export default PinchPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pinch.js b/ui/src/phaser3-rex-plugins/plugins/pinch.js new file mode 100644 index 000000000..0ab9536b5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pinch.js @@ -0,0 +1,2 @@ +import Pinch from './input/gestures/pinch/Pinch.js'; +export default Pinch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pixelationpipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/pixelationpipeline-plugin.d.ts new file mode 100644 index 000000000..d3a0c23f1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pixelationpipeline-plugin.d.ts @@ -0,0 +1,32 @@ +// import * as Phaser from 'phaser'; +import PixelationPostFxPipeline from './pixelationpipeline'; + +export default PixelationPipelinePlugin; + +declare namespace PixelationPipelinePlugin { + + interface IConfig { + pixelWidth?: number, + pixelHeight?: number, + + name?: string, + } + +} + +declare class PixelationPipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: PixelationPipelinePlugin.IConfig + ): PixelationPostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): PixelationPostFxPipeline | PixelationPostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pixelationpipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/pixelationpipeline-plugin.js new file mode 100644 index 000000000..a42db80ed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pixelationpipeline-plugin.js @@ -0,0 +1,14 @@ +import PixelationPostFxPipeline from './pixelationpipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class PixelationPipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(PixelationPostFxPipeline, 'rexPixelationPostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.PixelationPostFx', PixelationPostFxPipeline); + +export default PixelationPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pixelationpipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/pixelationpipeline.d.ts new file mode 100644 index 000000000..dccf7d8b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pixelationpipeline.d.ts @@ -0,0 +1,2 @@ +import PixelationPostFxPipeline from './shaders/pixelation/PixelationPostFxPipeline'; +export default PixelationPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pixelationpipeline.js b/ui/src/phaser3-rex-plugins/plugins/pixelationpipeline.js new file mode 100644 index 000000000..6cb1af1d9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pixelationpipeline.js @@ -0,0 +1,2 @@ +import PixelationPostFxPipeline from './shaders/pixelation/PixelationPostFxPipeline.js'; +export default PixelationPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pngappender-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/pngappender-plugin.d.ts new file mode 100644 index 000000000..184dc0249 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pngappender-plugin.d.ts @@ -0,0 +1,7 @@ +import AppendData from './data/pngappender/AppendData'; +import ExtractData from './data/pngappender/ExtractData'; + +export default class PNGAppenderPlugin extends Phaser.Plugins.BasePlugin { + append: typeof AppendData; + extract: typeof ExtractData; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pngappender-plugin.js b/ui/src/phaser3-rex-plugins/plugins/pngappender-plugin.js new file mode 100644 index 000000000..de15bd40e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pngappender-plugin.js @@ -0,0 +1,20 @@ +import PNGAppender from './pngappender.js'; + +class PNGAppenderPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +// mixin +Object.assign( + PNGAppenderPlugin.prototype, + PNGAppender +); + +export default PNGAppenderPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pngappender.d.ts b/ui/src/phaser3-rex-plugins/plugins/pngappender.d.ts new file mode 100644 index 000000000..c00b5eed3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pngappender.d.ts @@ -0,0 +1,9 @@ +import AppendData from './data/pngappender/AppendData'; +import ExtractData from './data/pngappender/ExtractData'; + +declare var Methods: { + append: typeof AppendData, + extract: typeof ExtractData +}; + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pngappender.js b/ui/src/phaser3-rex-plugins/plugins/pngappender.js new file mode 100644 index 000000000..bb7d46d1e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pngappender.js @@ -0,0 +1,7 @@ +import AppendData from './data/pngappender/AppendData.js'; +import ExtractData from './data/pngappender/ExtractData.js'; + +export default { + append: AppendData, + extract: ExtractData +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/polarcoordinate-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/polarcoordinate-plugin.d.ts new file mode 100644 index 000000000..c307e77a6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/polarcoordinate-plugin.d.ts @@ -0,0 +1,5 @@ +import AddPolarCoordinateProperties from './polarcoordinate'; + +export default class PolarCoordinatePlugin extends Phaser.Plugins.BasePlugin { + add: typeof AddPolarCoordinateProperties; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/polarcoordinate-plugin.js b/ui/src/phaser3-rex-plugins/plugins/polarcoordinate-plugin.js new file mode 100644 index 000000000..2afe4e86a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/polarcoordinate-plugin.js @@ -0,0 +1,19 @@ +import AddPolarCoordinateProperties from './polarcoordinate.js'; + +class PolarCoordinatePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, ox, oy, rotation, radius) { + return AddPolarCoordinateProperties(gameObject, ox, oy, rotation, radius) + } +} + +export default PolarCoordinatePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/polarcoordinate.d.ts b/ui/src/phaser3-rex-plugins/plugins/polarcoordinate.d.ts new file mode 100644 index 000000000..01ff8ee7d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/polarcoordinate.d.ts @@ -0,0 +1,2 @@ +import AddPolarCoordinateProperties from './behaviors/polarcoordinate/AddPolarCoordinateProperties'; +export default AddPolarCoordinateProperties; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/polarcoordinate.js b/ui/src/phaser3-rex-plugins/plugins/polarcoordinate.js new file mode 100644 index 000000000..b3c5c8dcc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/polarcoordinate.js @@ -0,0 +1,2 @@ +import AddPolarCoordinateProperties from './behaviors/polarcoordinate/AddPolarCoordinateProperties.js'; +export default AddPolarCoordinateProperties; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/pool.js b/ui/src/phaser3-rex-plugins/plugins/pool.js new file mode 100644 index 000000000..95e51ce56 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/pool.js @@ -0,0 +1,2 @@ +import Pool from './utils/struct/Stack.js'; +export default Pool; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/popup.d.ts b/ui/src/phaser3-rex-plugins/plugins/popup.d.ts new file mode 100644 index 000000000..01a03408f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/popup.d.ts @@ -0,0 +1,2 @@ +import PopUp from './behaviors/scale/PopUp'; +export default PopUp; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/popup.js b/ui/src/phaser3-rex-plugins/plugins/popup.js new file mode 100644 index 000000000..5564cef28 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/popup.js @@ -0,0 +1,2 @@ +import PopUp from './behaviors/scale/PopUp.js'; +export default PopUp; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/postfxpipelinebehavior.js b/ui/src/phaser3-rex-plugins/plugins/postfxpipelinebehavior.js new file mode 100644 index 000000000..14645617e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/postfxpipelinebehavior.js @@ -0,0 +1,2 @@ +import PostFxPipelineBehavior from './utils/renderer/postfxpipeline/BasePostFxPipelineBehavior.js'; +export default PostFxPipelineBehavior; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/quadimage-plugin.js b/ui/src/phaser3-rex-plugins/plugins/quadimage-plugin.js new file mode 100644 index 000000000..f2605dba3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/quadimage-plugin.js @@ -0,0 +1,51 @@ +import QuadImageFactory from './gameobjects/mesh/quad/image/Factory.js'; +import QuadImageCreator from './gameobjects/mesh/quad/image/Creator.js'; +import QuadImage from './gameobjects/mesh/quad/image/Image.js'; + +import QuadRenderTextureFactory from './gameobjects/mesh/quad/rendertexture/Factory.js'; +import QuadRenderTextureCreator from './gameobjects/mesh/quad/rendertexture/Creator.js'; +import QuadRenderTexture from './gameobjects/mesh/quad/rendertexture/RenderTexture.js'; + +import SkewImageFactory from './gameobjects/mesh/quad/skewimage/Factory'; +import SkewImageCreator from './gameobjects/mesh/quad/skewimage/Creator.js'; +import SkewImage from './gameobjects/mesh/quad/skewimage/SkewImage.js'; + +import SkewRenderTextureFactory from './gameobjects/mesh/quad/skewrendertexture/Factory.js'; +import SkewRenderTextureCreator from './gameobjects/mesh/quad/skewrendertexture/Creator.js'; +import SkewRenderTexture from './gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture.js'; + +import ContainerSkew from './behaviors/containerskew/ContainerSkew.js'; + +import SetValue from './utils/object/SetValue.js'; + +class QuadImagePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexQuadImage', QuadImageFactory, QuadImageCreator); + pluginManager.registerGameObject('rexQuadRenderTexture', QuadRenderTextureFactory, QuadRenderTextureCreator); + + pluginManager.registerGameObject('rexSkewImage', SkewImageFactory, SkewImageCreator); + pluginManager.registerGameObject('rexSkewRenderTexture', SkewRenderTextureFactory, SkewRenderTextureCreator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + addContainerSkew(parentContainer, config) { + return new ContainerSkew(parentContainer, config); + } +} + +SetValue(window, 'RexPlugins.GameObjects.QuadImage', QuadImage); +SetValue(window, 'RexPlugins.GameObjects.QuadRenderTexture', QuadRenderTexture); +SetValue(window, 'RexPlugins.GameObjects.SkewImage', SkewImage); +SetValue(window, 'RexPlugins.GameObjects.SkewRenderTexture', SkewRenderTexture); + +SetValue(window, 'RexPlugins.GameObjects.ContainerSkew', ContainerSkew); + +export default QuadImagePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/quadimage.d.ts b/ui/src/phaser3-rex-plugins/plugins/quadimage.d.ts new file mode 100644 index 000000000..17c6c1298 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/quadimage.d.ts @@ -0,0 +1,13 @@ +import QuadImage from './gameobjects/mesh/quad/image/Image'; +import QuadRenderTexture from './gameobjects/mesh/quad/rendertexture/RenderTexture'; +import SkewImage from './gameobjects/mesh/quad/skewimage/SkewImage'; +import SkewRenderTexture from './gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture'; +import ContainerSkew from './behaviors/containerskew/ContainerSkew'; + +export { + QuadImage, + QuadRenderTexture, + SkewImage, + SkewRenderTexture, + ContainerSkew +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/quadimage.js b/ui/src/phaser3-rex-plugins/plugins/quadimage.js new file mode 100644 index 000000000..375544b7e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/quadimage.js @@ -0,0 +1,13 @@ +import QuadImage from './gameobjects/mesh/quad/image/Image.js'; +import QuadRenderTexture from './gameobjects/mesh/quad/rendertexture/RenderTexture.js'; +import SkewImage from './gameobjects/mesh/quad/skewimage/SkewImage.js'; +import SkewRenderTexture from './gameobjects/mesh/quad/skewrendertexture/SkewRenderTexture.js'; +import ContainerSkew from './behaviors/containerskew/ContainerSkew.js'; + +export { + QuadImage, + QuadRenderTexture, + SkewImage, + SkewRenderTexture, + ContainerSkew +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/quest-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/quest-plugin.d.ts new file mode 100644 index 000000000..60ca6c951 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/quest-plugin.d.ts @@ -0,0 +1,8 @@ +import QuestionManager from './quest'; + +export default class QuestPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: QuestionManager.IConfig + ): QuestionManager; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/quest-plugin.js b/ui/src/phaser3-rex-plugins/plugins/quest-plugin.js new file mode 100644 index 000000000..2ccb49d8c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/quest-plugin.js @@ -0,0 +1,18 @@ +import QuestionManager from './quest.js'; + +class QuestPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new QuestionManager(config); + } +} + +export default QuestPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/quest.d.ts b/ui/src/phaser3-rex-plugins/plugins/quest.d.ts new file mode 100644 index 000000000..6d15a9cb1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/quest.d.ts @@ -0,0 +1,2 @@ +import QuestionManager from './logic/quest/questions/QuestionManager'; +export default QuestionManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/quest.js b/ui/src/phaser3-rex-plugins/plugins/quest.js new file mode 100644 index 000000000..9c25a338d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/quest.js @@ -0,0 +1,2 @@ +import QuestionManager from './logic/quest/questions/QuestionManager.js'; +export default QuestionManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/randomplace-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/randomplace-plugin.d.ts new file mode 100644 index 000000000..793265897 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/randomplace-plugin.d.ts @@ -0,0 +1,6 @@ +import RandomPlace from './randomplace'; + +export default class RandomPlacePlugin extends Phaser.Plugins.BasePlugin { + randomPlace: typeof RandomPlace + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/randomplace-plugin.js b/ui/src/phaser3-rex-plugins/plugins/randomplace-plugin.js new file mode 100644 index 000000000..31931098c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/randomplace-plugin.js @@ -0,0 +1,19 @@ +import RandomPlace from './randomplace.js'; + +class RandomPlacePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + randomPlace(items, options) { + return RandomPlace(items, options); + } +} + +export default RandomPlacePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/randomplace.d.ts b/ui/src/phaser3-rex-plugins/plugins/randomplace.d.ts new file mode 100644 index 000000000..61c5dec12 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/randomplace.d.ts @@ -0,0 +1,2 @@ +import RandomPlace from './actions/RandomPlace'; +export default RandomPlace; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/randomplace.js b/ui/src/phaser3-rex-plugins/plugins/randomplace.js new file mode 100644 index 000000000..499ebbdea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/randomplace.js @@ -0,0 +1,2 @@ +import RandomPlace from './actions/RandomPlace.js'; +export default RandomPlace; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/raycaster-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/raycaster-plugin.d.ts new file mode 100644 index 000000000..31eff7f42 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/raycaster-plugin.d.ts @@ -0,0 +1,8 @@ +import Raycaster from './raycaster'; + +export default class RaycasterPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: Raycaster.IConfig + ): Raycaster; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/raycaster-plugin.js b/ui/src/phaser3-rex-plugins/plugins/raycaster-plugin.js new file mode 100644 index 000000000..9d8955435 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/raycaster-plugin.js @@ -0,0 +1,19 @@ +import Raycaster from './raycaster.js'; + +class RaycasterPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new Raycaster(config); + } +} + +export default RaycasterPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/raycaster.d.ts b/ui/src/phaser3-rex-plugins/plugins/raycaster.d.ts new file mode 100644 index 000000000..0c33a6883 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/raycaster.d.ts @@ -0,0 +1,2 @@ +import Raycaster from './math/raycaster/Raycaster'; +export default Raycaster; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/raycaster.js b/ui/src/phaser3-rex-plugins/plugins/raycaster.js new file mode 100644 index 000000000..54f68d5f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/raycaster.js @@ -0,0 +1,2 @@ +import Raycaster from './math/raycaster/Raycaster.js'; +export default Raycaster; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/realtimetimers-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/realtimetimers-plugin.d.ts new file mode 100644 index 000000000..57eead832 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/realtimetimers-plugin.d.ts @@ -0,0 +1,8 @@ +import RealTimeTimers from './realtimetimers'; + +export default class RealTimeTimersPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: RealTimeTimers.IConfig + ): RealTimeTimers; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/realtimetimers-plugin.js b/ui/src/phaser3-rex-plugins/plugins/realtimetimers-plugin.js new file mode 100644 index 000000000..51fd0786b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/realtimetimers-plugin.js @@ -0,0 +1,20 @@ +import RealTimeTimers from './realtimetimers.js'; + +class RealTimeTimersPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new RealTimeTimers(config); + } + +} + +export default RealTimeTimersPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/realtimetimers.d.ts b/ui/src/phaser3-rex-plugins/plugins/realtimetimers.d.ts new file mode 100644 index 000000000..0f82a113a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/realtimetimers.d.ts @@ -0,0 +1,2 @@ +import RealTimeTimers from './time/realtimetimers/RealTimeTimers'; +export default RealTimeTimers; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/realtimetimers.js b/ui/src/phaser3-rex-plugins/plugins/realtimetimers.js new file mode 100644 index 000000000..d24e32af4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/realtimetimers.js @@ -0,0 +1,2 @@ +import RealTimeTimers from './time/realtimetimers/RealTimeTimers.js'; +export default RealTimeTimers; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/requestdrag.d.ts b/ui/src/phaser3-rex-plugins/plugins/requestdrag.d.ts new file mode 100644 index 000000000..476b67cfd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/requestdrag.d.ts @@ -0,0 +1,2 @@ +import RequestDrag from './utils/input/RequestDrag'; +export default RequestDrag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/requestdrag.js b/ui/src/phaser3-rex-plugins/plugins/requestdrag.js new file mode 100644 index 000000000..1e401f3d6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/requestdrag.js @@ -0,0 +1,2 @@ +import RequestDrag from './utils/input/RequestDrag.js'; +export default RequestDrag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/restorabledata-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/restorabledata-plugin.d.ts new file mode 100644 index 000000000..856720f9e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/restorabledata-plugin.d.ts @@ -0,0 +1,10 @@ +import DataManager from './restorabledata'; + +export default class DataManagerPlugin extends Phaser.Plugins.BasePlugin { + add( + parent: object, + eventEmitter?: Phaser.Events.EventEmitter, + config?: object + ): DataManager; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/restorabledata-plugin.js b/ui/src/phaser3-rex-plugins/plugins/restorabledata-plugin.js new file mode 100644 index 000000000..876e1aa40 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/restorabledata-plugin.js @@ -0,0 +1,20 @@ +import DataManager from './restorabledata.js'; + +class DataManagerPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(parent, eventEmitter, config) { + return new DataManager(parent, eventEmitter, config); + } + +} + +export default DataManagerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/restorabledata.d.ts b/ui/src/phaser3-rex-plugins/plugins/restorabledata.d.ts new file mode 100644 index 000000000..b2849901b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/restorabledata.d.ts @@ -0,0 +1,2 @@ +import DataManager from './data/restorabledata/DataManager'; +export default DataManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/restorabledata.js b/ui/src/phaser3-rex-plugins/plugins/restorabledata.js new file mode 100644 index 000000000..815cd346c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/restorabledata.js @@ -0,0 +1,2 @@ +import DataManager from './data/restorabledata/DataManager.js'; +export default DataManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/rhombus-plugin.js b/ui/src/phaser3-rex-plugins/plugins/rhombus-plugin.js new file mode 100644 index 000000000..412ea949c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/rhombus-plugin.js @@ -0,0 +1,19 @@ +import Rhombus from './rhombus.js'; + +class RhombusPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(x, y, width, height) { + return new Rhombus(x, y, width, height); + } +} + +export default RhombusPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/rhombus.js b/ui/src/phaser3-rex-plugins/plugins/rhombus.js new file mode 100644 index 000000000..a6fa26bc9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/rhombus.js @@ -0,0 +1,2 @@ +import Rhombus from './geom/rhombus/Rhombus.js'; +export default Rhombus; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/rotate-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/rotate-plugin.d.ts new file mode 100644 index 000000000..609db44d2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/rotate-plugin.d.ts @@ -0,0 +1,9 @@ +import Rotate from './rotate'; + +export default class RotatePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Rotate.IConfig + ): Rotate; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/rotate-plugin.js b/ui/src/phaser3-rex-plugins/plugins/rotate-plugin.js new file mode 100644 index 000000000..0c2fc7321 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/rotate-plugin.js @@ -0,0 +1,19 @@ +import Rotate from './rotate.js'; + +class RotatePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Rotate(gameObject, config); + } +} + +export default RotatePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/rotate.d.ts b/ui/src/phaser3-rex-plugins/plugins/rotate.d.ts new file mode 100644 index 000000000..ce7de8eb7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/rotate.d.ts @@ -0,0 +1,2 @@ +import Rotate from './behaviors/rotate/Rotate'; +export default Rotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/rotate.js b/ui/src/phaser3-rex-plugins/plugins/rotate.js new file mode 100644 index 000000000..d1b00e1cf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/rotate.js @@ -0,0 +1,2 @@ +import Rotate from './behaviors/rotate/Rotate.js'; +export default Rotate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/rotateto-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/rotateto-plugin.d.ts new file mode 100644 index 000000000..96a25dd87 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/rotateto-plugin.d.ts @@ -0,0 +1,9 @@ +import RotateTo from './rotateto'; + +export default class RotateToPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: RotateTo.IConfig + ): RotateTo; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/rotateto-plugin.js b/ui/src/phaser3-rex-plugins/plugins/rotateto-plugin.js new file mode 100644 index 000000000..1047bcee5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/rotateto-plugin.js @@ -0,0 +1,19 @@ +import RotateTo from './rotateto.js'; + +class RotateToPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new RotateTo(gameObject, config); + } +} + +export default RotateToPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/rotateto.d.ts b/ui/src/phaser3-rex-plugins/plugins/rotateto.d.ts new file mode 100644 index 000000000..e85fac41b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/rotateto.d.ts @@ -0,0 +1,2 @@ +import RotateTo from './behaviors/rotateto/RotateTo'; +export default RotateTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/rotateto.js b/ui/src/phaser3-rex-plugins/plugins/rotateto.js new file mode 100644 index 000000000..e0c1a6614 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/rotateto.js @@ -0,0 +1,2 @@ +import RotateTo from './behaviors/rotateto/RotateTo.js'; +export default RotateTo; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/roundrectangle-plugin.js b/ui/src/phaser3-rex-plugins/plugins/roundrectangle-plugin.js new file mode 100644 index 000000000..31367fdfc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/roundrectangle-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/shape/roundrectangle/Factory.js'; +import Creator from './gameobjects/shape/roundrectangle/Creator.js'; +import RoundRectangle from './gameobjects/shape/roundrectangle/RoundRectangle.js'; +import SetValue from './utils/object/SetValue.js'; + +class RoundRectanglePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexRoundRectangle', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.RoundRectangle', RoundRectangle); + +export default RoundRectanglePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/roundrectangle.d.ts b/ui/src/phaser3-rex-plugins/plugins/roundrectangle.d.ts new file mode 100644 index 000000000..067fa3453 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/roundrectangle.d.ts @@ -0,0 +1,2 @@ +import RoundRectangle from './gameobjects/shape/roundrectangle/RoundRectangle'; +export default RoundRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/roundrectangle.js b/ui/src/phaser3-rex-plugins/plugins/roundrectangle.js new file mode 100644 index 000000000..7b243b775 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/roundrectangle.js @@ -0,0 +1,2 @@ +import RoundRectangle from './gameobjects/shape/roundrectangle/RoundRectangle.js'; +export default RoundRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/roundrectanglecanvas-plugin.js b/ui/src/phaser3-rex-plugins/plugins/roundrectanglecanvas-plugin.js new file mode 100644 index 000000000..258051d78 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/roundrectanglecanvas-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/canvas/roundrectangle/Factory.js'; +import Creator from './gameobjects/canvas/roundrectangle/Creator.js'; +import RoundRectangle from './gameobjects/canvas/roundrectangle/RoundRectangle.js'; +import SetValue from './utils/object/SetValue.js'; + +class CircleMaskImagePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexRoundRectangleCanvas', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.RoundRectangleCanvas', RoundRectangle); + +export default CircleMaskImagePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/roundrectanglecanvas.d.ts b/ui/src/phaser3-rex-plugins/plugins/roundrectanglecanvas.d.ts new file mode 100644 index 000000000..310b2e112 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/roundrectanglecanvas.d.ts @@ -0,0 +1,2 @@ +import RoundRecrangle from './gameobjects/canvas/roundrectangle/RoundRectangle.js'; +export default RoundRecrangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/roundrectanglecanvas.js b/ui/src/phaser3-rex-plugins/plugins/roundrectanglecanvas.js new file mode 100644 index 000000000..310b2e112 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/roundrectanglecanvas.js @@ -0,0 +1,2 @@ +import RoundRecrangle from './gameobjects/canvas/roundrectangle/RoundRectangle.js'; +export default RoundRecrangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/runcommands-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/runcommands-plugin.d.ts new file mode 100644 index 000000000..58bfaf7ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/runcommands-plugin.d.ts @@ -0,0 +1,10 @@ +import RunCommands from './logic/runcommands/RunCommands'; + +export default class RunCommandsPlugin extends Phaser.Plugins.BasePlugin { + run( + queue: any[], + scope?: object, + config?: RunCommands.IConfig + ): any; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/runcommands-plugin.js b/ui/src/phaser3-rex-plugins/plugins/runcommands-plugin.js new file mode 100644 index 000000000..4362dd1b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/runcommands-plugin.js @@ -0,0 +1,14 @@ +import RunCommands from './runcommands.js'; + +class RunCommandsPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + run(queue, scope, config) { + return RunCommands(queue, scope, config); + } +} + +export default RunCommandsPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/runcommands.d.ts b/ui/src/phaser3-rex-plugins/plugins/runcommands.d.ts new file mode 100644 index 000000000..c500a0149 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/runcommands.d.ts @@ -0,0 +1,2 @@ +import RunCommands from './logic/runcommands/RunCommands'; +export default RunCommands; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/runcommands.js b/ui/src/phaser3-rex-plugins/plugins/runcommands.js new file mode 100644 index 000000000..abf61caaf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/runcommands.js @@ -0,0 +1,2 @@ +import RunCommands from './logic/runcommands/RunCommands.js'; +export default RunCommands; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale-down-destroy.d.ts b/ui/src/phaser3-rex-plugins/plugins/scale-down-destroy.d.ts new file mode 100644 index 000000000..892a6efea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale-down-destroy.d.ts @@ -0,0 +1,2 @@ +import ScaleDownDestroy from './behaviors/scale/ScaleDownDestroy'; +export default ScaleDownDestroy; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale-down-destroy.js b/ui/src/phaser3-rex-plugins/plugins/scale-down-destroy.js new file mode 100644 index 000000000..ff372ebdb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale-down-destroy.js @@ -0,0 +1,2 @@ +import ScaleDownDestroy from './behaviors/scale/ScaleDownDestroy.js'; +export default ScaleDownDestroy; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/scale-plugin.d.ts new file mode 100644 index 000000000..ce05b0c25 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale-plugin.d.ts @@ -0,0 +1,17 @@ +import Scale from './scale'; +import ScaleDown from './behaviors/scale/ScaleDown'; +import ScaleDownDestroy from './scale-down-destroy'; +import PopUp from './popup'; +import Yoyo from './behaviors/scale/Yoyo'; + +export default class ScalePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Scale.IConfig + ): Scale; + + scaleDown: typeof ScaleDown; + scaleDownDestroy: typeof ScaleDownDestroy; + popUp: typeof PopUp; + yoyo: typeof Yoyo; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale-plugin.js b/ui/src/phaser3-rex-plugins/plugins/scale-plugin.js new file mode 100644 index 000000000..e86313f3c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale-plugin.js @@ -0,0 +1,35 @@ +import Scale from './scale.js'; +import ScaleDown from './behaviors/scale/ScaleDown.js'; +import ScaleDownDestroy from './scale-down-destroy.js'; +import Popup from './popup.js'; +import Yoyo from './behaviors/scale/Yoyo.js'; + +class ScalePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Scale(gameObject, config); + } +} + +// mixin +var methods = { + scaleDown: ScaleDown, + scaleDownDestroy: ScaleDownDestroy, + popup: Popup, + yoyo: Yoyo, +} +Object.assign( + ScalePlugin.prototype, + methods +); + +export default ScalePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale.d.ts b/ui/src/phaser3-rex-plugins/plugins/scale.d.ts new file mode 100644 index 000000000..a21bfca80 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale.d.ts @@ -0,0 +1,2 @@ +import Scale from './behaviors/scale/Scale'; +export default Scale; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale.js b/ui/src/phaser3-rex-plugins/plugins/scale.js new file mode 100644 index 000000000..989107701 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale.js @@ -0,0 +1,2 @@ +import Scale from './behaviors/scale/Scale.js'; +export default Scale; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/CheckScaleMode.js b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/CheckScaleMode.js new file mode 100644 index 000000000..40e02c97b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/CheckScaleMode.js @@ -0,0 +1,17 @@ +var WarnCounter = 0; + +var CheckScaleMode = function (scene) { + var scaleManager = scene.sys.scale; + if (scaleManager.scaleMode === Phaser.Scale.RESIZE) { + return true; + } + + // Not RESIZE mode + if (WarnCounter === 0) { + console.warn('Scale outer only works with RESIZE scale mode'); + } + WarnCounter++; + return false; +} + +export default CheckScaleMode; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/GetInnerViewport.js b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/GetInnerViewport.js new file mode 100644 index 000000000..db8f507b2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/GetInnerViewport.js @@ -0,0 +1,14 @@ +const Rectangle = Phaser.Geom.Rectangle; +var GetInnerViewport = function (scaleOuter, out) { + if (out === undefined) { + out = new Rectangle(); + } + + var gameConfig = scaleOuter.scene.game.config; + var width = gameConfig.width, + height = gameConfig.height; + out.setTo(0, 0, width, height); + return out; +} + +export default GetInnerViewport; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/GetOuterViewport.js b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/GetOuterViewport.js new file mode 100644 index 000000000..8934c1db4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/GetOuterViewport.js @@ -0,0 +1,18 @@ +const Rectangle = Phaser.Geom.Rectangle; +var GetOuterViewport = function (scaleOuter, out) { + if (out === undefined) { + out = new Rectangle(); + } + + var scale = 1 / scaleOuter.zoom; + var displaySize = scaleOuter.scene.sys.scale.displaySize; + out.width = displaySize.width * scale; + out.height = displaySize.height * scale; + + var gameConfig = scaleOuter.scene.game.config; + out.centerX = gameConfig.width / 2; + out.centerY = gameConfig.height / 2; + + return out; +} +export default GetOuterViewport; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/GetScaleOuterCameraParameters.js b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/GetScaleOuterCameraParameters.js new file mode 100644 index 000000000..54537db6b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/GetScaleOuterCameraParameters.js @@ -0,0 +1,28 @@ +var GetScaleOutCameraParameters = function (scene, out) { + if (out === undefined) { + out = {}; + } + + var gameConfig = scene.game.config; + var gameWidth = gameConfig.width, + gameHeight = gameConfig.height; + var gameAspectRatio = (gameHeight === 0) ? 1 : gameWidth / gameHeight; + + var displaySize = scene.sys.scale.displaySize; + var displayWidth = displaySize.width, + displayHeight = displaySize.height; + var displayAspectRatio = (displayHeight === 0) ? 1 : displayWidth / displayHeight; + + out.scrollX = (gameWidth - displayWidth) / 2; + out.scrollY = (gameHeight - displayHeight) / 2; + + if (gameAspectRatio < displayAspectRatio) { + out.zoom = (displayHeight / gameHeight); + } else { + out.zoom = (displayWidth / gameWidth) + } + + return out; +} + +export default GetScaleOutCameraParameters; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/ScaleOuter.d.ts b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/ScaleOuter.d.ts new file mode 100644 index 000000000..cc0e4b2d2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/ScaleOuter.d.ts @@ -0,0 +1,30 @@ +export default ScaleOuter; + +declare class ScaleOuter { + constructor( + scene: Phaser.Scene + ); + + destroy(): void; + + stop(): this; + + add( + camera: Phaser.Cameras.Scene2D.BaseCamera + ): this; + + scale(): this; + + readonly scrollX: number; + readonly scrollY: number; + readonly zoom: number; + readonly innerViewport: Phaser.Geom.Rectangle; + readonly outerViewport: Phaser.Geom.Rectangle; + + getShrinkedOuterViewport( + maxRatio: number, + minRatio?: number, + out?: Phaser.Geom.Rectangle | true, + ): Phaser.Geom.Rectangle; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/ScaleOuter.js b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/ScaleOuter.js new file mode 100644 index 000000000..d8c7ae37c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/ScaleOuter.js @@ -0,0 +1,132 @@ +import CheckScaleMode from './CheckScaleMode.js'; +import GetScaleOutCameraParameters from './GetScaleOuterCameraParameters.js'; +import GetInnerViewport from './GetInnerViewport.js'; +import GetOuterViewport from './GetOuterViewport.js'; +import ShrinkSizeByRatio from './ShrinkSizeByRatio.js' + +const Rectangle = Phaser.Geom.Rectangle; +const CopyRectangle = Phaser.Geom.Rectangle.CopyFrom +const SetStruct = Phaser.Structs.Set; + +class ScaleOuter { + constructor(scene) { + this.scene = scene; + // Set gameConfig.scale.mode to Phaser.Scale.RESIZE + + this.cameras = new SetStruct(); + this.scrollX = 0; + this.scrollY = 0; + this.zoom = 1; + + this._innerViewport = undefined; + this._outerViewport = undefined; + this._shrinkOuterViewport = undefined; + + this.boot(); + } + + boot() { + var scene = this.scene; + if (CheckScaleMode(scene)) { + scene.sys.scale.on('resize', this.scale, this); + scene.sys.game.events.once('prestep',this.start, this); + } + + scene.sys.events.on('shutdown', function () { + // cameras of this scene will be destroyed when scene shutdown + this.cameras.clear(); + }, this); + } + + destroy() { + this.stop(); + + this.cameras.clear(); + this.cameras = undefined; + this.scene = undefined; + this._innerViewport = undefined; + this._outerViewport = undefined; + this._shrinkOuterViewport = undefined; + } + + start() { + if (this.cameras.size === 0) { + // Add default camera + this.add(this.scene.sys.cameras.main); + } + + this.scale(); + + return this; + } + + stop() { + var scene = this.scene; + scene.sys.scale.off('resize', this.scale, this); + scene.sys.game.events.off('prestep',this.start, this); + return this; + } + + add(camera) { + this.cameras.set(camera) + this.scale(); + return this; + } + + get innerViewport() { + return this._innerViewport; + } + + get outerViewport() { + return this._outerViewport; + } + + getShrinkedOuterViewport(maxRatio, minRatio, out) { + if (typeof (minRatio) !== 'number') { + out = minRatio; + minRatio = undefined; + } + + if (out === undefined) { + out = new Rectangle(); + } else if (out === true) { + if (this._shrinkOuterViewport === undefined) { + this._shrinkOuterViewport = new Rectangle(); + } + out = this._shrinkOuterViewport; + } + + CopyRectangle(this._outerViewport, out); + ShrinkSizeByRatio(out, maxRatio, minRatio); + out.centerX = this._outerViewport.centerX; + out.centerY = this._outerViewport.centerY; + + return out; + } + + // Internal methods + onFirstTick() { + if (this.cameras.size === 0) { + // Add default camera + this.add(this.scene.sys.cameras.main); + } + this.scale(); + } + + scale() { + GetScaleOutCameraParameters(this.scene, this); + this.cameras.iterate(function (camera, index) { + camera.zoomX = this.zoom; + camera.zoomY = this.zoom; + camera.scrollX = this.scrollX; + camera.scrollY = this.scrollY; + }, this); + + this._innerViewport = GetInnerViewport(this, this._innerViewport); + this._outerViewport = GetOuterViewport(this, this._outerViewport); + + return this; + } +} + +export default ScaleOuter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/ShrinkSizeByRatio.js b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/ShrinkSizeByRatio.js new file mode 100644 index 000000000..0b2d86b93 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scale/scaleouter/ShrinkSizeByRatio.js @@ -0,0 +1,17 @@ +var ShrinkSizeByRatio = function (rectangle, maxRatio, minRatio) { + var width = rectangle.width, + height = rectangle.height, + ratio = width / height; + + if ((maxRatio !== undefined) && (ratio > maxRatio)) { + rectangle.width = height * maxRatio; // Shrink width + } + + if ((minRatio !== undefined) && (ratio < minRatio)) { + rectangle.height = width / minRatio; // Shrink height + } + + return rectangle; +} + +export default ShrinkSizeByRatio; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scaleouter-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/scaleouter-plugin.d.ts new file mode 100644 index 000000000..2087179ef --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scaleouter-plugin.d.ts @@ -0,0 +1,19 @@ +export default class ScalePlugin extends Phaser.Plugins.ScenePlugin { + add( + camera: Phaser.Cameras.Scene2D.BaseCamera, + ): this; + + scale(): this; + + readonly scrollX: number; + readonly scrollY: number; + readonly zoom: number; + readonly innerViewport: Phaser.Geom.Rectangle; + readonly outerViewport: Phaser.Geom.Rectangle; + + getShrinkedOuterViewport( + maxRatio: number, + minRatio?: number, + out?: Phaser.Geom.Rectangle | true, + ): Phaser.Geom.Rectangle; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scaleouter-plugin.js b/ui/src/phaser3-rex-plugins/plugins/scaleouter-plugin.js new file mode 100644 index 000000000..405b5938e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scaleouter-plugin.js @@ -0,0 +1,64 @@ +import ScaleOuter from "./scaleouter.js"; + +class ScaleOuterPlugin extends Phaser.Plugins.ScenePlugin { + constructor(scene, pluginManager) { + super(scene, pluginManager); + this.scaleOuter = new ScaleOuter(scene); + } + + start() { + var eventEmitter = this.scene.sys.events; + eventEmitter.on('destroy', this.destroy, this); + } + + destroy() { + this.scaleOuter.destroy(); + this.scaleOuter = undefined; + super.destroy(); + } + + add(camera) { + this.scaleOuter.add(camera); + return this; + } + + scale() { + if (this.scaleOuter.cameras.size === 0) { + // Add default camera + this.add(this.scene.sys.cameras.main); + } + this.scaleOuter.scale(); + return this; + } + + stop() { + this.scaleOuter.stop(); + return this; + } + + get scrollX() { + return this.scaleOuter.scrollX; + } + + get scrollY() { + return this.scaleOuter.scrollY; + } + + get zoom() { + return this.scaleOuter.zoom; + } + + get innerViewport() { + return this.scaleOuter.innerViewport; + } + + get outerViewport() { + return this.scaleOuter.outerViewport; + } + + getShrinkedOuterViewport(maxRatio, minRatio, out) { + return this.scaleOuter.getShrinkedOuterViewport(maxRatio, minRatio, out); + } +} + +export default ScaleOuterPlugin; diff --git a/ui/src/phaser3-rex-plugins/plugins/scaleouter.d.ts b/ui/src/phaser3-rex-plugins/plugins/scaleouter.d.ts new file mode 100644 index 000000000..1df1e3a62 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scaleouter.d.ts @@ -0,0 +1,2 @@ +import ScaleOuter from './scale/scaleouter/ScaleOuter'; +export default ScaleOuter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scaleouter.js b/ui/src/phaser3-rex-plugins/plugins/scaleouter.js new file mode 100644 index 000000000..8c82e35da --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scaleouter.js @@ -0,0 +1,2 @@ +import ScaleOuter from './scale/scaleouter/ScaleOuter.js'; +export default ScaleOuter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scripttagloader-plugin.js b/ui/src/phaser3-rex-plugins/plugins/scripttagloader-plugin.js new file mode 100644 index 000000000..7fbd0e3e2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scripttagloader-plugin.js @@ -0,0 +1,16 @@ + +import LoaderCallback from './loader/scripttag/ScriptTagLoaderCallback.js'; + +class ScriptTagLoaderPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + + pluginManager.registerFileType('rexScriptTag', LoaderCallback); + } + + addToScene(scene) { + scene.sys.load['rexScriptTag'] = LoaderCallback; + } +} + +export default ScriptTagLoaderPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scripttagloader.d.ts b/ui/src/phaser3-rex-plugins/plugins/scripttagloader.d.ts new file mode 100644 index 000000000..7a57b58e9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scripttagloader.d.ts @@ -0,0 +1,3 @@ + +import LoaderCallback from './loader/scripttag/ScriptTagLoaderCallback'; +export default LoaderCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scripttagloader.js b/ui/src/phaser3-rex-plugins/plugins/scripttagloader.js new file mode 100644 index 000000000..28ac457f9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scripttagloader.js @@ -0,0 +1,6 @@ + +import LoaderCallback from './loader/scripttag/ScriptTagLoaderCallback.js'; + +Phaser.Loader.FileTypesManager.register('rexScriptTag', LoaderCallback); + +export default LoaderCallback; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scroller-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/scroller-plugin.d.ts new file mode 100644 index 000000000..85dd8c5e5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scroller-plugin.d.ts @@ -0,0 +1,9 @@ +import Scroller from './scroller'; + +export default class ScrollerPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Scroller.IConfig + ): Scroller; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scroller-plugin.js b/ui/src/phaser3-rex-plugins/plugins/scroller-plugin.js new file mode 100644 index 000000000..1d5b46231 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scroller-plugin.js @@ -0,0 +1,20 @@ +import Scroller from './scroller.js'; + +class ScrollerPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Scroller(gameObject, config); + } + +} + +export default ScrollerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scroller.d.ts b/ui/src/phaser3-rex-plugins/plugins/scroller.d.ts new file mode 100644 index 000000000..24d7fe221 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scroller.d.ts @@ -0,0 +1,2 @@ +import Scroller from './input/scroller/Scroller'; +export default Scroller; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/scroller.js b/ui/src/phaser3-rex-plugins/plugins/scroller.js new file mode 100644 index 000000000..5f6bd2f00 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/scroller.js @@ -0,0 +1,2 @@ +import Scroller from './input/scroller/Scroller.js'; +export default Scroller; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/sequence-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/sequence-plugin.d.ts new file mode 100644 index 000000000..fde71dc94 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/sequence-plugin.d.ts @@ -0,0 +1,8 @@ +import Sequence from './sequence'; + +export default class SequencePlugin extends Phaser.Plugins.BasePlugin { + add( + config?: Sequence.IConfig + ): Sequence; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/sequence-plugin.js b/ui/src/phaser3-rex-plugins/plugins/sequence-plugin.js new file mode 100644 index 000000000..25199d604 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/sequence-plugin.js @@ -0,0 +1,18 @@ +import Sequence from './sequence.js'; + +class SequencePlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new Sequence(config); + } +} + +export default SequencePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/sequence.d.ts b/ui/src/phaser3-rex-plugins/plugins/sequence.d.ts new file mode 100644 index 000000000..5a9ac5b3d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/sequence.d.ts @@ -0,0 +1,2 @@ +import Sequence from './logic/runcommands/sequence/Sequence'; +export default Sequence; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/sequence.js b/ui/src/phaser3-rex-plugins/plugins/sequence.js new file mode 100644 index 000000000..3aaf79a43 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/sequence.js @@ -0,0 +1,2 @@ +import Sequence from './logic/runcommands/sequence/Sequence.js'; +export default Sequence; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/barrel/BarrelPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/barrel/BarrelPostFxPipeline.d.ts new file mode 100644 index 000000000..7eed9addf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/barrel/BarrelPostFxPipeline.d.ts @@ -0,0 +1,33 @@ +export default BarrelPostFxPipeline; + +declare namespace BarrelPostFxPipeline { + interface IConfig { + shrink?: boolean, + center?: { + x?: number, y?: number + }, + radius?: number, + power?: number, + intensity?: number, + } +} + +declare class BarrelPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON(o?: BarrelPostFxPipeline.IConfig): this; + + setShrinkMode(mode?: boolean): this; + shrinkMode: boolean; + + setCenter(x: number, y?: number): this; + centerX: number; + centerY: number; + + setRadius(value: number): this; + radius: number; + + setPower(power: number): this; + power: this; + + setIntensity(value: number): this; + intensity: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/barrel/BarrelPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/barrel/BarrelPostFxPipeline.js new file mode 100644 index 000000000..82d4e7d04 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/barrel/BarrelPostFxPipeline.js @@ -0,0 +1,86 @@ +import FragSrc from './barrel-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; + + +class BarrelPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexBarrelPostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.shrinkMode = false; + this.fishEyeMode = 0; + this.centerX = 0; // position wo resolution + this.centerY = 0; // position wo resolution + this.radius = 0; + this.power = 1; + this.intensity = 1; + } + + resetFromJSON(o) { + this.setShrinkMode(GetValue(o, 'shrink', false)); + this.setRadius(GetValue(o, 'radius', 0)); + this.setCenter(GetValue(o, 'center.x', undefined), GetValue(o, 'center.y', undefined)); + this.setPower(GetValue(o, 'power', 0.5)); + this.setIntensity(GetValue(o, 'intensity', 1)); + return this; + } + + onPreRender() { + this.set1f('shrinkMode', (this.shrinkMode) ? 1 : 0); + this.set1f('radius', this.radius); + + var texWidth = this.renderer.width, + textHeight = this.renderer.height; + this.set2f('center', this.centerX, (textHeight - this.centerY)); + this.set2f('texSize', texWidth, textHeight); + + this.set1f('power', this.power); + this.set1f('intensity', this.intensity); + } + + // radius + setRadius(value) { + this.radius = value; + return this; + } + + // center + setCenter(x, y) { + if (x === undefined) { + x = this.renderer.width / 2; + y = this.renderer.height / 2; + } + this.centerX = x; + this.centerY = y; + return this; + } + + // power + setPower(power) { + this.power = power; + return this; + } + + // intensity + setIntensity(value) { + this.intensity = value; + return this; + } + + // shrinkMode + setShrinkMode(mode) { + if (mode === undefined) { + mode = true; + } + this.shrinkMode = mode; + return this; + } +} + +export default BarrelPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/barrel/barrel-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/barrel/barrel-postfxfrag.js new file mode 100644 index 000000000..f38410851 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/barrel/barrel-postfxfrag.js @@ -0,0 +1,37 @@ +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform float shrinkMode; +uniform vec2 texSize; +uniform vec2 center; +uniform float radius; +uniform float power; +uniform float intensity; + +void main (void) { + vec2 tc = outTexCoord * texSize; + tc -= center; + float dist = length(tc) / radius; + float factor = pow(dist, power); + if (shrinkMode > 0.0) { + factor = 1.0 / factor; + } + + tc *= mix(1.0, factor, intensity); + tc += center; + gl_FragColor = texture2D(uMainSampler, tc / texSize); + +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/colorreplace/ColorReplacePostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/colorreplace/ColorReplacePostFxPipeline.d.ts new file mode 100644 index 000000000..c4eec6ce4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/colorreplace/ColorReplacePostFxPipeline.d.ts @@ -0,0 +1,24 @@ +// import * as Phaser from 'phaser'; + +export default ColorReplacePostFxPipeline; + +declare namespace ColorReplacePostFxPipeline { + interface IConfig { + originalColor?: number, + newColor?: number, + epsilon?: number, + } +} + +declare class ColorReplacePostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON(o?: ColorReplacePostFxPipeline.IConfig): this; + + setEpsilon(value: number): this; + epsilon: number; + + setOriginalColor(value: number | Phaser.Types.Display.ColorObject): this; + originalColor: Phaser.Display.Color; + + setNewColor(value: number | Phaser.Types.Display.ColorObject): this; + newColor: Phaser.Display.Color; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/colorreplace/ColorReplacePostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/colorreplace/ColorReplacePostFxPipeline.js new file mode 100644 index 000000000..2df7808a2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/colorreplace/ColorReplacePostFxPipeline.js @@ -0,0 +1,73 @@ +import FragSrc from './colorreplace-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; +const IntegerToRGB = Phaser.Display.Color.IntegerToRGB; +const Color = Phaser.Display.Color; + +class ColorReplacePostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexColorReplacePostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.epsilon = 0.4; + this._originalColor = new Color(); + this._newColor = new Color(); + } + + resetFromJSON(o) { + this.setEpsilon(GetValue(o, 'epsilon', 0.4)); + this.setOriginalColor(GetValue(o, 'originalColor', 0xff0000)); + this.setNewColor(GetValue(o, 'newColor', 0x000000)); + return this; + } + + onPreRender() { + this.set1f('epsilon', this.epsilon); + this.set3f('originalColor', this._originalColor.redGL, this._originalColor.greenGL, this._originalColor.blueGL); + this.set3f('newColor', this._newColor.redGL, this._newColor.greenGL, this._newColor.blueGL); + } + + setEpsilon(value) { + this.epsilon = value; + return this; + } + + get originalColor() { + return this._originalColor; + } + + set originalColor(value) { + if (typeof (value) === 'number') { + value = IntegerToRGB(value); + } + this._originalColor.setFromRGB(value); + } + + setOriginalColor(value) { + this.originalColor = value; + return this; + } + + get newColor() { + return this._newColor; + } + + set newColor(value) { + if (typeof (value) === 'number') { + value = IntegerToRGB(value); + } + this._newColor.setFromRGB(value); + } + + setNewColor(value) { + this.newColor = value; + return this; + } +} + +export default ColorReplacePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/colorreplace/colorreplace-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/colorreplace/colorreplace-postfxfrag.js new file mode 100644 index 000000000..1f0545a27 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/colorreplace/colorreplace-postfxfrag.js @@ -0,0 +1,27 @@ +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform float epsilon; +uniform vec3 originalColor; +uniform vec3 newColor; + +void main (void) { + vec4 currentColor = texture2D(uMainSampler, outTexCoord); + vec3 colorDiff = originalColor - (currentColor.rgb / max(currentColor.a, 0.0000000001)); + float colorDistance = length(colorDiff); + float doReplace = step(colorDistance, epsilon); + gl_FragColor = vec4(mix(currentColor.rgb, (newColor + colorDiff) * currentColor.a, doReplace), currentColor.a); +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/crossstitching/CrossStitchingPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/crossstitching/CrossStitchingPostFxPipeline.d.ts new file mode 100644 index 000000000..3ce5117c2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/crossstitching/CrossStitchingPostFxPipeline.d.ts @@ -0,0 +1,23 @@ +// import * as Phaser from 'phaser'; +export default CrossStitchingPostFxPipeline; + +declare namespace CrossStitchingPostFxPipeline { + interface IConfig { + stitchingWidth?: number, + stitchingHeight?: number, + brightness?: number, + } +} + +declare class CrossStitchingPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON(o?: CrossStitchingPostFxPipeline.IConfig): this; + + setStitchingWidth(value: number): this; + stitchingWidth: number; + setStitchingHeight(value: number): this; + setStitchingSize(width: number, height?: number): this; + stitchingHeight: number; + + setBrightness(value: number): this; + brightness: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/crossstitching/CrossStitchingPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/crossstitching/CrossStitchingPostFxPipeline.js new file mode 100644 index 000000000..296eb503e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/crossstitching/CrossStitchingPostFxPipeline.js @@ -0,0 +1,69 @@ +import FragSrc from './crossstitching-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; +const Clamp = Phaser.Math.Clamp; + +class CrossStitchingPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexCrossStitchingPostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.stitchingWidth = 6; // width of stitching wo resolution + this.stitchingHeight = 6; // height of stitching wo resolution + this._brightness = 0; + } + + resetFromJSON(o) { + this.setStitchingSize(GetValue(o, 'stitchingWidth', 6), GetValue(o, 'stitchingHeight', 6)); + this.setBrightness(GetValue(o, 'brightness', 0)); + return this; + } + + onPreRender() { + this.set2f('stitchingSize', this.stitchingWidth, this.stitchingHeight); + this.set2f('texSize', this.renderer.width, this.renderer.height); + this.set1f('brightness', this._brightness); + } + + // stitchingWidth + setStitchingWidth(value) { + this.stitchingWidth = value; + return this; + } + + // stitchingHeight + setStitchingHeight(value) { + this.stitchingHeight = value; + return this; + } + + setStitchingSize(width, height) { + if (height === undefined) { + height = width; + } + this.stitchingWidth = width; + this.stitchingHeight = height; + return this; + } + + // brightness + get brightness() { + return this._brightness; + } + + set brightness(value) { + this._brightness = Clamp(value, 0, 1); + } + + setBrightness(value) { + this.brightness = value; + return this; + } +} + +export default CrossStitchingPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/crossstitching/crossstitching-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/crossstitching/crossstitching-postfxfrag.js new file mode 100644 index 000000000..68c534421 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/crossstitching/crossstitching-postfxfrag.js @@ -0,0 +1,50 @@ +// reference : https://www.geeks3d.com/20110408/cross-stitching-post-processing-shader-glsl-filter-geexlab-pixel-bender/ + +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform vec2 texSize; +uniform vec2 stitchingSize; +uniform float brightness; + +void main (void) { + vec2 cPos = outTexCoord * texSize; + int remX = int(mod(cPos.x, stitchingSize.x)); + int remY = int(mod(cPos.y, stitchingSize.y)); + vec2 tlPos; + if (remX == 0 && remY == 0) { + tlPos = cPos; + } else { + tlPos = floor(cPos / stitchingSize); + tlPos.x = tlPos.x * stitchingSize.x; + tlPos.y = tlPos.y * stitchingSize.y; + } + vec2 blPos = tlPos; + blPos.y += (stitchingSize.y - 1.0); + + vec4 color0, color1; + if ( + (remX == remY) || + (((int(cPos.x) - int(blPos.x)) == (int(blPos.y) - int(cPos.y)))) + ) { + color0 = texture2D(uMainSampler, tlPos * vec2(1.0/texSize.x, 1.0/texSize.y)) * 1.4; + color1 = vec4(0.2, 0.15, 0.05, 1.0); + } else { + color0 = vec4(0.0, 0.0, 0.0, 1.0); + color1 = texture2D(uMainSampler, tlPos * vec2(1.0/texSize.x, 1.0/texSize.y)) * 1.4; + } + gl_FragColor = mix(color0, color1, brightness); +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/dissolve/DissolvePostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/dissolve/DissolvePostFxPipeline.d.ts new file mode 100644 index 000000000..e0ce62a45 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/dissolve/DissolvePostFxPipeline.d.ts @@ -0,0 +1,52 @@ +// import * as Phaser from 'phaser'; + +export default DissolvePostFxPipeline; + +declare namespace DissolvePostFxPipeline { + interface IConfig { + toTexture?: string, + toFrame?: string, + resizeMode?: DissolvePostFxPipeline.ResizeModeType + + noiseX?: number, + noiseY?: number, + noiseZ?: number, + fromEdgeStart?: number, + fromEdgeWidth?: number, + toEdgeStart?: number, + toEdgeWidth?: number, + + progress?: number, + } + + type ResizeModeType = 0 | 1 | 2 | 'stretch' | 'contain' | 'cover'; + +} + +declare class DissolvePostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON(o?: DissolvePostFxPipeline.IConfig): this; + + setProgress(value: number): this; + progress: number; + + setNoise(x?: number, y?: number, z?: number): this; + noiseX: number; + noiseY: number; + noiseZ: number; + + setTransitionTargetTexture( + key?: string, frame?: string, + resizeMode?: DissolvePostFxPipeline.ResizeModeType + ): this + + setResizeMode(mode: DissolvePostFxPipeline.ResizeModeType): this; + resizeMode: number; + + setFromEdge(edgeStart: number, edgeWidth: number): this; + fromEdgeStart: number; + edgeWidth: number; + setToEdge(edgeStart: number, edgeWidth: number): this; + toEdgeStart: number; + toEdgeWidth: number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/dissolve/DissolvePostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/dissolve/DissolvePostFxPipeline.js new file mode 100644 index 000000000..558cc317f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/dissolve/DissolvePostFxPipeline.js @@ -0,0 +1,159 @@ +import FragSrc from './dissolve-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; +const Clamp = Phaser.Math.Clamp; + +class DissolvePostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexDissolvePostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this._progress = 0; + this.toFrame = null; + this.targetTexture = null; + this.resizeMode = 1; + this.toRatio = 1; + + this.noiseX = 0; + this.noiseY = 0; + this.noiseZ = 0; + this.fromEdgeStart = 0.01; + this.fromEdgeWidth = 0.05; + this.toEdgeStart = 0.01; + this.toEdgeWidth = 0.05; + } + + resetFromJSON(o) { + this.setProgress(GetValue(o, 'progress', 0)); + this.setTransitionTargetTexture(GetValue(o, 'toTexture', '__DEFAULT'), GetValue(o, 'toFrame', undefined), GetValue(o, 'resizeMode', 1)); + this.setNoise(GetValue(o, 'noiseX', undefined), GetValue(o, 'noiseY', undefined), GetValue(o, 'noiseZ', undefined)); + this.setFromEdge(GetValue(o, 'fromEdgeStart', 0.01), GetValue(o, 'fromEdgeWidth', 0.05)); + this.setToEdge(GetValue(o, 'toEdgeStart', 0.01), GetValue(o, 'toEdgeWidth', 0.05)); + return this; + } + + onBoot() { + this.setTransitionTargetTexture(); + } + + onPreRender() { + this.set1f('progress', this.progress); + this.set1i('resizeMode', this.resizeMode); + + this.set1f('noiseX', this.noiseX); + this.set1f('noiseY', this.noiseY); + this.set1f('noiseZ', this.noiseZ); + this.set1f('fromEdgeStart', this.fromEdgeStart); + this.set1f('fromEdgeWidth', this.fromEdgeWidth); + this.set1f('toEdgeStart', this.toEdgeStart); + this.set1f('toEdgeWidth', this.toEdgeWidth); + } + + onDraw(renderTarget) { + this.set1f('fromRatio', renderTarget.width / renderTarget.height); + + this.bindTexture(this.targetTexture, 1); + + this.bindAndDraw(renderTarget); + } + + get progress() { + return this._progress; + } + + set progress(value) { + this._progress = Clamp(value, 0, 1); + } + + setProgress(value) { + this.progress = value; + return this; + } + + setTransitionTargetTexture(key, frame, resizeMode) { + if (key === undefined) { + key = '__DEFAULT'; + } + var phaserTexture = this.game.textures.getFrame(key, frame); + + if (!phaserTexture) { + phaserTexture = this.game.textures.getFrame('__DEFAULT'); + } + + this.toRatio = phaserTexture.width / phaserTexture.height; + + this.toFrame = phaserTexture; + this.targetTexture = phaserTexture.glTexture; + + if (resizeMode !== undefined) { + this.resizeMode = resizeMode; + } + + this.set1i('uMainSampler2', 1); + this.set1f('toRatio', this.toRatio); + + return this; + } + + setResizeMode(mode) { + if (typeof (mode) === 'string') { + mode = ResizeMode[mode]; + } + this.resizeMode = mode; + return this; + } + + setNoise(x, y, z) { + if (x === undefined) { + x = 4 + Math.random() * 6; + } + if (y === undefined) { + y = 4 + Math.random() * 6; + } + if (z === undefined) { + z = Math.random() * 10; + } + this.noiseX = x; + this.noiseY = y; + this.noiseZ = z; + return this; + } + + setFromEdge(edgeStart, edgeWidth) { + this.fromEdgeStart = edgeStart; + this.fromEdgeWidth = edgeWidth; + return this; + } + + setToEdge(edgeStart, edgeWidth) { + this.toEdgeStart = edgeStart; + this.toEdgeWidth = edgeWidth; + return this; + } +} + +/** + * Set the resize mode of the target texture. + * + * Can be either: + * + * 0 - Stretch. The target texture is stretched to the size of the source texture. + * 1 - Contain. The target texture is resized to fit the source texture. This is the default. + * 2 - Cover. The target texture is resized to cover the source texture. + * + * If the source and target textures are the same size, then use a resize mode of zero + * for speed. + * + */ +var ResizeMode = { + stretch: 0, + contain: 1, + cover: 2 +} + +export default DissolvePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/dissolve/dissolve-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/dissolve/dissolve-postfxfrag.js new file mode 100644 index 000000000..6b1c02489 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/dissolve/dissolve-postfxfrag.js @@ -0,0 +1,67 @@ +// https://github.com/ykob/glsl-dissolve/blob/master/src/glsl/dissolve.fs + +import Perlin from '../utils/noise/Perlin.js'; + +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; +// Scene buffer +uniform sampler2D uMainSampler; +uniform sampler2D uMainSampler2; + +uniform int resizeMode; +uniform float progress; +uniform float fromRatio; +uniform float toRatio; +varying vec2 outFragCoord; +// Effect parameters +uniform float noiseX; +uniform float noiseY; +uniform float noiseZ; +uniform float fromEdgeStart; +uniform float fromEdgeWidth; +uniform float toEdgeStart; +uniform float toEdgeWidth; + +${Perlin} + +vec4 getFromColor (vec2 uv) { + return texture2D(uMainSampler, uv); +} + +vec4 getToColor (vec2 uv) { + if (resizeMode == 2) { + // cover + return texture2D(uMainSampler2, 0.5 + (vec2(uv.x, 1.0 - uv.y) - 0.5) * vec2(min(fromRatio / toRatio, 1.0), min((toRatio / fromRatio), 1.0))); + } else if (resizeMode == 1) { + // contain + return texture2D(uMainSampler2, 0.5 + (vec2(uv.x, 1.0 - uv.y) - 0.5) * vec2(max(fromRatio / toRatio, 1.0), max((toRatio / fromRatio), 1.0))); + } else { + // stretch + return texture2D(uMainSampler2, vec2(uv.x, 1.0 - uv.y)); + } +} + +vec4 transition (vec2 uv) { + vec4 colorFront = getFromColor(uv); + vec4 colorTo = getToColor(uv); + + float noise = (Perlin(vec3(uv.x * noiseX, uv.y * noiseY, noiseZ)) + 1.0) / 2.0 + * (1.0 - (fromEdgeStart + fromEdgeWidth + toEdgeStart + toEdgeWidth)) + + (fromEdgeStart + fromEdgeWidth + toEdgeStart + toEdgeWidth) * 0.5; + vec4 colorResult = colorFront * smoothstep(progress - (fromEdgeStart + fromEdgeWidth), progress - fromEdgeStart, noise) + + colorTo * smoothstep((1.0 - progress) - (toEdgeStart + toEdgeWidth), (1.0 - progress) - toEdgeStart, (1.0 - noise)); + return colorResult; +} + +void main () { + vec2 uv = outFragCoord; + gl_FragColor = transition(uv); +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/DropShadowPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/DropShadowPostFxPipeline.d.ts new file mode 100644 index 000000000..87d6d1430 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/DropShadowPostFxPipeline.d.ts @@ -0,0 +1,54 @@ +// import * as Phaser from 'phaser'; + +export default DropShadowPostFxPipeline; + +declare namespace DropShadowPostFxPipeline { + interface IConfig { + rotation?: number, + angle?: number, + distance?: number, + shadowColor?: number, + alpha?: number, + shadowOnly?: boolean, + blur?: number | number[], + quality?: number, + pixelWidth?: number, + pixelHeight?: number, + } +} + +declare class DropShadowPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON(o?: DropShadowPostFxPipeline.IConfig): this; + + setRotation(value: number): this; + setAngle(value: number): this; + rotation: number; + angle: number; + + setDistance(value: number): this; + distance: number; + + setShadowColor(value: number): this; + shadowColor: number; + + setAlpha(value: number): this; + alpha: number; + + setShadowOnly(enable?: boolean): this; + shadowOnly: boolean; + + setBlur(value: number): this; + blur: number; + + setQuality(value: number): this; + quality: number; + + setKernela(value: number): this; + kernels: number; + + setPixelWidth(value: number): this; + setPixelHeight(value: number): this; + setPixelSize(width: number, height: number): this; + pixelWidth: number; + pixelHeight: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/DropShadowPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/DropShadowPostFxPipeline.js new file mode 100644 index 000000000..f31f34e79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/DropShadowPostFxPipeline.js @@ -0,0 +1,230 @@ +import FragSrc from './dropshadow-postfxfrag.js'; +import KawaseBlurFragSrc from '../kawaseblur/kawaseblurFilter-postfxfrag.js'; +import GenerateKernels from '../kawaseblur/GenerateKernels.js'; +import ShadowDrawer from './ShadowDrawer.js' +import KawaseBlurDrawer from '../kawaseblur/KawaseBlurDrawer.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; +const DegToRad = Phaser.Math.DegToRad; +const RadToDeg = Phaser.Math.RadToDeg; +const IntegerToRGB = Phaser.Display.Color.IntegerToRGB; +const Color = Phaser.Display.Color; + +class DropShadowPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexDropShadowPostFx', + game: game, + shaders: [ + { fragShader: FragSrc, }, + { fragShader: KawaseBlurFragSrc, }, + ], + }); + + this.shadowDrawer = new ShadowDrawer(this, this.shaders[0]); + this.kawaseBlurDrawer = new KawaseBlurDrawer(this, this.shaders[1]); + + this.rotation = 0; + this.distance = 0; + this._shadowColor = new Color(); + this.alpha = 0.5; + this.shadowOnly = false; + + // KawaseBlur + this._kernels = [0]; + this._blur = 0; + this._quality = 1; + this.pixelWidth = 1; // width of pixel wo resolution + this.pixelHeight = 1; // height of pixel wo resolution + } + + resetFromJSON(o) { + var rotation = GetValue(o, 'rotation', undefined); + if (rotation === undefined) { + this.setAngle(GetValue(o, 'angle', 45)); + } else { + this.setRotation(rotation); + } + + this.setDistance(GetValue(o, 'distance', 5)); + + this.setShadowColor(GetValue(o, 'shadowColor', 0xffffff)); + this.setAlpha(GetValue(o, 'alpha', 0.5)); + + this.setShadowOnly(GetValue(o, 'shadowOnly', false)); + + // KawaseBlur + var blur = GetValue(o, 'blur', 4); + if (typeof(blur) === 'number') { + this.setBlur(blur); + this.setQuality(GetValue(o, 'quality', 3)); + } else { + this.setKernela(blur); + } + + this.setPixelSize(GetValue(o, 'pixelWidth', 1), GetValue(o, 'pixelHeight', 1)); + + return this; + } + + onPreRender() { + } + + onDraw(renderTarget) { + var targetFrame; + + // shadow + targetFrame = this.shadowDrawer.draw(this.shadowDrawer.init(renderTarget), true); + + // kawase-blur + targetFrame = this.kawaseBlurDrawer.draw(targetFrame, true); + + // Add renderTarget to result + if (!this.shadowOnly) { + this.copyFrame(renderTarget, targetFrame, 1, false); + } + + this.copyToGame(targetFrame); + } + + // rotation + setRotation(value) { + this.rotation = value; + return this; + } + + get angle() { + return RadToDeg(this.rotation); + } + + set angle(value) { + this.rotation = DegToRad(value); + } + + setAngle(value) { + this.angle = value; + return this; + } + + // distance + setDistance(value) { + this.distance = value; + return this; + } + + // shadow color + get shadowColor() { + return this._shadowColor; + } + + set shadowColor(value) { + if (typeof (value) === 'number') { + value = IntegerToRGB(value); + } + this._shadowColor.setFromRGB(value); + } + + setShadowColor(value) { + this.shadowColor = value; + return this; + } + + // alpha + setAlpha(value) { + this.alpha = value; + return this; + } + + // shadowOnly + setShadowOnly(enable) { + if (enable === undefined) { + enable = true; + } + + this.shadowOnly = enable; + return this; + } + + // KawaseBlur + // blur + get blur() { + return this._blur; + } + + set blur(value) { + if (this._blur === value) { + return; + } + + this._blur = value; + GenerateKernels(this._blur, this._quality, this._kernels); + } + + setBlur(value) { + this.blur = value; + return this; + } + + // quality + get quality() { + return this._quality; + } + + set quality(value) { + if (this._quality === value) { + return; + } + + this._quality = value; + GenerateKernels(this._blur, this._quality, this._kernels); + } + + setQuality(value) { + this.quality = value; + return this; + } + + // kernels + get kernels() { + return this._kernels; + } + + set kernels(value) { + if (value === undefined) { + value = [0]; + } + + this._kernels = value; + this._quality = value.length; + this._blur = Math.max(...value); + } + + setKernela(value) { + this.kernels = value; + return this; + } + + // pixelWidth + setPixelWidth(value) { + this.pixelWidth = value; + return this; + } + + // pixelHeight + setPixelHeight(value) { + this.pixelHeight = value; + return this; + } + + setPixelSize(width, height) { + if (height === undefined) { + height = width; + } + this.pixelWidth = width; + this.pixelHeight = height; + return this; + } +} + +export default DropShadowPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/ShadowDrawer.js b/ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/ShadowDrawer.js new file mode 100644 index 000000000..e82998864 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/ShadowDrawer.js @@ -0,0 +1,30 @@ +import Drawer from '../utils/drawer/Drawer.js'; + +class ShadowDrawer extends Drawer { + draw(startFrame, returnLastFrame) { + var self = this.postFXPipeline; + var shader = this.shader; + + var sourceFrame = startFrame; + var targetFrame = this.getAnotherFrame(sourceFrame); + var returnFrame; + + // Set uniforms + var offsetX = (self.distance / self.renderer.width) * Math.cos(self.rotation); + var offsetY = (self.distance / self.renderer.height) * Math.sin(self.rotation) + self.set2f('offset', offsetX, offsetY, shader); + self.set3f('color', self._shadowColor.redGL, self._shadowColor.greenGL, self._shadowColor.blueGL, shader); + self.set1f('alpha', self.alpha, shader); + // Bind and draw + if (returnLastFrame) { + self.bindAndDraw(sourceFrame, targetFrame, true, true, shader); + returnFrame = targetFrame; + } else { + self.bindAndDraw(sourceFrame, null, true, true, shader); + } + + return returnFrame; + } +} + +export default ShadowDrawer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/dropshadow-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/dropshadow-postfxfrag.js new file mode 100644 index 000000000..fd58d75d0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/dropshadow/dropshadow-postfxfrag.js @@ -0,0 +1,31 @@ +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform float alpha; +uniform vec3 color; +uniform vec2 offset; + +void main (void) { + vec4 sample = texture2D(uMainSampler, outTexCoord - offset); + + // Premultiply alpha + sample.rgb = color.rgb * sample.a; + + // alpha user alpha + sample *= alpha; + + gl_FragColor = sample; +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/fisheye/FishEyePostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/fisheye/FishEyePostFxPipeline.d.ts new file mode 100644 index 000000000..b2148990a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/fisheye/FishEyePostFxPipeline.d.ts @@ -0,0 +1,30 @@ +export default FishEyePostFxPipeline; + +declare namespace FishEyePostFxPipeline { + interface IConfig { + mode?: 0 | 1 | 'asin' | 'sin', + center?: { + x?: number, y?: number + }, + radius?: number, + intensity?: number, + + } +} + +declare class FishEyePostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON(o?: FishEyePostFxPipeline.IConfig): this; + + setFishEyeMode(mode: number | string): this; + fishEyeMode: number; + + setCenter(x: number, y?: number): this; + centerX: number; + centerY: number; + + setRadius(value: number): this; + radius: number; + + setIntensity(value: number): this; + intensity: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/fisheye/FishEyePostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/fisheye/FishEyePostFxPipeline.js new file mode 100644 index 000000000..062a445de --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/fisheye/FishEyePostFxPipeline.js @@ -0,0 +1,82 @@ +import FragSrc from './fisheye-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; + + +class FishEyePostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexFishEyePostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.fishEyeMode = 0; + this.centerX = 0; // position wo resolution + this.centerY = 0; // position wo resolution + this.radius = 0; + this.intensity = 1; + } + + resetFromJSON(o) { + this.setFishEyeMode(GetValue(o, 'mode', 0)); + this.setRadius(GetValue(o, 'radius', 0)); + this.setCenter(GetValue(o, 'center.x', undefined), GetValue(o, 'center.y', undefined)); + this.setIntensity(GetValue(o, 'intensity', 1)); + return this; + } + + onPreRender() { + this.set1f('mode', this.fishEyeMode); + + this.set1f('radius', this.radius); + + var texWidth = this.renderer.width, + textHeight = this.renderer.height; + this.set2f('center', this.centerX, (textHeight - this.centerY)); + this.set2f('texSize', texWidth, textHeight); + + this.set1f('intensity', this.intensity); + } + + // Mode + setFishEyeMode(mode) { + if (typeof (mode) === 'string') { + mode = FishEyeMode[mode]; + } + this.fishEyeMode = mode; + return this; + } + + // radius + setRadius(value) { + this.radius = value; + return this; + } + + // center + setCenter(x, y) { + if (x === undefined) { + x = this.renderer.width / 2; + y = this.renderer.height / 2; + } + this.centerX = x; + this.centerY = y; + return this; + } + + // intensity + setIntensity(value) { + this.intensity = value; + return this; + } +} + +const FishEyeMode = { + 'asin': 0, + 'sin': 1 +} + +export default FishEyePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/fisheye/fisheye-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/fisheye/fisheye-postfxfrag.js new file mode 100644 index 000000000..8616be97a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/fisheye/fisheye-postfxfrag.js @@ -0,0 +1,40 @@ +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform float mode; +uniform vec2 texSize; +uniform vec2 center; +uniform float radius; +uniform float intensity; + +void main (void) { + vec2 tc = outTexCoord * texSize; + tc -= center; + float dist = length(tc) / radius; + if (dist < 1.0) { + float factor; + if (mode > 0.0) { + factor = sin(dist * 1.570795); + } else { + factor = asin(dist) / 1.570795; + } + tc *= mix(1.0, factor, intensity); + } + + tc += center; + gl_FragColor = texture2D(uMainSampler, tc / texSize); + +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter/GlowFilterPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter/GlowFilterPostFxPipeline.d.ts new file mode 100644 index 000000000..c4d016092 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter/GlowFilterPostFxPipeline.d.ts @@ -0,0 +1,16 @@ +// import * as Phaser from 'phaser'; + +export default GlowFilterPostFxPipeline; + +declare namespace GlowFilterPostFxPipeline { + interface IConfig { + intensity?: number, + } +} + +declare class GlowFilterPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON(o?: GlowFilterPostFxPipeline.IConfig): this; + + setIntensity(value: number): this; + intensity: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter/GlowFilterPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter/GlowFilterPostFxPipeline.js new file mode 100644 index 000000000..52b89113c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter/GlowFilterPostFxPipeline.js @@ -0,0 +1,34 @@ +import FragSrc from './glowfilter-postfxfrag'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; + +class GlowFilterPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexGlowFilterPostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.intensity = 0; + } + + resetFromJSON(o) { + this.setIntensity(GetValue(o, 'intensity', 0)); + return this; + } + + onPreRender() { + this.set1f('intensity', this.intensity); + } + + // intensity + setIntensity(value) { + this.intensity = value; + return this; + } +} + +export default GlowFilterPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter/glowfilter-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter/glowfilter-postfxfrag.js new file mode 100644 index 000000000..d30c4d4d5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter/glowfilter-postfxfrag.js @@ -0,0 +1,38 @@ +// https://gist.github.com/MatthewBarker/032c325ef8577c6d0188 + +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform float intensity; + +void main() { + vec4 front = texture2D(uMainSampler, outTexCoord); + vec4 sum = vec4(0); + for(int xx = -4; xx <= 4; xx++) { + for(int yy = -3; yy <= 3; yy++) { + float dist = sqrt(float(xx*xx) + float(yy*yy)); + float factor = 0.0; + if (dist == 0.0) { + factor = 2.0; + } else { + factor = 2.0/abs(float(dist)); + } + sum += texture2D(uMainSampler, outTexCoord + vec2(xx, yy) * 0.002) * factor; + } + } + + gl_FragColor = mix(front, sum, intensity); +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter2/GlowFilterPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter2/GlowFilterPostFxPipeline.d.ts new file mode 100644 index 000000000..2649c17d1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter2/GlowFilterPostFxPipeline.d.ts @@ -0,0 +1,34 @@ +// import * as Phaser from 'phaser'; + +export default GlowFilterPostFxPipeline; + +declare namespace GlowFilterPostFxPipeline { + interface IConfig { + outerStrength?: number, + innerStrength?: number, + glowColor?: number, + knockout?: boolean, + } +} + +declare class GlowFilterPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON(o?: GlowFilterPostFxPipeline.IConfig): this; + + setOuterStrength(value: number): this; + outerStrength: number; + + setInnerStrength(value: number): this; + innerStrength: number; + + setGlowColor(value: number | Phaser.Types.Display.ColorObject): this; + glowColor: Phaser.Display.Color; + + setKnockout(value: boolean): this; + knockout: boolean; + + static setQuality(quality: number): void; + static getQuality(): number; + + static setDistance(distance: number): void; + static getDistance(): number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter2/GlowFilterPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter2/GlowFilterPostFxPipeline.js new file mode 100644 index 000000000..15af1a7ed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter2/GlowFilterPostFxPipeline.js @@ -0,0 +1,107 @@ +import GetFrag from './glowfilter-postfxfrag'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; +const IntegerToRGB = Phaser.Display.Color.IntegerToRGB; +const Color = Phaser.Display.Color; + +var Quality = 0.1; +var Distance = 10; +var FragSrc = GetFrag({ quality: Quality, distance: Distance }); +class GlowFilterPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexGlowFilterPostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.outerStrength = 0; + this.innerStrength = 0; + this._glowColor = new Color(); + this.knockout = false; + } + + resetFromJSON(o) { + this.setOuterStrength(GetValue(o, 'outerStrength', 4)); + this.setInnerStrength(GetValue(o, 'innerStrength', 0)); + this.setGlowColor(GetValue(o, 'glowColor', 0xffffff)); + this.setKnockout(GetValue(o, 'knockout', false)); + return this; + } + + onPreRender() { + this.set1f('outerStrength', this.outerStrength); + this.set1f('innerStrength', this.innerStrength); + + var color = this._glowColor; + this.set4f('glowColor', color.redGL, color.greenGL, color.blueGL, color.alphaGL); + + this.set1f('knockout', (this.knockout) ? 1 : 0); + + this.set2f('texSize', this.renderer.width, this.renderer.height); + } + + // outerStrength + setOuterStrength(value) { + this.outerStrength = value; + return this; + } + + // innerStrength + setInnerStrength(value) { + this.innerStrength = value; + return this; + } + + // glowColor + get glowColor() { + return this._glowColor; + } + + set glowColor(value) { + if (typeof (value) === 'number') { + value = IntegerToRGB(value); + } + this._glowColor.setFromRGB(value); + } + + setGlowColor(value) { + this.glowColor = value; + return this; + } + + // knockout + setKnockout(value) { + this.knockout = value; + return this; + } + + static setQuality(value) { + if (Quality === value) { + return; + } + Quality = value; + FragSrc = GetFrag({ quality: Quality, distance: Distance }); + } + + static getQuality() { + return Quality; + } + + static setDistance(value) { + if (Distance === value) { + return; + } + Distance = value; + FragSrc = GetFrag({ quality: Quality, distance: Distance }); + } + + static getDistance() { + return Distance; + } + +} + +export default GlowFilterPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter2/glowfilter-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter2/glowfilter-postfxfrag.js new file mode 100644 index 000000000..06b68142b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/glowfilter2/glowfilter-postfxfrag.js @@ -0,0 +1,85 @@ +// Reference: https://github.com/pixijs/filters/blob/main/filters/glow/src/glow.frag + +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform vec2 texSize; +uniform float outerStrength; +uniform float innerStrength; +uniform vec4 glowColor; // (0, 0, 0); +uniform float knockout; + +// const +const float PI = 3.14159265358979323846264; + +const float DIST = __DIST__; +const float ANGLE_STEP_SIZE = min(__ANGLE_STEP_SIZE__, PI * 2.0); +const float ANGLE_STEP_NUM = ceil(PI * 2.0 / ANGLE_STEP_SIZE); + +const float MAX_TOTAL_ALPHA = ANGLE_STEP_NUM * DIST * (DIST + 1.0) / 2.0; + + +void main(void) { + vec2 px = vec2(1./texSize.x, 1./texSize.y); + + float totalAlpha = 0.0; + + vec2 direction; + vec2 offset; + vec4 curColor; + + for (float angle = 0.; angle < PI * 2.; angle += ANGLE_STEP_SIZE) { + direction = vec2(cos(angle), sin(angle)) * px; + + for (float curDistance = 0.0; curDistance < DIST; curDistance++) { + offset = direction * (curDistance + 1.0); + curColor = texture2D(uMainSampler, outTexCoord + offset); + totalAlpha += (DIST - curDistance) * curColor.a; + } + } + + curColor = texture2D(uMainSampler, outTexCoord); + + float alphaRatio = (totalAlpha / MAX_TOTAL_ALPHA); + + float innerGlowAlpha = (1.0 - alphaRatio) * innerStrength * curColor.a; + float innerGlowStrength = min(1.0, innerGlowAlpha); + + vec4 innerColor = mix(curColor, glowColor, innerGlowStrength); + + float outerGlowAlpha = alphaRatio * outerStrength * (1. - curColor.a); + float outerGlowStrength = min(1.0 - innerColor.a, outerGlowAlpha); + + vec4 outerGlowColor = outerGlowStrength * glowColor.rgba; + + if (knockout > 0.) { + float resultAlpha = outerGlowAlpha + innerGlowAlpha; + gl_FragColor = vec4(glowColor.rgb * resultAlpha, resultAlpha); + } + else { + gl_FragColor = innerColor + outerGlowColor; + } +} +`; + +const GetValue = Phaser.Utils.Objects.GetValue; +var GetFrag = function (config) { + var quality = GetValue(config, 'quality', 0, 1); + var distance = GetValue(config, 'distance', 10); + return frag + .replace(/__ANGLE_STEP_SIZE__/gi, `${(1 / quality / distance).toFixed(7)}`) + .replace(/__DIST__/gi, `${Math.round(distance).toFixed(0)}.0`); +} + + +export default GetFrag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/grayscale/GrayScalePostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/grayscale/GrayScalePostFxPipeline.d.ts new file mode 100644 index 000000000..26e775eb4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/grayscale/GrayScalePostFxPipeline.d.ts @@ -0,0 +1,16 @@ +// import * as Phaser from 'phaser'; + +export default GrayScalePostFxPipeline; + +declare namespace GrayScalePostFxPipeline { + interface IConfig { + intensity?: number, + } +} + +declare class GrayScalePostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON(o?: GrayScalePostFxPipeline.IConfig): this; + + setIntensity(value: number): this; + intensity: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/grayscale/GrayScalePostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/grayscale/GrayScalePostFxPipeline.js new file mode 100644 index 000000000..e0449f869 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/grayscale/GrayScalePostFxPipeline.js @@ -0,0 +1,34 @@ +import FragSrc from './grayscale-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; + +class GrayScalePostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexGrayScalePostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.intensity = 1; + } + + resetFromJSON(o) { + this.setIntensity(GetValue(o, 'intensity', 1)); + return this; + } + + onPreRender() { + this.set1f('intensity', this.intensity); + } + + // intensity + setIntensity(value) { + this.intensity = value; + return this; + } +} + +export default GrayScalePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/grayscale/grayscale-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/grayscale/grayscale-postfxfrag.js new file mode 100644 index 000000000..6b94dc1e8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/grayscale/grayscale-postfxfrag.js @@ -0,0 +1,20 @@ +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; +// Effect parameters +uniform float intensity; +void main (void) { + vec4 front = texture2D(uMainSampler, outTexCoord); + float gray = dot(front.rgb, vec3(0.299, 0.587, 0.114)); + gl_FragColor = mix(front, vec4(gray, gray, gray, front.a), intensity); +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipeline.d.ts new file mode 100644 index 000000000..9da426ad9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipeline.d.ts @@ -0,0 +1,111 @@ +// import * as Phaser from 'phaser'; +export default HorrifiPostFxPipeline; + +declare namespace HorrifiPostFxPipeline { + interface IConfig { + enable?: boolean, + + // Bloom + bloomEnable?: boolean, + bloomRadius?: number, bloomIntensity?: number, bloomThreshold?: number, + bloomTexelWidth?: number, bloomTexelHeight?: number, + + // Chromatic abberation + chromaticEnable?: boolean, + chabIntensity?: number, + + // Vignette + vignetteEnable?: boolean, + vignetteStrength?: number, vignetteIntensity?: number, + + // Noise + noiseEnable?: boolean, + noiseStrength?: number, + noiseSeed?: number, + + // VHS + vhsEnable?: boolean, + vhsStrength?: number, + + // Scanlines + scanlinesEnable?: boolean, + scanStrength?: number, + + // CRT + crtEnable?: boolean, + crtWidth?: number, crtHeight?: number, + + } +} + +declare class HorrifiPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + + // Bloom + bloomEnable: boolean; + bloomRadius: number; + bloomIntensity: number; + bloomThreshold: number; + bloomTexelWidth: number; + bloomTexelHeight: number; + + // Chromatic abberation + chromaticEnable: boolean; + chabIntensity: number; + + // Vignette + vignetteEnable: boolean; + vignetteStrength: number; + vignetteIntensity: number; + + // Noise + noiseEnable: boolean; + noiseStrength: number; + noiseSeed: number; + + // VHS + vhsEnable: boolean; + vhsStrength: number; + + // Scanlines + scanlinesEnable: boolean; + scanStrength: number; + + // CRT + crtEnable: boolean; + crtWidth: number; + crtHeight: number; + + // Bloom + setBloomEnable(enable?: boolean): this; + setBloomRadius(value: number): this; + setBloomIntensity(value: number): this; + setBloomThreshold(value: number): this; + setBloomTexelSize(width: number, height?: number): this; + + // Chromatic abberation + setChromaticEnable(enable?: boolean): this; + setChabIntensity(value: number): this; + + // Vignette + setVignetteEnable(Genable?: boolean): this; + setVignetteStrength(value: number): this; + setVignetteIntensity(value: number): this; + + // Noise + setNoiseEnable(enable?: boolean): this; + setNoiseStrength(value: number): this; + setNoiseSeed(value: number): this; + + // VHS + setVHSEnable(enable?: boolean): this; + setVhsStrength(value: number): this; + + // Scanlines + setScanlinesEnable(enable?: boolean): this; + setScanStrength(value: number): this; + + // CRT + setCRTEnable(enable?: boolean): this; + setCrtSize(width: number, height?: number): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipeline.js new file mode 100644 index 000000000..9f734e5a0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipeline.js @@ -0,0 +1,139 @@ +import FragSrc from './horrifi-postfxfrag.js'; +import Methods from './methods/Methods.js'; +import GetTickDelta from '../../utils/system/GetTickDelta.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; + +class HorrifiPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexHorrifiPostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.now = 0; + + // Bloon + this.bloomEnable = false; + this.bloomRadius = 0; + this.bloomIntensity = 0; + this.bloomThreshold = 0; + this.bloomTexelWidth = 0; + this.bloomTexelHeight = 0; + + // Chromatic abberation + this.chromaticEnable = false; + this.chabIntensity = 0; + + // Vignette + this.vignetteEnable = false; + this.vignetteStrength = 0; + this.vignetteIntensity = 0; + + // Noise + this.noiseEnable = false; + this.noiseStrength = 0; + this.noiseSeed = Math.random(); + + // VHS + this.vhsEnable = false; + this.vhsStrength = 0; + + // Scanlines + this.scanlinesEnable = false; + this.scanStrength = 0; + + // CRT + this.crtEnable = false; + this.crtWidth = 0; + this.crtHeight = 0; + } + + resetFromJSON(o) { + var enable = GetValue(o, 'enable', false); + + // Bloom + this.setBloomEnable(GetValue(o, 'bloomEnable', enable)); + this.setBloomRadius(GetValue(o, 'bloomRadius', 0)); + this.setBloomIntensity(GetValue(o, 'bloomIntensity', 0)); + this.setBloomThreshold(GetValue(o, 'bloomThreshold', 0)); + this.setBloomTexelSize(GetValue(o, 'bloomTexelWidth', 0), GetValue(o, 'bloomTexelHeight')); + + // Chromatic abberation + this.setChromaticEnable(GetValue(o, 'chromaticEnable', enable)); + this.setChabIntensity(GetValue(o, 'chabIntensity', 0)); + + // Vignette + this.setVignetteEnable(GetValue(o, 'vignetteEnable', enable)); + this.setVignetteStrength(GetValue(o, 'vignetteStrength', 0)); + this.setVignetteIntensity(GetValue(o, 'vignetteIntensity', 0)); + + // Noise + this.setNoiseEnable(GetValue(o, 'noiseEnable', enable)); + this.setNoiseStrength(GetValue(o, 'noiseStrength', 0)); + this.setNoiseSeed(GetValue(0, 'noiseSeed', Math.random())); + + // VHS + this.setVHSEnable(GetValue(o, 'vhsEnable', enable)); + this.setVhsStrength(GetValue(o, 'vhsStrength', 0)); + + // Scanlines + this.setScanlinesEnable(GetValue(o, 'scanlinesEnable', enable)); + this.setScanStrength(GetValue(o, 'scanStrength', 0)); + + // CRT + this.setCRTEnable(GetValue(o, 'crtEnable', enable)); + this.setCrtSize(GetValue(o, 'crtWidth', 0), GetValue(o, 'crtHeight', undefined)); + + return this; + } + + onPreRender() { + this.set1f('noiseSeed', this.noiseSeed); + + // Bloon + this.set1f('bloomEnable', (this.bloomEnable) ? 1 : 0); + this.set3f('bloom', this.bloomRadius, this.bloomIntensity, this.bloomThreshold); + this.set2f('bloomTexel', this.bloomTexelWidth, this.bloomTexelHeight); + + // Chromatic abberation + this.set1f('chromaticEnable', (this.chromaticEnable) ? 1 : 0); + this.set1f('chabIntensity', this.chabIntensity); + + // Vignette + this.set1f('vignetteEnable', (this.vignetteEnable) ? 1 : 0); + this.set2f('vignette', this.vignetteStrength, this.vignetteIntensity); + + // Noise + this.set1f('noiseEnable', (this.noiseEnable) ? 1 : 0); + this.set1f('noiseStrength', this.noiseStrength); + + // VHS + this.set1f('vhsEnable', (this.vhsEnable) ? 1 : 0); + this.set1f('vhsStrength', this.vhsStrength); + + // Scanlines + this.set1f('scanlinesEnable', (this.scanlinesEnable) ? 1 : 0); + this.set1f('scanStrength', this.scanStrength); + + // CRT + this.set1f('crtEnable', (this.crtEnable) ? 1 : 0); + this.set2f('crtSize', this.crtWidth, this.crtHeight); + + // Eanble by VHS + if (this.vhsEnable) { + this.now += GetTickDelta(this.game); + } + this.set1f('time', this.now); + } +} + +Object.assign( + HorrifiPostFxPipeline.prototype, + Methods +) + +export default HorrifiPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipelineBehavior.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipelineBehavior.d.ts new file mode 100644 index 000000000..8fc9ff28e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipelineBehavior.d.ts @@ -0,0 +1,21 @@ +import BasePostFxPipelineBehavior from '../../utils/renderer/postfxpipeline/BasePostFxPipelineBehavior'; +import HorrifiPostFxPipeline from './HorrifiPostFxPipeline'; + +export default HorrifiPostFxPipelineBehavior; + +declare namespace HorrifiPostFxPipelineBehavior { + interface IConfig extends HorrifiPostFxPipeline.IConfig { + + } +} + +declare class HorrifiPostFxPipelineBehavior extends BasePostFxPipelineBehavior { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: HorrifiPostFxPipelineBehavior.IConfig + ); + + getPipeline( + config?: HorrifiPostFxPipelineBehavior.IConfig + ): HorrifiPostFxPipeline; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipelineBehavior.js b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipelineBehavior.js new file mode 100644 index 000000000..766086a43 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/HorrifiPostFxPipelineBehavior.js @@ -0,0 +1,10 @@ +import BasePostFxPipelineBehavior from '../../utils/renderer/postfxpipeline/BasePostFxPipelineBehavior.js'; +import HorrifiPostFxPipeline from './HorrifiPostFxPipeline.js'; + +class HorrifiPostFxPipelineBehavior extends BasePostFxPipelineBehavior { + createPipeline(game) { + return new HorrifiPostFxPipeline(game); + } +} + +export default HorrifiPostFxPipelineBehavior; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/horrifi-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/horrifi-postfxfrag.js new file mode 100644 index 000000000..5164364da --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/horrifi-postfxfrag.js @@ -0,0 +1,160 @@ +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +uniform float time; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +#define SAMPLES 32. + +// Bloom +uniform float bloomEnable; +uniform vec3 bloom; +uniform vec2 bloomTexel; + +// Chromatic abberation +uniform float chromaticEnable; +uniform float chabIntensity; + +// Vignette +uniform float vignetteEnable; +uniform vec2 vignette; + +// Noise +uniform float noiseEnable; +uniform float noiseStrength; +uniform float noiseSeed; + +// VHS +uniform float vhsEnable; +uniform float vhsStrength; + +// Scanlines +uniform float scanlinesEnable; +uniform float scanStrength; + +// CRT +uniform float crtEnable; +uniform vec2 crtSize; + + +// Noise +float noise(vec2 uv) { + return fract(sin(uv.x*12.9898+uv.y*78.233)*437.585453*noiseSeed); +} + +// VHS +vec4 vhs(vec2 uv) { + vec2 tcoord = uv; + tcoord.x += sin(time*noise(uv)); + return texture2D( uMainSampler, tcoord)*vhsStrength; +} + +// Vignette +float vig(vec2 uv) { + uv *= 1. - uv; + return ( pow(uv.x*uv.y*vignette.x*10.,vignette.y) ); +} + +// Chromatic abberation +vec3 chromatic(vec2 uv, float offset) { + float r = texture2D( uMainSampler, vec2(uv.x+offset, uv.y)).r; + float g = texture2D( uMainSampler, uv).g; + float b = texture2D( uMainSampler, vec2(uv.x-offset, uv.y)).b; + return vec3(r,g,b); +} + +// Bloom +vec4 blur(vec2 uv) { + float total = 0.; + float rad = 1.; + mat2 ang = mat2(.73736882209777832,-.67549037933349609,.67549037933349609,.73736882209777832); + vec2 point = normalize(fract(cos(uv*mat2(195,174,286,183))*742.)-.5)*(bloom.x/sqrt(SAMPLES)); + vec4 amount = vec4(0); + + for ( float i=0.; i .5 ) { + mainUv = crtCurve(outTexCoord); + } + + // Base coloring + vec4 color = texture2D( uMainSampler, mainUv); + + // Chromatic abberation + if ( chromaticEnable > .5 ) { + color.rgb *= chromatic(mainUv, chabIntensity * 0.01); + } + + // Scanlines + if ( scanlinesEnable > .5 ) { + color.rgb *= (1.-scanStrength)+(sin(mainUv.y*1024.)*scanStrength); + } + + // Bloom + if ( bloomEnable > .5 ) { + color.rgb += blur(mainUv).rgb; + } + + // Noise + if ( noiseEnable > .5 ) { + color.rgb += noise(mainUv)*noiseStrength; + } + + // VHS + if ( vhsEnable > .5 ) { + color += vhs(mainUv); + } + + // Vignette + if ( vignetteEnable > .5) { + color.rgb *= vig(mainUv); + } + + // Cutoff edges + if ( crtEnable > .5) { + if ( (mainUv.x < 0.)|| (mainUv.y < 0.) || (mainUv.x > 1.)|| (mainUv.y > 1.) ) { + color.rgb *= 0.; + } + } + + gl_FragColor = color; +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/BloonConfigurationMethods.js b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/BloonConfigurationMethods.js new file mode 100644 index 000000000..a3b9738d4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/BloonConfigurationMethods.js @@ -0,0 +1,35 @@ +export default { + setBloomEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.bloomEnable = enable; + return this; + }, + + setBloomRadius(value) { + this.bloomRadius = value; + return this; + }, + + setBloomIntensity(value) { + this.bloomIntensity = value; + return this; + }, + + setBloomThreshold(value) { + this.bloomThreshold = value; + return this; + }, + + setBloomTexelSize(width, height) { + if (height === undefined) { + height = width; + } + this.bloomTexelWidth = width; + this.bloomTexelHeight = height; + return this; + } + + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/CRTConfigurationMethod.js b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/CRTConfigurationMethod.js new file mode 100644 index 000000000..bec9dddda --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/CRTConfigurationMethod.js @@ -0,0 +1,18 @@ +export default { + setCRTEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.crtEnable = enable; + return this; + }, + + setCrtSize(width, height) { + if (height === undefined) { + height = width; + } + this.crtWidth = width; + this.crtHeight = height; + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/ChromaticConfigurationMethods.js b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/ChromaticConfigurationMethods.js new file mode 100644 index 000000000..d0203ba15 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/ChromaticConfigurationMethods.js @@ -0,0 +1,14 @@ +export default { + setChromaticEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.chromaticEnable = enable; + return this; + }, + + setChabIntensity(value) { + this.chabIntensity = value; + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/Methods.js new file mode 100644 index 000000000..e177fbbe6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/Methods.js @@ -0,0 +1,25 @@ +import SetEnable from './SetEnable.js'; +import BloonConfigurationMethods from './BloonConfigurationMethods.js'; +import ChromaticConfigurationMethods from './ChromaticConfigurationMethods.js'; +import VignetteConfigurationMethod from './VignetteConfigurationMethod.js'; +import NoiseConfigurationMethod from './NoiseConfigurationMethod.js'; +import VHSConfigurationMethod from './VHSConfigurationMethod.js'; +import ScanlinesConfigurationMethod from './ScanlinesConfigurationMethod.js'; +import CRTConfigurationMethod from './CRTConfigurationMethod.js'; + +var Methods = { + setEnable: SetEnable +}; + +Object.assign( + Methods, + BloonConfigurationMethods, + ChromaticConfigurationMethods, + VignetteConfigurationMethod, + NoiseConfigurationMethod, + VHSConfigurationMethod, + ScanlinesConfigurationMethod, + CRTConfigurationMethod, +) + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/NoiseConfigurationMethod.js b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/NoiseConfigurationMethod.js new file mode 100644 index 000000000..44d58808b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/NoiseConfigurationMethod.js @@ -0,0 +1,19 @@ +export default { + setNoiseEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.noiseEnable = enable; + return this; + }, + + setNoiseStrength(value) { + this.noiseStrength = value; + return this; + }, + + setNoiseSeed(value) { + this.noiseSeed = value; + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/ScanlinesConfigurationMethod.js b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/ScanlinesConfigurationMethod.js new file mode 100644 index 000000000..a41ee4dc9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/ScanlinesConfigurationMethod.js @@ -0,0 +1,14 @@ +export default { + setScanlinesEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.scanlinesEnable = enable; + return this; + }, + + setScanStrength(value) { + this.scanStrength = value; + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/SetEnable.js b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/SetEnable.js new file mode 100644 index 000000000..52fe7a067 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/SetEnable.js @@ -0,0 +1,16 @@ +var SetEnable = function (enable) { + if (enable === undefined) { + enable = true; + } + this.bloomEnable = enable; + this.chromaticEnable = enable; + this.vignetteEnable = enable; + this.noiseEnable = enable; + this.vhsEnable = enable; + this.scanlinesEnable = enable; + this.crtEnable = enable; + + return this; +} + +export default SetEnable; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/VHSConfigurationMethod.js b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/VHSConfigurationMethod.js new file mode 100644 index 000000000..3a8e39899 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/VHSConfigurationMethod.js @@ -0,0 +1,14 @@ +export default { + setVHSEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.vhsEnable = enable; + return this; + }, + + setVhsStrength(value) { + this.vhsStrength = value; + return this; + }, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/VignetteConfigurationMethod.js b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/VignetteConfigurationMethod.js new file mode 100644 index 000000000..9dd543484 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/horrifi/methods/VignetteConfigurationMethod.js @@ -0,0 +1,19 @@ +export default { + setVignetteEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.vignetteEnable = enable; + return this; + }, + + setVignetteStrength(value) { + this.vignetteStrength = value; + return this; + }, + + setVignetteIntensity(value) { + this.vignetteIntensity = value; + return this; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/hsladjust/HslAdjustPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/hsladjust/HslAdjustPostFxPipeline.d.ts new file mode 100644 index 000000000..c42dcb0ff --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/hsladjust/HslAdjustPostFxPipeline.d.ts @@ -0,0 +1,23 @@ +// import * as Phaser from 'phaser'; +export default HslAdjustPostFxPipeline; + +declare namespace HslAdjustPostFxPipeline { + interface IConfig { + hueRotate?: number, + satAdjust?: number, + lumAdjust?: number + } +} + +declare class HslAdjustPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON(o?: HslAdjustPostFxPipeline.IConfig): this; + + setHueRotate(value: number): this; + hueRotate: number; + + setSatAdjust(value: number): this; + satAdjust: number; + + setLumAdjust(value: number): this; + lumAdjust: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/hsladjust/HslAdjustPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/hsladjust/HslAdjustPostFxPipeline.js new file mode 100644 index 000000000..731a82d2d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/hsladjust/HslAdjustPostFxPipeline.js @@ -0,0 +1,52 @@ +import FragSrc from './hslAdjust-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; + +class HslAdjustPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexHslAdjustPostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.hueRotate = 0; + this.satAdjust = 1; + this.lumAdjust = 0.5; + } + + resetFromJSON(o) { + this.setHueRotate(GetValue(o, 'hueRotate', 0)); + this.setSatAdjust(GetValue(o, 'satAdjust', 1)); + this.setLumAdjust(GetValue(o, 'lumAdjust', 0.5)); + return this; + } + + onPreRender() { + this.set1f('hueRotate', (this.hueRotate) % 1); + this.set1f('satAdjust', this.satAdjust); + this.set1f('lumAdjust', this.lumAdjust); + } + + // hueRotate + setHueRotate(value) { + this.hueRotate = value; // 0: rotate 0 degrees, 0.5: rotate 180 degrees, 1: rotate 360 degrees + return this; + } + + // satAdjust + setSatAdjust(value) { + this.satAdjust = value; // 0: gray, 1: original color, > 1: + return this; + } + + // lumAdjust + setLumAdjust(value) { + this.lumAdjust = value; // 0: dark, 0.5: original color, 1: white + return this; + } +} + +export default HslAdjustPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/hsladjust/hslAdjust-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/hsladjust/hslAdjust-postfxfrag.js new file mode 100644 index 000000000..b4a7c18ca --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/hsladjust/hslAdjust-postfxfrag.js @@ -0,0 +1,34 @@ +import RGBToHSL from '../utils/RGBToHSL.js'; +import HSLToRGB from '../utils/HSLToRGB.js'; + +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform float hueRotate; +uniform float satAdjust; +uniform float lumAdjust; +` ++ RGBToHSL + HSLToRGB + +`\ +void main(void) { + vec4 front = texture2D(uMainSampler, outTexCoord); + vec3 hsl = RGBToHSL(front.rgb); + hsl.x -= hueRotate; + hsl.y *= satAdjust; + hsl.z += (lumAdjust - 0.5) * front.a; + vec3 rgb = HSLToRGB(hsl); + gl_FragColor = vec4(rgb, front.a); +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/inverse/InversePostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/inverse/InversePostFxPipeline.d.ts new file mode 100644 index 000000000..57d357e0a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/inverse/InversePostFxPipeline.d.ts @@ -0,0 +1,15 @@ +// import * as Phaser from 'phaser'; +export default InversePostFxPipeline; + +declare namespace InversePostFxPipeline { + interface IConfig { + intensity?: number, + } +} + +declare class InversePostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON(o?: InversePostFxPipeline.IConfig): this; + + setIntensity(value: number): this; + intensity: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/inverse/InversePostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/inverse/InversePostFxPipeline.js new file mode 100644 index 000000000..94cfb89fe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/inverse/InversePostFxPipeline.js @@ -0,0 +1,34 @@ +import FragSrc from './inverse-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; + +class InversePostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexInversePostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.intensity = 1; + } + + resetFromJSON(o) { + this.setIntensity(GetValue(o, 'intensity', 1)); + return this; + } + + onPreRender() { + this.set1f('intensity', this.intensity); + } + + // intensity + setIntensity(value) { + this.intensity = value; + return this; + } +} + +export default InversePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/inverse/inverse-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/inverse/inverse-postfxfrag.js new file mode 100644 index 000000000..d1952c76e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/inverse/inverse-postfxfrag.js @@ -0,0 +1,23 @@ +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform float intensity; + +void main (void) { + vec4 front = texture2D(uMainSampler, outTexCoord); + vec3 inverse = vec3(front.a - front.rgb); + gl_FragColor = vec4(mix(front.rgb, inverse, intensity), front.a); +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/GenerateKernels.js b/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/GenerateKernels.js new file mode 100644 index 000000000..828ddcf1d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/GenerateKernels.js @@ -0,0 +1,13 @@ +var GenerateKernels = function (blur, quality, out) { + if (out === undefined) { + out = []; + } + + out.length = quality; + for (var i = quality; i > 0; i--) { + out[i] = blur * (i / quality); + } + return out; +} + +export default GenerateKernels; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/KawaseBlurDrawer.js b/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/KawaseBlurDrawer.js new file mode 100644 index 000000000..647d0b1e4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/KawaseBlurDrawer.js @@ -0,0 +1,40 @@ +import Drawer from '../utils/drawer/Drawer.js'; + +class KawaseBlurDrawer extends Drawer { + draw(startFrame, returnLastFrame) { + var self = this.postFXPipeline; + var shader = this.shader; + + var sourceFrame = startFrame; + var targetFrame = this.getAnotherFrame(sourceFrame); + var returnFrame; + + var uvX = self.pixelWidth / self.renderer.width; + var uvY = self.pixelHeight / self.renderer.height; + var offset, uOffsetX, uOffsetY; + for (var i = 0, last = self._quality - 1; i <= last; i++) { + // Set uniforms + offset = self._kernels[i] + 0.5; + uOffsetX = offset * uvX; + uOffsetY = offset * uvY; + self.set2f('uOffset', uOffsetX, uOffsetY, shader); + // Bind and draw + if (i < last) { + self.bindAndDraw(sourceFrame, targetFrame, true, true, shader); + sourceFrame = targetFrame; + targetFrame = this.getAnotherFrame(sourceFrame); + } else { // Last step + if (returnLastFrame) { + self.bindAndDraw(sourceFrame, targetFrame, true, true, shader); + returnFrame = targetFrame; + } else { + self.bindAndDraw(sourceFrame, null, true, true, shader); + } + } + } + + return returnFrame; + } +} + +export default KawaseBlurDrawer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/KawaseBlurFilterPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/KawaseBlurFilterPostFxPipeline.d.ts new file mode 100644 index 000000000..29f095f94 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/KawaseBlurFilterPostFxPipeline.d.ts @@ -0,0 +1,25 @@ +// import * as Phaser from 'phaser'; + +export default KawaseBlurFilterPostFxPipeline; + +declare namespace KawaseBlurFilterPostFxPipeline { + interface IConfig { + blur?: number, + quality?: number, + pixelWidth?: number, + pixelHeight?: number + } +} + +declare class KawaseBlurFilterPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON(o?: KawaseBlurFilterPostFxPipeline.IConfig): this; + + setBlur(value: number): this; + blur: number; + + setPixelSize(width: number, height?: number): this; + setPixelWidth(value: number): this; + setPixelHeight(value: number): this; + pixelWidth: number; + pixelHeight: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/KawaseBlurFilterPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/KawaseBlurFilterPostFxPipeline.js new file mode 100644 index 000000000..fa3042055 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/KawaseBlurFilterPostFxPipeline.js @@ -0,0 +1,125 @@ +import FragSrc from './kawaseblurFilter-postfxfrag.js'; +import GenerateKernels from './GenerateKernels.js'; +import KawaseBlurDrawer from './KawaseBlurDrawer.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; + +class KawaseBlurFilterPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexKawaseBlurFilterPostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.drawer = new KawaseBlurDrawer(this); + this._kernels = [0]; + this._blur = 0; + this._quality = 1; + this.pixelWidth = 1; // width of pixel wo resolution + this.pixelHeight = 1; // height of pixel wo resolution + } + + resetFromJSON(o) { + var blur = GetValue(o, 'blur', 4); + if (typeof(blur) === 'number') { + this.setBlur(blur); + this.setQuality(GetValue(o, 'quality', 3)); + } else { + this.setKernela(blur); + } + + this.setPixelSize(GetValue(o, 'pixelWidth', 1), GetValue(o, 'pixelHeight', 1)); + return this; + } + + onPreRender() { + } + + onDraw(renderTarget) { + this.drawer.draw(this.drawer.init(renderTarget)); + } + + // blur + get blur() { + return this._blur; + } + + set blur(value) { + if (this._blur === value) { + return; + } + + this._blur = value; + GenerateKernels(this._blur, this._quality, this._kernels); + } + + setBlur(value) { + this.blur = value; + return this; + } + + // quality + get quality() { + return this._quality; + } + + set quality(value) { + if (this._quality === value) { + return; + } + + this._quality = value; + GenerateKernels(this._blur, this._quality, this._kernels); + } + + setQuality(value) { + this.quality = value; + return this; + } + + // kernels + get kernels() { + return this._kernels; + } + + set kernels(value) { + if (value === undefined) { + value = [0]; + } + + this._kernels = value; + this._quality = value.length; + this._blur = Math.max(...value); + } + + setKernela(value) { + this.kernels = value; + return this; + } + + // pixelWidth + setPixelWidth(value) { + this.pixelWidth = value; + return this; + } + + // pixelHeight + setPixelHeight(value) { + this.pixelHeight = value; + return this; + } + + setPixelSize(width, height) { + if (height === undefined) { + height = width; + } + this.pixelWidth = width; + this.pixelHeight = height; + return this; + } +} + +export default KawaseBlurFilterPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/kawaseblurFilter-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/kawaseblurFilter-postfxfrag.js new file mode 100644 index 000000000..e4337c430 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/kawaseblur/kawaseblurFilter-postfxfrag.js @@ -0,0 +1,38 @@ +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform vec2 uOffset; + +void main (void) { + vec4 color = vec4(0.0); + + // Sample top left pixel + color += texture2D(uMainSampler, vec2(outTexCoord.x - uOffset.x, outTexCoord.y + uOffset.y)); + + // Sample top right pixel + color += texture2D(uMainSampler, vec2(outTexCoord.x + uOffset.x, outTexCoord.y + uOffset.y)); + + // Sample bottom right pixel + color += texture2D(uMainSampler, vec2(outTexCoord.x + uOffset.x, outTexCoord.y - uOffset.y)); + + // Sample bottom left pixel + color += texture2D(uMainSampler, vec2(outTexCoord.x - uOffset.x, outTexCoord.y - uOffset.y)); + + // Average + color *= 0.25; + + gl_FragColor = color; +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/outline/OutlinePostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/outline/OutlinePostFxPipeline.d.ts new file mode 100644 index 000000000..f297ab38e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/outline/OutlinePostFxPipeline.d.ts @@ -0,0 +1,12 @@ +// import * as Phaser from 'phaser'; + +export default class OutlinePostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + setThickness(value: number): this; + thickness: number; + + setOutlineColor(value: number | Phaser.Types.Display.ColorObject): this; + outlineColor: Phaser.Display.Color; + + static setQuality(quality: number): void; + static getQuality(): number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/outline/OutlinePostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/outline/OutlinePostFxPipeline.js new file mode 100644 index 000000000..a2f54d25c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/outline/OutlinePostFxPipeline.js @@ -0,0 +1,69 @@ +import GetFrag from './outline-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; +const IntegerToRGB = Phaser.Display.Color.IntegerToRGB; +const Color = Phaser.Display.Color; + +var Quality = 0.1; +var FragSrc = GetFrag(Quality); +class OutlinePostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexOutlinePostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.thickness = 0; + this._outlineColor = new Color(); + } + + resetFromJSON(o) { + this.setThickness(GetValue(o, 'thickness', 3)); + this.setOutlineColor(GetValue(o, 'outlineColor', 0xffffff)); + return this; + } + + onPreRender() { + this.set1f('thickness', this.thickness); + this.set3f('outlineColor', this._outlineColor.redGL, this._outlineColor.greenGL, this._outlineColor.blueGL); + this.set2f('texSize', this.renderer.width, this.renderer.height); + } + + setThickness(value) { + this.thickness = value; + return this; + } + + get outlineColor() { + return this._outlineColor; + } + + set outlineColor(value) { + if (typeof (value) === 'number') { + value = IntegerToRGB(value); + } + this._outlineColor.setFromRGB(value); + } + + setOutlineColor(value) { + this.outlineColor = value; + return this; + } + + static setQuality(value) { + if (Quality === value) { + return; + } + Quality = value; + FragSrc = GetFrag(value); + } + + static getQuality() { + return Quality; + } +} + +export default OutlinePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/outline/outline-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/outline/outline-postfxfrag.js new file mode 100644 index 000000000..f4f0d0e15 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/outline/outline-postfxfrag.js @@ -0,0 +1,54 @@ +// Reference: https://github.com/pixijs/pixi-filters/blob/master/filters/outline/src/outline.frag + +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform vec2 texSize; +uniform float thickness; +uniform vec3 outlineColor; + +const float DOUBLE_PI = 3.14159265358979323846264 * 2.; + +void main() { + vec4 front = texture2D(uMainSampler, outTexCoord); + + if (thickness > 0.0) { + vec2 mag = vec2(thickness/texSize.x, thickness/texSize.y); + vec4 curColor; + float maxAlpha = front.a; + vec2 offset; + for (float angle = 0.; angle < DOUBLE_PI; angle += #{angleStep}) { + offset = vec2(mag.x * cos(angle), mag.y * sin(angle)); + curColor = texture2D(uMainSampler, outTexCoord + offset); + maxAlpha = max(maxAlpha, curColor.a); + } + vec3 resultColor = front.rgb + (outlineColor.rgb * (1. - front.a)) * maxAlpha; + gl_FragColor = vec4(resultColor, maxAlpha); + } else { + gl_FragColor = front; + } +} +`; + +const MAX_SAMPLES = 100; +const MIN_SAMPLES = 1; +var GetFrag = function (quality) { + if (quality === undefined) { + quality = 0.1; + } + var samples = Math.max((quality * MAX_SAMPLES), MIN_SAMPLES); + var angleStep = (Math.PI * 2 / samples).toFixed(7); + return frag.replace(/\#\{angleStep\}/, angleStep); +} + +export default GetFrag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/pixelation/PixelationPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/pixelation/PixelationPostFxPipeline.d.ts new file mode 100644 index 000000000..d097ea6d2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/pixelation/PixelationPostFxPipeline.d.ts @@ -0,0 +1,10 @@ +// import * as Phaser from 'phaser'; + +export default class PixelationPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + setPixelSize(width: number, height?: number): this; + setPixelWidth(value: number): this; + setPixelHeight(value: number): this; + pixelWidth: number; + pixelHeight: number; + pixelSize: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/pixelation/PixelationPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/pixelation/PixelationPostFxPipeline.js new file mode 100644 index 000000000..82c49a4a1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/pixelation/PixelationPostFxPipeline.js @@ -0,0 +1,63 @@ +import FragSrc from './pixelation-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; + + +class PixelationPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexPixelationPostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.pixelWidth = 4; // width of pixel wo resolution + this.pixelHeight = 4; // height of pixel wo resolution + } + + resetFromJSON(o) { + var pixelSize = GetValue(o, 'pixelSize', 4); + this.setPixelSize(GetValue(o, 'pixelWidth', pixelSize), GetValue(o, 'pixelHeight', pixelSize)); + return this; + } + + onPreRender() { + this.set2f('pixelSize', this.pixelWidth, this.pixelHeight); + this.set2f('texSize', this.renderer.width, this.renderer.height); + } + + // pixelWidth + setPixelWidth(value) { + this.pixelWidth = value; + return this; + } + + // pixelHeight + setPixelHeight(value) { + this.pixelHeight = value; + return this; + } + + setPixelSize(width, height) { + if (height === undefined) { + height = width; + } + this.pixelWidth = width; + this.pixelHeight = height; + return this; + } + + get pixelSize() { + return (this.pixelWidth + this.pixelHeight) / 2; + } + + set pixelSize(value) { + this.pixelWidth = value; + this.pixelHeight = value; + } + +} + +export default PixelationPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/pixelation/pixelation-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/pixelation/pixelation-postfxfrag.js new file mode 100644 index 000000000..d19a88b8a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/pixelation/pixelation-postfxfrag.js @@ -0,0 +1,33 @@ +// reference : https://www.geeks3d.com/20101029/shader-library-pixelation-post-processing-effect-glsl/ + +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform vec2 texSize; +uniform vec2 pixelSize; + +void main (void) { + if ((pixelSize.x > 0.0) || (pixelSize.y > 0.0)) { + vec2 dxy = pixelSize/texSize; + vec2 tc = vec2( + dxy.x*( floor(outTexCoord.x/dxy.x) + 0.5 ), + dxy.y*( floor(outTexCoord.y/dxy.y) + 0.5 ) + ); + gl_FragColor = texture2D(uMainSampler, tc); + } else { + gl_FragColor = texture2D(uMainSampler, outTexCoord); + } +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/shockwave/ShockwavePostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/shockwave/ShockwavePostFxPipeline.d.ts new file mode 100644 index 000000000..b36da35c9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/shockwave/ShockwavePostFxPipeline.d.ts @@ -0,0 +1,20 @@ +// import * as Phaser from 'phaser'; + +export default class ShockwavePostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + setCenter(x?: number, y?: number): this; + centerX: number; + centerY: number; + + setWaveRadius(value: number): this; + waveRadius: number; + + setWaveWidth(value: number): this; + waveWidth: number; + + setPowBaseScale(value: number): this; + powBaseScale: number; + + setPowExponent(value: number): this; + powExponent: number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/shockwave/ShockwavePostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/shockwave/ShockwavePostFxPipeline.js new file mode 100644 index 000000000..93eeb9b46 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/shockwave/ShockwavePostFxPipeline.js @@ -0,0 +1,87 @@ +import FragSrc from './shockwave-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; +const Clamp = Phaser.Math.Clamp; + +class ShockwavePostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexShockwavePostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.centerX = 0; // position wo resolution + this.centerY = 0; // position wo resolution + this.waveWidth = 20; + this.powBaseScale = 0.8; + this.powExponent = 0.1; + } + + resetFromJSON(o) { + this.setCenter(GetValue(o, 'center.x', undefined), GetValue(o, 'center.y', undefined)); + this.setWaveRadius(GetValue(o, 'waveRadius', 0)); + this.setWaveWidth(GetValue(o, 'waveWidth', 20)); + this.setPowBaseScale(GetValue(o, 'powBaseScale', 0.8)); + this.setPowExponent(GetValue(o, 'powExponent', 0.1)); + return this; + } + + onPreRender() { + this.set1f('waveRadius', this.waveRadius); + this.set1f('waveHalfWidth', this.waveWidth / 2); + this.set1f('powBaseScale', this.powBaseScale); + this.set1f('powExponent', this.powExponent); + + var texWidth = this.renderer.width, + textHeight = this.renderer.height; + this.set2f('center', this.centerX, (textHeight - this.centerY)); + this.set2f('texSize', texWidth, textHeight); + } + + // center + setCenter(x, y) { + if (x === undefined) { + x = this.renderer.width / 2; + y = this.renderer.height / 2; + } + this.centerX = x; + this.centerY = y; + return this; + } + + // waveRadius + setWaveRadius(value) { + if (value === undefined) { + value = 0; + } + this.waveRadius = value; + return this; + } + + // waveWidth + setWaveWidth(value) { + if (value === undefined) { + value = 0; + } + this.waveWidth = value; + return this; + } + + // powBaseScale + setPowBaseScale(value) { + this.powBaseScale = value; + return this; + } + + // powExponent + setPowExponent(value) { + this.powExponent = value; + return this; + } + +} + +export default ShockwavePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/shockwave/shockwave-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/shockwave/shockwave-postfxfrag.js new file mode 100644 index 000000000..1a0ce2df6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/shockwave/shockwave-postfxfrag.js @@ -0,0 +1,43 @@ +// reference : https://www.geeks3d.com/20110408/cross-stitching-post-processing-shader-glsl-filter-geexlab-pixel-bender/ + +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform vec2 texSize; +uniform vec2 center; +uniform float waveRadius; +uniform float waveHalfWidth; // 10.0 +uniform float powBaseScale; // 0.8 +uniform float powExponent; // 0.1 + +void main (void) { + if (waveHalfWidth > 0.0) { + vec2 tc = outTexCoord * texSize; + tc -= center; + + float diff = length(tc) - waveRadius; + if ((diff <= waveHalfWidth) && (diff >= -waveHalfWidth)) { + diff /= max(texSize.x, texSize.y); + float powDiff = 1.0 - pow(abs(diff*powBaseScale), powExponent); + tc += texSize * diff * powDiff; + } + + tc += center; + gl_FragColor = texture2D(uMainSampler, tc / texSize); + } else { + gl_FragColor = texture2D(uMainSampler, outTexCoord); + } +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/split/SplitPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/split/SplitPostFxPipeline.d.ts new file mode 100644 index 000000000..6d12de394 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/split/SplitPostFxPipeline.d.ts @@ -0,0 +1,27 @@ +// import * as Phaser from 'phaser'; + +export default class SplitPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + setSplit(x?: number, y?: number): this; + splitX: number; + splitY: number; + + setSpace(left?: number, right?: number, top?: number, bottom?: number): this; + spaceLeft: number; + spaceRight: number; + spaceTop: number; + spaceBottom: number; + setSplittedWidth(width?: number): this; + splittedWidth: number; + setSplittedHeight(height?: number): this; + splittedHeight: number; + + splitAtCenter(width?: number, height?: number): this; + + setAngle(angle: number): this; + setRotation(rotation: number): this; + angle: number; + rotation: number; + + setShiftEnable(enable?: boolean): this; + shiftEnable: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/split/SplitPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/split/SplitPostFxPipeline.js new file mode 100644 index 000000000..5f4d09c42 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/split/SplitPostFxPipeline.js @@ -0,0 +1,182 @@ +import FragSrc from './split-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; +const DegToRad = Phaser.Math.DegToRad; +const RadToDeg = Phaser.Math.RadToDeg; + +class SplitPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexSplitPostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.splitX = 0; + this.splitY = 0; + this.spaceLeft = 0; + this.spaceRight = 0; + this.spaceTop = 0; + this.spaceBottom = 0; + this.rotation = 0; + this.shiftEnable = true; + } + + resetFromJSON(o) { + var splittedWidth = GetValue(o, 'width', undefined); + if (splittedWidth === undefined) { + this.spaceLeft = GetValue(o, 'left', 0); + this.spaceRight = GetValue(o, 'right', 0); + } else { + this.splittedWidth = splittedWidth; + } + + var splittedHeight = GetValue(o, 'height', undefined); + if (splittedHeight === undefined) { + this.spaceTop = GetValue(o, 'top', 0); + this.spaceBottom = GetValue(o, 'bottom', 0); + } else { + this.splittedHeight = splittedHeight; + } + + this.splitX = GetValue(o, 'x', this.renderer.width / 2); + this.splitY = GetValue(o, 'Y', this.renderer.height / 2); + + var rotation = GetValue(o, 'rotation', undefined); + if (rotation === undefined) { + this.setAngle(GetValue(o, 'angle', 0)); + } else { + this.setRotation(rotation); + } + + this.shiftEnable = GetValue(o, 'shiftEnable', true); + return this; + } + + onPreRender() { + var texWidth = this.renderer.width, + textHeight = this.renderer.height; + this.set2f('split', this.splitX, (textHeight - this.splitY)); + this.set1f('angle', this.rotation); + this.set2f('texSize', texWidth, textHeight); + + this.set1f('spaceLeft', this.spaceLeft); + this.set1f('spaceRight', this.spaceRight); + this.set1f('spaceTop', this.spaceTop); + this.set1f('spaceBottom', this.spaceBottom); + + this.set1f('shiftEnable', (this.shiftEnable) ? 1 : 0); + } + + // split + setSplit(x, y) { + if (x === undefined) { + x = 0; + } + if (y === undefined) { + y = 0; + } + + this.splitX = x; + this.splitY = y; + return this; + } + + splitAtCenter(width, height) { + this.setSplit(this.renderer.width / 2, this.renderer.height / 2) + if (width !== undefined) { + this.setSplittedWidth(width); + } + if (height !== undefined) { + this.setSplittedHeight(height); + } + return this; + } + + // rotation + setRotation(value) { + this.rotation = value; + return this; + } + + get angle() { + return RadToDeg(this.rotation); + } + + set angle(value) { + this.rotation = DegToRad(value); + } + + setAngle(value) { + this.angle = value; + return this; + } + + // space + setSpace(left, right, top, bottom) { + if (left === undefined) { + left = 0; + } + if (right === undefined) { + right = 0; + } + if (top === undefined) { + top = 0; + } + if (bottom === undefined) { + bottom = 0; + } + this.spaceLeft = left; + this.spaceRight = right; + this.spaceTop = top; + this.spaceBottom = bottom; + return this; + } + + get splittedWidth() { + return this.spaceLeft + this.spaceRight; + } + + set splittedWidth(value) { + this.spaceLeft = value / 2; + this.spaceRight = this.spaceLeft; + } + + setSplittedWidth(width) { + if (width === undefined) { + width = 0; + } + this.splittedWidth = width; + return this; + } + + get splittedHeight() { + return this.spaceTop + this.spaceBottom; + } + + set splittedHeight(value) { + this.spaceTop = value / 2; + this.spaceBottom = this.spaceTop; + } + + setSplittedHeight(height) { + if (height === undefined) { + height = 0; + } + this.splittedHeight = height; + return this; + } + + // shiftEnable + setShiftEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.shiftEnable = enable; + return true; + } +} + +export default SplitPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/split/split-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/split/split-postfxfrag.js new file mode 100644 index 000000000..0c1e2719c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/split/split-postfxfrag.js @@ -0,0 +1,56 @@ +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform vec2 texSize; +uniform vec2 split; +uniform float spaceLeft; +uniform float spaceRight; +uniform float spaceTop; +uniform float spaceBottom; +uniform float angle; +uniform float shiftEnable; + +vec2 rotate(vec2 uv, float angle) { + float s = sin(angle); + float c = cos(angle); + return vec2( + uv.x * c + uv.y * s, + uv.y * c - uv.x * s + ); +} + +void main (void) { + vec2 tc = outTexCoord * texSize; + tc -= split; + tc = rotate(tc, -angle); + + if ( + ((tc.x > -spaceLeft) && (tc.x < spaceRight)) || + ((tc.y > -spaceTop) && (tc.y < spaceBottom)) + ) { + gl_FragColor = vec4(0,0,0,0); + } else { + if (shiftEnable > 0.0) { + tc.x += (tc.x < 0.0)? spaceLeft: -spaceRight; + tc.y += (tc.y < 0.0)? spaceTop: -spaceBottom; + } + + tc = rotate(tc, angle); + tc += split; + gl_FragColor = texture2D(uMainSampler, tc / texSize); + } + +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/swirl/SwirlPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/swirl/SwirlPostFxPipeline.d.ts new file mode 100644 index 000000000..d0984fb57 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/swirl/SwirlPostFxPipeline.d.ts @@ -0,0 +1,15 @@ +// import * as Phaser from 'phaser'; + +export default class SwirlPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + setCenter(x: number, y?: number): this; + centerX: number; + centerY: number; + + setRotation(radians: number): this; + rotation: number; + setAngle(degrees: number): this; + angle: number; + + setRadius(value: number): this; + radius: number; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/swirl/SwirlPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/swirl/SwirlPostFxPipeline.js new file mode 100644 index 000000000..461d14166 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/swirl/SwirlPostFxPipeline.js @@ -0,0 +1,82 @@ +import FragSrc from './swirl-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; +const DegToRad = Phaser.Math.DegToRad; +const RadToDeg = Phaser.Math.RadToDeg; + +class SwirlPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexSwirlPostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.centerX = 0; // position wo resolution + this.centerY = 0; // position wo resolution + this.radius = 0; + this.rotation = 0; + } + + resetFromJSON(o) { + this.radius = GetValue(o, 'radius', 0); + var rotation = GetValue(o, 'rotation', undefined); + if (rotation === undefined) { + this.setAngle(GetValue(o, 'angle', 0)); + } else { + this.setRotation(rotation); + } + this.setCenter(GetValue(o, 'center.x', undefined), GetValue(o, 'center.y', undefined)); + return this; + } + + onPreRender() { + this.set1f('radius', this.radius); + this.set1f('angle', this.rotation); + + var texWidth = this.renderer.width, + textHeight = this.renderer.height; + this.set2f('center', this.centerX, (textHeight - this.centerY)); + this.set2f('texSize', texWidth, textHeight); + } + + // radius + setRadius(value) { + this.radius = value; + return this; + } + + // rotation + setRotation(value) { + this.rotation = value; + return this; + } + + get angle() { + return RadToDeg(this.rotation); + } + + set angle(value) { + this.rotation = DegToRad(value); + } + + setAngle(value) { + this.angle = value; + return this; + } + + // center + setCenter(x, y) { + if (x === undefined) { + x = this.renderer.width / 2; + y = this.renderer.height / 2; + } + this.centerX = x; + this.centerY = y; + return this; + } +} + +export default SwirlPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/swirl/swirl-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/swirl/swirl-postfxfrag.js new file mode 100644 index 000000000..3924f6842 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/swirl/swirl-postfxfrag.js @@ -0,0 +1,37 @@ +// reference : https://www.geeks3d.com/20110428/shader-library-swirl-post-processing-filter-in-glsl/ + +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform vec2 texSize; +uniform vec2 center; +uniform float radius; +uniform float angle; + +void main (void) { + vec2 tc = outTexCoord * texSize; + tc -= center; + float dist = length(tc); + if (dist < radius) { + float percent = (radius - dist) / radius; + float theta = percent * percent * angle * 8.0; + float s = sin(theta); + float c = cos(theta); + tc = vec2(dot(tc, vec2(c, -s)), dot(tc, vec2(s, c))); + } + tc += center; + gl_FragColor = texture2D(uMainSampler, tc / texSize); +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/toonify/ToonifyPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/toonify/ToonifyPostFxPipeline.d.ts new file mode 100644 index 000000000..102e7774c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/toonify/ToonifyPostFxPipeline.d.ts @@ -0,0 +1,18 @@ +// import * as Phaser from 'phaser'; + +export default class ToonifyPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + setEdgeThreshold(value: number): this; + edgeThreshold: number; + + setHueLevels(value: number): this; + hueLevels: number; + + setSatLevels(value: number): this; + satLevels: number; + + setValLevels(value: number): this; + valLevels: number; + + setEdgeColor(value: number | Phaser.Types.Display.ColorObject): this; + edgeColor: Phaser.Display.Color; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/toonify/ToonifyPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/toonify/ToonifyPostFxPipeline.js new file mode 100644 index 000000000..57a1ab97d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/toonify/ToonifyPostFxPipeline.js @@ -0,0 +1,124 @@ +import FragSrc from './toonify-postfxfrag.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const GetValue = Phaser.Utils.Objects.GetValue; +const IntegerToRGB = Phaser.Display.Color.IntegerToRGB; +const Color = Phaser.Display.Color; + +class ToonifyPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexToonifyPostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.edgeThreshold = 0; + this.hueLevels = 0; + this._satLevels = 0; + this._valLevels = 0; + this._edgeColor = new Color(); + } + + resetFromJSON(o) { + this.setEdgeThreshold(GetValue(o, 'edgeThreshold', 0.2)); + this.setHueLevels(GetValue(o, 'hueLevels', 0)); + this.setSatLevels(GetValue(o, 'satLevels', 0)); + this.setValLevels(GetValue(o, 'valLevels', 0)); + this.setEdgeColor(GetValue(o, 'edgeColor', 0)); + return this; + } + + onPreRender() { + this.set1f('edgeThreshold', this.edgeThreshold); + this.set1f('hStep', this.hueStep); + this.set1f('sStep', this.satStep); + this.set1f('vStep', this.valStep); + this.set3f('edgeColor', this._edgeColor.redGL, this._edgeColor.greenGL, this._edgeColor.blueGL); + this.set2f('texSize', this.renderer.width, this.renderer.height); + } + + // edgeThreshold + setEdgeThreshold(value) { + this.edgeThreshold = value; + return this; + } + + // hueLevels + setHueLevels(value) { + this.hueLevels = value; + return this; + } + + get hueStep() { + if (this.hueLevels > 0) { + return 360 / this.hueLevels; + } else { + return 0; + } + } + + // satLevels + get satLevels() { + return this._satLevels; + } + + set satLevels(value) { + this._satLevels = value; + } + + setSatLevels(value) { + this.satLevels = value; + return this; + } + + get satStep() { + if (this._satLevels > 0) { + return 1 / this._satLevels; + } else { + return 0; + } + } + + // valLevels + get valLevels() { + return this._valLevels; + } + + set valLevels(value) { + this._valLevels = value; + } + + setValLevels(value) { + this.valLevels = value; + return this; + } + + get valStep() { + if (this._valLevels > 0) { + return 1 / this._valLevels; + } else { + return 0; + } + } + + // edgeColor + get edgeColor() { + return this._edgeColor; + } + + set edgeColor(value) { + if (typeof (value) === 'number') { + value = IntegerToRGB(value); + } + this._edgeColor.setFromRGB(value); + } + + setEdgeColor(value) { + this.edgeColor = value; + return this; + } +} + +export default ToonifyPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/toonify/toonify-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/toonify/toonify-postfxfrag.js new file mode 100644 index 000000000..fd7969472 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/toonify/toonify-postfxfrag.js @@ -0,0 +1,51 @@ +import RGBToHSV from '../utils/RGBToHSV.js'; +import HSVToRGB from '../utils/HSVToRGB.js'; +import IsEdge from '../utils/IsEdge.js'; + +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; +uniform vec2 texSize; + +// Effect parameters +uniform float edgeThreshold; // 0.2; +uniform float hStep; // 60 +uniform float sStep; // 0.15 +uniform float vStep; // 0.33 +uniform vec3 edgeColor; // (0, 0, 0); +` ++ RGBToHSV + IsEdge + HSVToRGB + +` +void main() { + vec4 front = texture2D(uMainSampler, outTexCoord); + vec3 colorLevel; + if ((hStep > 0.0) || (sStep > 0.0) || (vStep > 0.0)) { + vec3 colorHsv = RGBToHSV(front.rgb); + if (hStep > 0.0) { + colorHsv.x = min(floor((colorHsv.x / hStep) + 0.5) * hStep, 360.0); + } + if (sStep > 0.0) { + colorHsv.y = min(floor((colorHsv.y / sStep) + 0.5) * sStep, 1.0); + } + if (vStep > 0.0) { + colorHsv.z = min(floor((colorHsv.z / vStep) + 0.5) * vStep, 1.0); + } + colorLevel = HSVToRGB(colorHsv.x, colorHsv.y, colorHsv.z); + } else { + colorLevel = front.rgb; + } + + vec3 outColor = (IsEdge(outTexCoord, texSize, edgeThreshold))? edgeColor : colorLevel; + gl_FragColor = vec4(outColor, front.a); +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/AvgRGB.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/AvgRGB.js new file mode 100644 index 000000000..d7ae8a0fe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/AvgRGB.js @@ -0,0 +1,6 @@ +const frag = `\ +float AvgRGB(vec4 color) { + return (color.r + color.g + color.b)/3.0; +} +`; +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/HSLToRGB.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/HSLToRGB.js new file mode 100644 index 000000000..4067c96fd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/HSLToRGB.js @@ -0,0 +1,27 @@ + +import HUEToRGB from './HUEToRGB.js'; +const frag = HUEToRGB + +`\ +vec3 HSLToRGB(vec3 hsl) { + vec3 rgb = vec3(hsl.z); + + if (hsl.y != 0.0) { + float f2; + + if (hsl.z < 0.5) { + f2 = hsl.z * (1.0 + hsl.y); + } else { + f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z); + } + + float f1 = 2.0 * hsl.z - f2; + + rgb.r = HUEToRGB(f1, f2, hsl.x + (1.0 / 3.0)); + rgb.g = HUEToRGB(f1, f2, hsl.x); + rgb.b = HUEToRGB(f1, f2, hsl.x - (1.0 / 3.0)); + } + + return rgb; +} +`; +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/HSVToRGB.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/HSVToRGB.js new file mode 100644 index 000000000..23a5d1c6d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/HSVToRGB.js @@ -0,0 +1,48 @@ +const frag = `\ +vec3 HSVToRGB(float h, float s, float v) { + int i; + float f, p, q, t; + vec3 res; + if( s == 0.0 ) { + // achromatic (grey) + res.x = v; + res.y = v; + res.z = v; + return res; + } + + h /= 60.0; // sector 0 to 5 + i = int(floor( h )); + f = h - float(i); // factorial part of h + p = v * ( 1.0 - s ); + q = v * ( 1.0 - s * f ); + t = v * ( 1.0 - s * ( 1.0 - f ) ); + if (i == 0) { + res.x = v; + res.y = t; + res.z = p; + } else if (i == 1) { + res.x = q; + res.y = v; + res.z = p; + } else if (i == 2) { + res.x = p; + res.y = v; + res.z = t; + } else if (i == 3) { + res.x = p; + res.y = q; + res.z = v; + } else if (i == 4) { + res.x = t; + res.y = p; + res.z = v; + } else { // i == 5 + res.x = v; + res.y = p; + res.z = q; + } + return res; +} +`; +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/HUEToRGB.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/HUEToRGB.js new file mode 100644 index 000000000..b68b312b9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/HUEToRGB.js @@ -0,0 +1,24 @@ +const frag = `\ +float HUEToRGB(float f1, float f2, float hue) { + if (hue < 0.0) { + hue += 1.0; + } else if (hue > 1.0) { + hue -= 1.0; + } + + float ret; + + if ((6.0 * hue) < 1.0) { + ret = f1 + (f2 - f1) * 6.0 * hue; + } else if ((2.0 * hue) < 1.0) { + ret = f2; + } else if ((3.0 * hue) < 2.0) { + ret = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0; + } else { + ret = f1; + } + + return ret; +} +`; +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/IsEdge.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/IsEdge.js new file mode 100644 index 000000000..53afcdefb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/IsEdge.js @@ -0,0 +1,39 @@ +import AvgRGB from './AvgRGB.js'; + +const frag = +AvgRGB + +`\ +#define EDGEGAIN 5.0 +bool IsEdge(vec2 coords, vec2 texSize, float threshold) { + if (threshold > 1.0) { + return false; + } + + vec2 tc = coords * texSize; + + float pixel[9]; + int k = 0; + float delta; + + // read neighboring pixel intensities + pixel[0] = AvgRGB( texture2D( uMainSampler, (tc + vec2(float(-1), float(-1))) / texSize ) ); + pixel[1] = AvgRGB( texture2D( uMainSampler, (tc + vec2(float(-1), float( 0))) / texSize ) ); + pixel[2] = AvgRGB( texture2D( uMainSampler, (tc + vec2(float(-1), float( 1))) / texSize ) ); + pixel[3] = AvgRGB( texture2D( uMainSampler, (tc + vec2(float( 0), float(-1))) / texSize ) ); + pixel[4] = AvgRGB( texture2D( uMainSampler, (tc + vec2(float( 0), float( 0))) / texSize ) ); + pixel[5] = AvgRGB( texture2D( uMainSampler, (tc + vec2(float( 0), float( 1))) / texSize ) ); + pixel[6] = AvgRGB( texture2D( uMainSampler, (tc + vec2(float( 1), float(-1))) / texSize ) ); + pixel[7] = AvgRGB( texture2D( uMainSampler, (tc + vec2(float( 1), float( 0))) / texSize ) ); + pixel[8] = AvgRGB( texture2D( uMainSampler, (tc + vec2(float( 1), float( 1))) / texSize ) ); + + // average color differences around neighboring pixels + delta = (abs(pixel[1]-pixel[7])+ + abs(pixel[5]-pixel[3]) + + abs(pixel[0]-pixel[8])+ + abs(pixel[2]-pixel[6]) + )/4.0; + + return (clamp(delta*EDGEGAIN, 0.0, 1.0) >= threshold); +} +`; +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/RGBToHSL.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/RGBToHSL.js new file mode 100644 index 000000000..2193889b0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/RGBToHSL.js @@ -0,0 +1,43 @@ +const frag = `\ +vec3 RGBToHSL(vec3 color) { + vec3 hsl = vec3(0.0, 0.0, 0.0); + + float fmin = min(min(color.r, color.g), color.b); + float fmax = max(max(color.r, color.g), color.b); + float delta = fmax - fmin; + + hsl.z = (fmax + fmin) / 2.0; + + if (delta == 0.0) { + hsl.x = 0.0; + hsl.y = 0.0; + } else { + if (hsl.z < 0.5) { + hsl.y = delta / (fmax + fmin); + } else { + hsl.y = delta / (2.0 - fmax - fmin); + } + + float dR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta; + float dG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta; + float dB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta; + + if (color.r == fmax) { + hsl.x = dB - dG; + } else if (color.g == fmax) { + hsl.x = (1.0 / 3.0) + dR - dB; + } else if (color.b == fmax) { + hsl.x = (2.0 / 3.0) + dG - dR; + } + + if (hsl.x < 0.0) { + hsl.x += 1.0; + } else if (hsl.x > 1.0) { + hsl.x -= 1.0; + } + } + + return hsl; +} +`; +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/RGBToHSV.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/RGBToHSV.js new file mode 100644 index 000000000..e9e083f81 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/RGBToHSV.js @@ -0,0 +1,36 @@ +const frag = `\ +vec3 RGBToHSV(vec3 color) { + float minv, maxv, delta; + vec3 res; + minv = min(min(color.r, color.g), color.b); + maxv = max(max(color.r, color.g), color.b); + res.z = maxv; // v + + delta = maxv - minv; + if( maxv != 0.0 ) { + res.y = delta / maxv; // s + } else { + // s = 0, v is undefined + res.y = 0.0; + res.x = -1.0; + return res; + } + + if( color.r == maxv ) { + res.x = ( color.g - color.b ) / delta; // between yellow & magenta + } else if( color.g == maxv ) { + res.x = 2.0 + ( color.b - color.r ) / delta; // between cyan & yellow + } else { + res.x = 4.0 + ( color.r - color.g ) / delta; // between magenta & cyan + } + + res.x = res.x * 60.0; // degrees + if( res.x < 0.0 ) { + res.x = res.x + 360.0; + } + + return res; +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/drawer/Drawer.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/drawer/Drawer.js new file mode 100644 index 000000000..e2f74500a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/drawer/Drawer.js @@ -0,0 +1,41 @@ +class Drawer { + constructor(postFXPipeline, shader) { + this.postFXPipeline = postFXPipeline; + this.shader = shader; + } + + getAnotherFrame(frame) { + var self = this.postFXPipeline; + var frame1 = self.fullFrame1, + frame2 = self.fullFrame2; + return (frame === frame1) ? frame2 : frame1; + } + + init(renderTarget, startFrame) { + var self = this.postFXPipeline; + if (startFrame === undefined) { + startFrame = self.fullFrame1; + } + + self.copyFrame(renderTarget, startFrame); + return startFrame; + } + + // Override + draw(startFrame, returnLastFrame) { + // var self = this.postFXPipeline; + // var shader = this.shader; + + // var sourceFrame = startFrame; + // var targetFrame = this.getAnotherFrame(sourceFrame); + // var returnFrame; + + // ... + + // return returnFrame; + } + + +} + +export default Drawer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/drawer/alpha/AlphaDrawer.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/drawer/alpha/AlphaDrawer.js new file mode 100644 index 000000000..45758e72d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/drawer/alpha/AlphaDrawer.js @@ -0,0 +1,37 @@ +import Drawer from '../Drawer.js'; + +class AlphaDrawer extends Drawer { + constructor(postFXPipeline, shader) { + super(postFXPipeline, shader); + + this.alpha = 1; + } + + setAlpha(alpha) { + this.alpha = alpha; + return this; + } + + // Override + draw(startFrame, returnLastFrame) { + var self = this.postFXPipeline; + var shader = this.shader; + + var sourceFrame = startFrame; + var targetFrame = this.getAnotherFrame(sourceFrame); + var returnFrame; + + self.set1f('alpha', this.alpha, shader); + if (returnLastFrame) { + self.bindAndDraw(sourceFrame, targetFrame, true, true, shader); + returnFrame = targetFrame; + } else{ + self.bindAndDraw(sourceFrame, null, true, true, shader); + } + + return returnFrame; + } + +} + +export default AlphaDrawer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/drawer/alpha/alpha-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/drawer/alpha/alpha-postfxfrag.js new file mode 100644 index 000000000..565848a5c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/drawer/alpha/alpha-postfxfrag.js @@ -0,0 +1,21 @@ +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform float alpha; + +void main (void) { + gl_FragColor = texture2D(uMainSampler, outTexCoord) * alpha; +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/noise/Perlin.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/noise/Perlin.js new file mode 100644 index 000000000..ad48cdb7c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/noise/Perlin.js @@ -0,0 +1,38 @@ +// Reference: https://medium.com/neosavvy-labs/webgl-with-perlin-noise-part-1-a87b56bbc9fb +const frag = `\ +vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } +vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } +vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); } +vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; } +vec3 fade(vec3 t) { return t*t*t*(t*(t*6.0-15.0)+10.0); } +float Perlin(vec3 P) { + vec3 i0 = mod289(floor(P)), i1 = mod289(i0 + vec3(1.0)); + vec3 f0 = fract(P), f1 = f0 - vec3(1.0), f = fade(f0); + vec4 ix = vec4(i0.x, i1.x, i0.x, i1.x), iy = vec4(i0.yy, i1.yy); + vec4 iz0 = i0.zzzz, iz1 = i1.zzzz; + vec4 ixy = permute(permute(ix) + iy), ixy0 = permute(ixy + iz0), ixy1 = permute(ixy + iz1); + vec4 gx0 = ixy0 * (1.0 / 7.0), gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; + vec4 gx1 = ixy1 * (1.0 / 7.0), gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; + gx0 = fract(gx0); gx1 = fract(gx1); + vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0), sz0 = step(gz0, vec4(0.0)); + vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1), sz1 = step(gz1, vec4(0.0)); + gx0 -= sz0 * (step(0.0, gx0) - 0.5); gy0 -= sz0 * (step(0.0, gy0) - 0.5); + gx1 -= sz1 * (step(0.0, gx1) - 0.5); gy1 -= sz1 * (step(0.0, gy1) - 0.5); + vec3 g0 = vec3(gx0.x,gy0.x,gz0.x), g1 = vec3(gx0.y,gy0.y,gz0.y), + g2 = vec3(gx0.z,gy0.z,gz0.z), g3 = vec3(gx0.w,gy0.w,gz0.w), + g4 = vec3(gx1.x,gy1.x,gz1.x), g5 = vec3(gx1.y,gy1.y,gz1.y), + g6 = vec3(gx1.z,gy1.z,gz1.z), g7 = vec3(gx1.w,gy1.w,gz1.w); + vec4 norm0 = taylorInvSqrt(vec4(dot(g0,g0), dot(g2,g2), dot(g1,g1), dot(g3,g3))); + vec4 norm1 = taylorInvSqrt(vec4(dot(g4,g4), dot(g6,g6), dot(g5,g5), dot(g7,g7))); + g0 *= norm0.x; g2 *= norm0.y; g1 *= norm0.z; g3 *= norm0.w; + g4 *= norm1.x; g6 *= norm1.y; g5 *= norm1.z; g7 *= norm1.w; + vec4 nz = mix(vec4(dot(g0, vec3(f0.x, f0.y, f0.z)), dot(g1, vec3(f1.x, f0.y, f0.z)), + dot(g2, vec3(f0.x, f1.y, f0.z)), dot(g3, vec3(f1.x, f1.y, f0.z))), + vec4(dot(g4, vec3(f0.x, f0.y, f1.z)), dot(g5, vec3(f1.x, f0.y, f1.z)), + dot(g6, vec3(f0.x, f1.y, f1.z)), dot(g7, vec3(f1.x, f1.y, f1.z))), f.z); + return 2.2 * mix(mix(nz.x,nz.z,f.y), mix(nz.y,nz.w,f.y), f.x); +} +float Perlin(vec2 P) { return Perlin(vec3(P, 0.0)); } +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/noise/Turbulence.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/noise/Turbulence.js new file mode 100644 index 000000000..07787dcf8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/noise/Turbulence.js @@ -0,0 +1,17 @@ +// Reference: https://medium.com/neosavvy-labs/webgl-with-perlin-noise-part-1-a87b56bbc9fb + +import PerlinNoiseFrag from './Perlin.js'; + +const frag = `\ +${PerlinNoiseFrag} +float Turbulence(vec3 P) { + float f = 0., s = 1.; + for (int i = 0 ; i < 9 ; i++) { + f += abs(Perlin(s * P)) / s; + s *= 2.; + P = vec3(.866 * P.x + .5 * P.z, P.y + 100., -.5 * P.x + .866 * P.z); + } + return f; +}`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/utils/spritefxcontrol/Base.js b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/spritefxcontrol/Base.js new file mode 100644 index 000000000..d362ccd01 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/utils/spritefxcontrol/Base.js @@ -0,0 +1,23 @@ +class Base { + constructor(pipeline, gameObject, config) { + this.pipeline = pipeline; + this.gameObject = gameObject; + this.resetFromJSON(config); + + gameObject.once('destroy', this.destroy, this); + } + + destroy() { + this.pipeline = undefined; + this.gameObject = undefined; + } + + resetFromJSON(o) { + return this; + } + + onDrawSprite() { + } +} + +export default Base; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipeline.d.ts new file mode 100644 index 000000000..896924efc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipeline.d.ts @@ -0,0 +1,43 @@ +// import * as Phaser from 'phaser'; +export default WarpPostFxPipeline; + +declare namespace WarpPostFxPipeline { + interface IConfig { + frequencyX?: number, frequencyY?: number, + frequency?: number, + + amplitudeX?: number, amplitudeY?: number, + amplitude?: number, + + speedX?: number, speedY?: number, + speed?: number, + speedEnable?: boolean + } +} + +declare class WarpPostFxPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline { + resetFromJSON( + config?: WarpPostFxPipeline.IConfig + ): this; + + setFrequency(width: number, height?: number): this; + setFrequencyX(value: number): this; + setFrequencyY(value: number): this; + frequencyX: number; + frequencyY: number; + frequency: number; + + setAmplitude(x: number, y?: number): this; + setAmplitudeX(value: number): this; + setAmplitudeY(value: number): this; + amplitudeX: number; + amplitudeY: number; + amplitude: number; + + setSpeedX(value: number): this; + setSpeedY(value: number): this; + setSpeed(x: number, y?: number): this; + speedX: number; + speedY: number; + speed: Phaser.Math.Vector2; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipeline.js b/ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipeline.js new file mode 100644 index 000000000..a40c7a551 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipeline.js @@ -0,0 +1,162 @@ +import FragSrc from './warp-postfxfrag.js'; +import GetTickDelta from '../../utils/system/GetTickDelta.js'; + +const PostFXPipeline = Phaser.Renderer.WebGL.Pipelines.PostFXPipeline; +const Vector2 = Phaser.Math.Vector2; +const GetValue = Phaser.Utils.Objects.GetValue; +const PI2 = Math.PI * 2; + + +class WarpPostFxPipeline extends PostFXPipeline { + constructor(game) { + super({ + name: 'rexWarpPostFx', + game: game, + renderTarget: true, + fragShader: FragSrc + }); + + this.frequencyX = 10; + this.frequencyY = 10; + + this.amplitudeX = 10; + this.amplitudeY = 10; + + this.speedEnable = false; + this.now = 0; + this.speed = new Vector2(0, 0); + + } + + resetFromJSON(o) { + var frequency = GetValue(o, 'frequency', 10); + this.setFrequency(GetValue(o, 'frequencyX', frequency), GetValue(o, 'frequencyY', frequency)); + + var amplitude = GetValue(o, 'amplitude', 10); + this.setAmplitude(GetValue(o, 'amplitudeX', amplitude), GetValue(o, 'amplitudeY', amplitude)); + + var speed = GetValue(o, 'speed', 0); + this.setSpeed(GetValue(o, 'speedX', speed), GetValue(o, 'speedY', speed)); + + this.setSpeedEnable(GetValue(o, 'speedEnable', (this.speedX !== 0) || (this.speedY !== 0))); + + return this; + } + + onPreRender() { + if (this.speedEnable) { + this.now += GetTickDelta(this.game); + } + + this.set2f('frequency', this.frequencyX, this.frequencyY); + this.set2f('amplitude', this.amplitudeX, this.amplitudeY); + + this.set2f('speed', this.speed.x, this.speed.y); + this.set1f('time', this.now); + + this.set2f('texSize', this.renderer.width, this.renderer.height); + } + + // frequencyX + setFrequencyX(value) { + this.frequencyX = value; + return this; + } + + // frequencyY + setFrequencyY(value) { + this.frequencyY = value; + return this; + } + + setFrequency(width, height) { + if (height === undefined) { + height = width; + } + this.frequencyX = width; + this.frequencyY = height; + return this; + } + + get frequency() { + return (this.frequencyX + this.frequencyY) / 2; + } + + set frequency(value) { + this.frequencyX = value; + this.frequencyY = value; + } + + // amplitudeX + setAmplitudeX(value) { + this.amplitudeX = value; + return this; + } + + // amplitudeY + setAmplitudeY(value) { + this.amplitudeY = value; + return this; + } + + setAmplitude(x, y) { + if (y === undefined) { + y = x; + } + this.amplitudeX = x; + this.amplitudeY = y; + return this; + } + + get amplitude() { + return (this.amplitudeX + this.amplitudeY) / 2; + } + + set amplitude(value) { + this.amplitudeX = value; + this.amplitudeY = value; + } + + // speed + setSpeedX(value) { + this.speedX = value; + return this; + } + setSpeedY(value) { + this.speed.y = value; + return this; + } + get speedX() { + return this.speed.x; + } + set speedX(value) { + this.speed.x = value; + } + get speedY() { + return this.speed.y; + } + set speedY(value) { + this.speed.y = value; + } + + setSpeed(x, y) { + if (y === undefined) { + y = x; + } + this.speedX = x; + this.speedY = y; + return this; + } + + // Speed enable + setSpeedEnable(enable) { + if (enable === undefined) { + enable = true; + } + this.speedEnable = enable; + return this; + } + +} + +export default WarpPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipelineBehavior.d.ts b/ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipelineBehavior.d.ts new file mode 100644 index 000000000..301d00806 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipelineBehavior.d.ts @@ -0,0 +1,21 @@ +import BasePostFxPipelineBehavior from '../../utils/renderer/postfxpipeline/BasePostFxPipelineBehavior'; +import WarpPostFxPipeline from './WarpPostFxPipeline'; + +export default WarpPostFxPipelineBehavior; + +declare namespace WarpPostFxPipelineBehavior { + interface IConfig extends WarpPostFxPipeline.IConfig { + + } +} + +declare class WarpPostFxPipelineBehavior extends BasePostFxPipelineBehavior { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: WarpPostFxPipelineBehavior.IConfig + ); + + getPipeline( + config?: WarpPostFxPipelineBehavior.IConfig + ): WarpPostFxPipeline; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipelineBehavior.js b/ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipelineBehavior.js new file mode 100644 index 000000000..28ebc61a9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/warp/WarpPostFxPipelineBehavior.js @@ -0,0 +1,10 @@ +import BasePostFxPipelineBehavior from '../../utils/renderer/postfxpipeline/BasePostFxPipelineBehavior.js'; +import WarpPostFxPipeline from './WarpPostFxPipeline.js'; + +class WarpPostFxPipelineBehavior extends BasePostFxPipelineBehavior { + createPipeline(game) { + return new WarpPostFxPipeline(game); + } +} + +export default WarpPostFxPipelineBehavior; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shaders/warp/warp-postfxfrag.js b/ui/src/phaser3-rex-plugins/plugins/shaders/warp/warp-postfxfrag.js new file mode 100644 index 000000000..5db4155db --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shaders/warp/warp-postfxfrag.js @@ -0,0 +1,33 @@ +// reference : https://www.geeks3d.com/20101029/shader-library-pixelation-post-processing-effect-glsl/ + +const frag = `\ +#ifdef GL_FRAGMENT_PRECISION_HIGH +#define highmedp highp +#else +#define highmedp mediump +#endif +precision highmedp float; + +// Scene buffer +uniform sampler2D uMainSampler; +varying vec2 outTexCoord; + +// Effect parameters +uniform vec2 texSize; +uniform vec2 amplitude; +uniform vec2 frequency; +uniform vec2 speed; +uniform float time; + + +void main (void) { + vec2 dxy = frequency/texSize; + vec2 r = amplitude/texSize; + vec2 spd = speed/texSize; + vec2 angle = (outTexCoord / dxy) + (spd*time); + vec2 tc = (vec2(cos(angle.x),sin(angle.y)) * r) + outTexCoord; + gl_FragColor = texture2D(uMainSampler, tc); +} +`; + +export default frag; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shakeposition-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/shakeposition-plugin.d.ts new file mode 100644 index 000000000..aaaae1ea2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shakeposition-plugin.d.ts @@ -0,0 +1,9 @@ +import Shake from './shakeposition'; + +export default class ShakePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Shake.IConfig + ): Shake; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shakeposition-plugin.js b/ui/src/phaser3-rex-plugins/plugins/shakeposition-plugin.js new file mode 100644 index 000000000..197ac5b2b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shakeposition-plugin.js @@ -0,0 +1,19 @@ +import Shake from './shakeposition.js'; + +class ShakePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Shake(gameObject, config); + } +} + +export default ShakePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shakeposition.d.ts b/ui/src/phaser3-rex-plugins/plugins/shakeposition.d.ts new file mode 100644 index 000000000..4cd6c0bd2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shakeposition.d.ts @@ -0,0 +1,2 @@ +import Shake from './behaviors/shake/ShakePosition'; +export default Shake; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shakeposition.js b/ui/src/phaser3-rex-plugins/plugins/shakeposition.js new file mode 100644 index 000000000..060556e5d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shakeposition.js @@ -0,0 +1,2 @@ +import Shake from './behaviors/shake/ShakePosition.js'; +export default Shake; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shatterimage-plugin.js b/ui/src/phaser3-rex-plugins/plugins/shatterimage-plugin.js new file mode 100644 index 000000000..996773270 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shatterimage-plugin.js @@ -0,0 +1,30 @@ +import ShatterImageFactory from './gameobjects/mesh/shatter/image/Factory.js'; +import ShatterImageCreator from './gameobjects/mesh/shatter/image/Creator.js'; +import ShatterImage from './gameobjects/mesh/shatter/image/Image.js'; + +import ShatterRenderTextureFactory from './gameobjects/mesh/shatter/rendertexture/Factory.js'; +import ShatterRenderTextureCreator from './gameobjects/mesh/shatter/rendertexture/Creator.js'; +import ShatterRenderTexture from './gameobjects/mesh/shatter/rendertexture/RenderTexture.js'; + +import SetValue from './utils/object/SetValue.js'; + +class ShatterImagePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexShatterImage', ShatterImageFactory, ShatterImageCreator); + pluginManager.registerGameObject('rexShatterRenderTexture', ShatterRenderTextureFactory, ShatterRenderTextureCreator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.ShatterImage', ShatterImage); +SetValue(window, 'RexPlugins.GameObjects.ShatterRenderTexture', ShatterRenderTexture); + +export default ShatterImagePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shatterimage.d.ts b/ui/src/phaser3-rex-plugins/plugins/shatterimage.d.ts new file mode 100644 index 000000000..657b6565d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shatterimage.d.ts @@ -0,0 +1,2 @@ +import ShatterImage from './gameobjects/mesh/shatter/image/Image'; +export default ShatterImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shatterimage.js b/ui/src/phaser3-rex-plugins/plugins/shatterimage.js new file mode 100644 index 000000000..586221e07 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shatterimage.js @@ -0,0 +1,9 @@ +import ShatterImage from './gameobjects/mesh/shatter/image/Image.js'; +import ShatterRenderTexture from './gameobjects/mesh/shatter/rendertexture/RenderTexture.js'; + +export { + ShatterImage, + ShatterRenderTexture +} + +export default ShatterImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/ship-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/ship-plugin.d.ts new file mode 100644 index 000000000..5c590cf30 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/ship-plugin.d.ts @@ -0,0 +1,9 @@ +import Ship from './moveto'; + +export default class ShipPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Ship.IConfig + ): Ship; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/ship-plugin.js b/ui/src/phaser3-rex-plugins/plugins/ship-plugin.js new file mode 100644 index 000000000..5c45f4853 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/ship-plugin.js @@ -0,0 +1,20 @@ +import Ship from './ship.js'; + +class ShipPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Ship(gameObject, config); + } + +} + +export default ShipPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/ship.d.ts b/ui/src/phaser3-rex-plugins/plugins/ship.d.ts new file mode 100644 index 000000000..e1e8ed7d1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/ship.d.ts @@ -0,0 +1,2 @@ +import Ship from './behaviors/ship/Ship.js'; +export default Ship; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/ship.js b/ui/src/phaser3-rex-plugins/plugins/ship.js new file mode 100644 index 000000000..e1e8ed7d1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/ship.js @@ -0,0 +1,2 @@ +import Ship from './behaviors/ship/Ship.js'; +export default Ship; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shockwavepipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/shockwavepipeline-plugin.d.ts new file mode 100644 index 000000000..fb65ae609 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shockwavepipeline-plugin.d.ts @@ -0,0 +1,36 @@ +// import * as Phaser from 'phaser'; +import ShockwavePostFxPipeline from './shockwavepipeline'; + + +export default ShockwavePipelinePlugin; + +declare namespace ShockwavePipelinePlugin { + + interface IConfig { + center?: { x?: number, y?: number }, + waveRadius?: number, + waveWidth?: number, + powBaseScale?: number, + powExponent?: number, + + name?: string, + } + +} + +declare class ShockwavePipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: ShockwavePipelinePlugin.IConfig + ): ShockwavePostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): ShockwavePostFxPipeline | ShockwavePostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shockwavepipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/shockwavepipeline-plugin.js new file mode 100644 index 000000000..2a85870eb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shockwavepipeline-plugin.js @@ -0,0 +1,14 @@ +import ShockwavePostFxPipeline from './shockwavepipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class ShockwavePipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(ShockwavePostFxPipeline, 'rexShockwavePostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.ShockwavePostFx', ShockwavePostFxPipeline); + +export default ShockwavePipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shockwavepipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/shockwavepipeline.d.ts new file mode 100644 index 000000000..f08a95dcf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shockwavepipeline.d.ts @@ -0,0 +1,2 @@ +import ShockwavePostFxPipeline from './shaders/shockwave/ShockwavePostFxPipeline'; +export default ShockwavePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/shockwavepipeline.js b/ui/src/phaser3-rex-plugins/plugins/shockwavepipeline.js new file mode 100644 index 000000000..b67e6bfa5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/shockwavepipeline.js @@ -0,0 +1,2 @@ +import ShockwavePostFxPipeline from './shaders/shockwave/ShockwavePostFxPipeline.js'; +export default ShockwavePostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/slider-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/slider-plugin.d.ts new file mode 100644 index 000000000..a3244f83c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/slider-plugin.d.ts @@ -0,0 +1,9 @@ +import Slider from './slider'; + +export default class SliderPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Slider.IConfig + ): Slider; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/slider-plugin.js b/ui/src/phaser3-rex-plugins/plugins/slider-plugin.js new file mode 100644 index 000000000..a8babc3d5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/slider-plugin.js @@ -0,0 +1,20 @@ +import Slider from './slider.js'; + +class SliderPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Slider(gameObject, config); + } + +} + +export default SliderPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/slider.d.ts b/ui/src/phaser3-rex-plugins/plugins/slider.d.ts new file mode 100644 index 000000000..3657b1b32 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/slider.d.ts @@ -0,0 +1,2 @@ +import Slider from './input/slider/Slider'; +export default Slider; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/slider.js b/ui/src/phaser3-rex-plugins/plugins/slider.js new file mode 100644 index 000000000..90f3a2a4c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/slider.js @@ -0,0 +1,2 @@ +import Slider from './input/slider/Slider.js'; +export default Slider; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/soundfade-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/soundfade-plugin.d.ts new file mode 100644 index 000000000..38f05b918 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/soundfade-plugin.d.ts @@ -0,0 +1,7 @@ +import SoundFade from './soundfade'; + +export default class SoundFadePlugin extends Phaser.Plugins.BasePlugin { + fadeIn: typeof SoundFade.fadeIn; + fadeOut: typeof SoundFade.fadeOut; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/soundfade-plugin.js b/ui/src/phaser3-rex-plugins/plugins/soundfade-plugin.js new file mode 100644 index 000000000..4f474b9e8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/soundfade-plugin.js @@ -0,0 +1,20 @@ +import SoundFade from './soundfade.js'; + +class SoundFadePlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +// mixin +Object.assign( + SoundFadePlugin.prototype, + SoundFade +); + +export default SoundFadePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/soundfade.d.ts b/ui/src/phaser3-rex-plugins/plugins/soundfade.d.ts new file mode 100644 index 000000000..9b57b0272 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/soundfade.d.ts @@ -0,0 +1,9 @@ +import FadeIn from './audio/fade/FadeIn'; +import FadeOut from './audio/fade/FadeOut'; + +declare var SoundFade: { + fadeIn: typeof FadeIn, + fadeOut: typeof FadeOut +} + +export default SoundFade; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/soundfade.js b/ui/src/phaser3-rex-plugins/plugins/soundfade.js new file mode 100644 index 000000000..b03c8f69e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/soundfade.js @@ -0,0 +1,7 @@ +import FadeIn from './audio/fade/FadeIn.js'; +import FadeOut from './audio/fade/FadeOut.js'; + +export default { + fadeIn: FadeIn, + fadeOut: FadeOut +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/spiralcurve-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/spiralcurve-plugin.d.ts new file mode 100644 index 000000000..1f1e5ce08 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/spiralcurve-plugin.d.ts @@ -0,0 +1,15 @@ +import SpiralCurve from './spiralcurve'; + +export default class SpiralCurvePlugin extends Phaser.Plugins.BasePlugin { + add( + config?: SpiralCurve.IConfig + ): SpiralCurve; + + add( + x?: number, y?: number, + startRadius?: number, endRadius?: number, + startAngle?: number, endAngle?: number, + rotation?: number + ): SpiralCurve + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/spiralcurve-plugin.js b/ui/src/phaser3-rex-plugins/plugins/spiralcurve-plugin.js new file mode 100644 index 000000000..60ce96256 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/spiralcurve-plugin.js @@ -0,0 +1,22 @@ +import SpiralCurve from './spiralcurve.js'; +import SetValue from './utils/object/SetValue.js'; + +class SpiralCurvePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(x, y, startRadius, endRadius, startAngle, endAngle, rotation) { + return new SpiralCurve(x, y, startRadius, endRadius, startAngle, endAngle, rotation); + } +} + +SetValue(window, 'RexPlugins.Curve.SpiralCurve', SpiralCurve); + +export default SpiralCurvePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/spiralcurve.d.ts b/ui/src/phaser3-rex-plugins/plugins/spiralcurve.d.ts new file mode 100644 index 000000000..f43b31a7a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/spiralcurve.d.ts @@ -0,0 +1,2 @@ +import SpiralCurve from './curve/SpiralCurve'; +export default SpiralCurve; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/spiralcurve.js b/ui/src/phaser3-rex-plugins/plugins/spiralcurve.js new file mode 100644 index 000000000..da1408349 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/spiralcurve.js @@ -0,0 +1,2 @@ +import SpiralCurve from './curve/SpiralCurve.js'; +export default SpiralCurve; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/splitpipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/splitpipeline-plugin.d.ts new file mode 100644 index 000000000..943506048 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/splitpipeline-plugin.d.ts @@ -0,0 +1,36 @@ +// import * as Phaser from 'phaser'; +import SplitPostFxPipeline from './splitpipeline'; + +export default SplitPipelinePlugin; + +declare namespace SplitPipelinePlugin { + + interface IConfig { + x?: number, y?: number, + + width?: number, height?: number, + left?: number, right?: number, top?: number, bottom?: number, + + shiftEnable?: boolean, + + name?: string + } + +} + +declare class SplitPipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: SplitPipelinePlugin.IConfig + ): SplitPostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): SplitPostFxPipeline | SplitPostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/splitpipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/splitpipeline-plugin.js new file mode 100644 index 000000000..50505f39b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/splitpipeline-plugin.js @@ -0,0 +1,14 @@ +import SplitPostFxPipeline from './splitpipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class SplitPipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(SplitPostFxPipeline, 'rexSplitPostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.SplitPostFx', SplitPostFxPipeline); + +export default SplitPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/splitpipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/splitpipeline.d.ts new file mode 100644 index 000000000..7e4f4fb5e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/splitpipeline.d.ts @@ -0,0 +1,2 @@ +import SplitPostFxPipeline from './shaders/split/SplitPostFxPipeline'; +export default SplitPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/splitpipeline.js b/ui/src/phaser3-rex-plugins/plugins/splitpipeline.js new file mode 100644 index 000000000..05413c59e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/splitpipeline.js @@ -0,0 +1,2 @@ +import SplitPostFxPipeline from './shaders/split/SplitPostFxPipeline.js'; +export default SplitPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/statemanager-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/statemanager-plugin.d.ts new file mode 100644 index 000000000..392c78963 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/statemanager-plugin.d.ts @@ -0,0 +1,8 @@ +import StateManager from './statemanager.js'; + +export default class StateManagerPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: StateManager.IConfig + ): StateManager; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/statemanager-plugin.js b/ui/src/phaser3-rex-plugins/plugins/statemanager-plugin.js new file mode 100644 index 000000000..49dfc6d6e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/statemanager-plugin.js @@ -0,0 +1,23 @@ +import StateManager from './statemanager.js'; +import SetValue from './utils/object/SetValue.js'; + +class StateManagerPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new StateManager(config); + } + +} + +SetValue(window, 'RexPlugins.StateManager', StateManager); + +export default StateManagerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/statemanager.d.ts b/ui/src/phaser3-rex-plugins/plugins/statemanager.d.ts new file mode 100644 index 000000000..47bd5ac94 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/statemanager.d.ts @@ -0,0 +1,2 @@ +import StateManager from './logic/statemanager/StateManager'; +export default StateManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/statemanager.js b/ui/src/phaser3-rex-plugins/plugins/statemanager.js new file mode 100644 index 000000000..43e9e82e7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/statemanager.js @@ -0,0 +1,2 @@ +import StateManager from './logic/statemanager/StateManager.js'; +export default StateManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/step-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/step-plugin.d.ts new file mode 100644 index 000000000..9e2a97908 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/step-plugin.d.ts @@ -0,0 +1,9 @@ +import Step from './step'; + +export default class StepPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: Step.IConfig + ): Step; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/step-plugin.js b/ui/src/phaser3-rex-plugins/plugins/step-plugin.js new file mode 100644 index 000000000..21027f1b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/step-plugin.js @@ -0,0 +1,19 @@ +import Step from './step.js'; + +class StepPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new Step(gameObject, config); + } +} + +export default StepPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/step.d.ts b/ui/src/phaser3-rex-plugins/plugins/step.d.ts new file mode 100644 index 000000000..096de3ae9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/step.d.ts @@ -0,0 +1,2 @@ +import Step from './behaviors/step/Step.js'; +export default Step; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/step.js b/ui/src/phaser3-rex-plugins/plugins/step.js new file mode 100644 index 000000000..096de3ae9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/step.js @@ -0,0 +1,2 @@ +import Step from './behaviors/step/Step.js'; +export default Step; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Clear.js b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Clear.js new file mode 100644 index 000000000..d4d6ecba5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Clear.js @@ -0,0 +1,8 @@ +import ClearObj from '../../../utils/object/Clear.js'; + +var Clear = function () { + ClearObj(this.cacheHeaders); + return this.store.clear(); +} + +export default Clear; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/DataProcessMethods.js b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/DataProcessMethods.js new file mode 100644 index 000000000..b8c2e0b22 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/DataProcessMethods.js @@ -0,0 +1,17 @@ +import LZString from '../../../utils/lzstring/lz-string.min.js'; + +export default { + toSaveData(data) { + if (this.zipMode) { + data = LZString.compress(JSON.stringify(data)); + } + return data; + }, + + toLoadData(data) { + if (this.zipMode) { + data = JSON.parse(LZString.decompress(data)) + } + return data; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Delete.js b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Delete.js new file mode 100644 index 000000000..3263b82fe --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Delete.js @@ -0,0 +1,10 @@ +import { GetHeaderKey, GetContentKey } from './GetKey.js'; +import RemoveItems from '../utils/RemoveItems.js'; + +var Delete = function (fileID) { + var headerKey = GetHeaderKey(fileID); + var contentKey = GetContentKey(fileID); + return RemoveItems([headerKey, contentKey]); +} + +export default Delete; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Files.d.ts b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Files.d.ts new file mode 100644 index 000000000..110b7f1d8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Files.d.ts @@ -0,0 +1,51 @@ +export default Files; + +declare namespace Files { + interface IConfig { + name?: string; + zip?: boolean; + } + + interface IBaseData { + fileID?: string; + + // Other properties + [name: string]: unknown; + } + + interface IHeader extends IBaseData { } + + interface IContent extends IBaseData { } +} + +declare class Files { + constructor( + config: Files.IConfig + ); + + save( + fileID: string, + header?: Files.IHeader, + content?: Files.IContent, + updateMode?: boolean + ): Promise< + { fileID: string } + >; + + loadHeaders( + ): Promise< + { + headers: { [fileID: string]: Files.IHeader } + } + >; + + load( + fileID: string + ): Promise< + { + fileID: string, + header: Files.IHeader, + content: Files.IContent + } + >; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Files.js b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Files.js new file mode 100644 index 000000000..fdda07bbc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Files.js @@ -0,0 +1,35 @@ +import localforage from 'localforage'; +import GetValue from '../../../utils/object/GetValue.js'; +import Save from './Save.js'; +import Load from './Load.js'; +import LoadHeaders from './LoadHeaders.js'; +import Delete from './Delete.js'; +import Clear from './Clear.js'; +import DataProcessMethods from './DataProcessMethods.js'; + +class Files { + constructor(config) { + this.store = localforage.createInstance({ + name: GetValue(config, 'name', 'files') + }); + + this.zipMode = GetValue(config, 'zip', true); + this.cacheHeaders = {}; + } +} + +var methods = { + save: Save, + load: Load, + loadHeaders: LoadHeaders, + delete: Delete, + clear: Clear, +} + +Object.assign( + Files.prototype, + methods, + DataProcessMethods +); + +export default Files; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/GetKey.js b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/GetKey.js new file mode 100644 index 000000000..d3bffff50 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/GetKey.js @@ -0,0 +1,25 @@ +var GetHeaderKey = function (fileID) { + return `H-${fileID}`; +} + +var GetContentKey = function (fileID) { + return `C-${fileID}`; +} + +var IsHeaderKey = function (key) { + return key.charAt(0) === 'H'; +} + +var IsContentKey = function (key) { + return key.charAt(0) === 'C'; +} + +var GetFileID = function (key) { + return key.split('-')[1]; +} + +export { + GetHeaderKey, GetContentKey, + IsHeaderKey, IsContentKey, + GetFileID +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Load.js b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Load.js new file mode 100644 index 000000000..049a64056 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Load.js @@ -0,0 +1,27 @@ +import { GetHeaderKey, GetContentKey } from './GetKey.js'; +import GetItems from '../utils/GetItems.js'; + +var Load = function (fileID) { + var headerKey = GetHeaderKey(fileID); + var contentKey = GetContentKey(fileID); + var self = this; + return GetItems([headerKey, contentKey], this.store) + .then(function (data) { + var header = self.toLoadData(data[headerKey]); + var content = self.toLoadData(data[contentKey]); + self.cacheHeaders[fileID] = header; + return Promise.resolve({ + fileID: fileID, + header: header, + content: content + }) + }) + .catch(function (error) { + return Promise.reject({ + fileID: fileID, + error: error + }) + }) +} + +export default Load; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/LoadHeaders.js b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/LoadHeaders.js new file mode 100644 index 000000000..bfb478fa3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/LoadHeaders.js @@ -0,0 +1,28 @@ +import { IsHeaderKey, GetFileID } from './GetKey.js'; +import GetItems from '../utils/GetItems.js'; +import Clear from '../../../utils/object/Clear.js'; + +var LoadHeaders = function () { + var self = this; + return this.store.keys() + .then(function (keys) { + return GetItems(keys.filter(IsHeaderKey), self.store); + }) + .then(function (data) { + Clear(self.cacheHeaders); + for (var key in data) { + self.cacheHeaders[GetFileID(key)] = self.toLoadData(data[key]); + } + + return Promise.resolve({ + headers: self.cacheHeaders + }); + }) + .catch(function () { + return Promise.reject({ + error: error, + }); + }); +} + +export default LoadHeaders; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Save.js b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Save.js new file mode 100644 index 000000000..f2dd0fb5f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/files/Save.js @@ -0,0 +1,73 @@ +import { GetHeaderKey, GetContentKey } from './GetKey.js'; +import GetItems from '../utils/GetItems.js'; +import SetItems from '../utils/SetItems.js'; + +var Save = function (fileID, header, content, updateMode) { + if (typeof (content) === 'boolean') { + updateMode = content; + content = undefined; + } + if (updateMode === undefined) { + updateMode = false; + } + + if (header === undefined) { + header = {}; + } + + header.fileID = fileID; + + if (content) { + content.fileID = fileID; + } + + var headerKey = GetHeaderKey(fileID); + var contentKey = GetContentKey(fileID); + var self = this; + var prevHeader, prevContent; + return new Promise(function (resolve, reject) { + if (updateMode) { + GetItems([headerKey, contentKey]) + .then(function (data) { + prevHeader = self.toLoadData(data[headerKey]); + prevContent = self.toLoadData(data[contentKey]); + resolve(); + }) + } else { + resolve(); + } + }) + .then(function () { + if (prevHeader && header) { + header = Object.assign(prevHeader, header); + } + if (prevContent && content) { + content = Object.assign(prevContent, content); + } + + self.cacheHeaders[fileID] = header; + + var data = {}; + if (header) { + data[headerKey] = self.toSaveData(header); + } + if (content) { + data[contentKey] = self.toSaveData(content); + } + + return SetItems(data, self.store); + }) + .then(function () { + return Promise.resolve({ + fileID: fileID + }); + }) + .catch(function (error) { + return Promise.reject({ + error: error, + fileID: fileID + }); + }); +} + +export default Save; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/storage/localforage/utils/GetItems.js b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/utils/GetItems.js new file mode 100644 index 000000000..2ae5dd35b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/utils/GetItems.js @@ -0,0 +1,31 @@ +import localforage from 'localforage'; + +var GetItems = function (data, store) { + if (store === undefined) { + store = localforage; + } + + if (Array.isArray(data)) { + var keys = data; + data = {}; + for (var i = 0, cnt = keys.length; i < cnt; i++) { + data[keys[i]] = null; + } + } + + var promises = []; + for (let key in data) { + promises.push( + store.getItem(key) + .then(function (value) { + data[key] = value; + }) + ); + } + return Promise.all(promises) + .then(function () { + return Promise.resolve(data); + }) +} + +export default GetItems; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/storage/localforage/utils/RemoveItems.js b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/utils/RemoveItems.js new file mode 100644 index 000000000..4b96b5a25 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/storage/localforage/utils/RemoveItems.js @@ -0,0 +1,27 @@ +import localforage from 'localforage'; + +var RemoveItems = function(data, store) { + if (store === undefined) { + store = localforage; + } + + var keys; + if (Array.isArray(data)) { + keys =data; + } else { + keys = []; + for(var key in data){ + keys.push(key); + } + } + + var promises =[]; + for(var i=0,cnt=keys.length; i 10) { + prand = (parseInt(prand.substring(0, 10)) + parseInt(prand.substring(10, prand.length))).toString(); + } + prand = (mult * prand + incr) % modu; + var enc_chr = ""; + var enc_str = ""; + for (var i = 0; i < str.length; i += 2) { + enc_chr = parseInt(parseInt(str.substring(i, i + 2), 16) ^ Math.floor((prand / modu) * 255)); + enc_str += String.fromCharCode(enc_chr); + prand = (mult * prand + incr) % modu; + } + return enc_str; +} + +export default function (data, pwd) { + pwd = escape(pwd.toString()); + var result = decrypt(data, pwd); + if (result != null) { + result = unescape(result) + } + return result; +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/string/xor/Encrypt.d.ts b/ui/src/phaser3-rex-plugins/plugins/string/xor/Encrypt.d.ts new file mode 100644 index 000000000..14f4de744 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/string/xor/Encrypt.d.ts @@ -0,0 +1 @@ +export default function Encrypt(src: string, pwd: string): string; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/string/xor/Encrypt.js b/ui/src/phaser3-rex-plugins/plugins/string/xor/Encrypt.js new file mode 100644 index 000000000..35b121e0a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/string/xor/Encrypt.js @@ -0,0 +1,46 @@ +function encrypt(str, pwd) { + if (pwd == null || pwd.length <= 0) { + return null; + } + + var prand = ""; + for (var i = 0; i < pwd.length; i++) { + prand += pwd.charCodeAt(i).toString(); + } + var spos = Math.floor(prand.length / 5); + var mult = parseInt(prand.charAt(spos) + prand.charAt(spos * 2) + prand.charAt(spos * 3) + prand.charAt(spos * 4) + prand.charAt(spos * 5)); + var incr = Math.ceil(pwd.length / 2); + var modu = Math.pow(2, 31) - 1; + if (mult < 2) { + return null; + } + var salt = Math.round(Math.random() * 1000000000) % 100000000; + prand += salt; + while (prand.length > 10) { + prand = (parseInt(prand.substring(0, 10)) + parseInt(prand.substring(10, prand.length))).toString(); + } + prand = (mult * prand + incr) % modu; + var enc_chr = ""; + var enc_str = ""; + for (var i = 0; i < str.length; i++) { + enc_chr = parseInt(str.charCodeAt(i) ^ Math.floor((prand / modu) * 255)); + if (enc_chr < 16) { + enc_str += "0" + enc_chr.toString(16); + } else { + enc_str += enc_chr.toString(16); + } + prand = (mult * prand + incr) % modu; + } + salt = salt.toString(16); + while (salt.length < 8) { + salt = 0 + salt; + } + enc_str += salt; + return enc_str; +} + +export default function (src, pwd) { + src = escape(src); + pwd = escape(pwd.toString()); + return encrypt(src, pwd); +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/stringtemplate-plugin.js b/ui/src/phaser3-rex-plugins/plugins/stringtemplate-plugin.js new file mode 100644 index 000000000..f3387c140 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/stringtemplate-plugin.js @@ -0,0 +1,37 @@ +import StringTemplate from './stringtemplate.js'; +import Compile from './string/stringtemplate/utils/Complile.js'; +import Render from './string/stringtemplate/utils/Render.js'; +import CreateProxyContext from './utils/proxy/createproxycontext/CreateProxyContext.js'; +import SetValue from './utils/object/SetValue.js'; + +class StringTemplatePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new StringTemplate(config); + } + + compile(content, config) { + return Compile(content, config); + } + + render(content, view, config) { + return Render(content, view, config); + } + + createProxyContext(config, baseContext) { + return CreateProxyContext(config, baseContext); + } +} + +SetValue(window, 'RexPlugins.StringTemplate', StringTemplate); + +export default StringTemplatePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/stringtemplate.js b/ui/src/phaser3-rex-plugins/plugins/stringtemplate.js new file mode 100644 index 000000000..6d42fbb0d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/stringtemplate.js @@ -0,0 +1,2 @@ +import StringTemplate from './string/stringtemplate/StringTemplate.js'; +export default StringTemplate; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/swirlpipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/swirlpipeline-plugin.d.ts new file mode 100644 index 000000000..a7227024c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/swirlpipeline-plugin.d.ts @@ -0,0 +1,36 @@ +// import * as Phaser from 'phaser'; +import SwirlPostFxPipeline from './swirlpipeline'; + +export default SwirlPipelinePlugin; + +declare namespace SwirlPipelinePlugin { + + interface IConfig { + center?: { + x?: number, y?: number + }, + + radius?: number, + rotation?: number, angle?: number, + + name?: string + } + +} + +declare class SwirlPipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: SwirlPipelinePlugin.IConfig + ): SwirlPostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): SwirlPostFxPipeline | SwirlPostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/swirlpipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/swirlpipeline-plugin.js new file mode 100644 index 000000000..d15cadeb9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/swirlpipeline-plugin.js @@ -0,0 +1,14 @@ +import SwirlPostFxPipeline from './swirlpipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class SwirlPipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(SwirlPostFxPipeline, 'rexSwirlPostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.SwirlPostFx', SwirlPostFxPipeline); + +export default SwirlPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/swirlpipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/swirlpipeline.d.ts new file mode 100644 index 000000000..b8da9e6ea --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/swirlpipeline.d.ts @@ -0,0 +1,2 @@ +import SwirlPostFxPipeline from './shaders/swirl/SwirlPostFxPipeline'; +export default SwirlPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/swirlpipeline.js b/ui/src/phaser3-rex-plugins/plugins/swirlpipeline.js new file mode 100644 index 000000000..1d19397df --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/swirlpipeline.js @@ -0,0 +1,2 @@ +import SwirlPostFxPipeline from './shaders/swirl/SwirlPostFxPipeline.js'; +export default SwirlPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tagplayer-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/tagplayer-plugin.d.ts new file mode 100644 index 000000000..80d3b2a0a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tagplayer-plugin.d.ts @@ -0,0 +1,13 @@ +import TagPlayer from './tagplayer'; + +export default class TagPlayerPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: TagPlayer.IConfig + ): TagPlayer; + + add( + scene: Phaser.Scene, + config?: TagPlayer.IConfig + ): TagPlayer + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tagplayer-plugin.js b/ui/src/phaser3-rex-plugins/plugins/tagplayer-plugin.js new file mode 100644 index 000000000..b920ccc17 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tagplayer-plugin.js @@ -0,0 +1,18 @@ +import TagPlayer from './tagplayer.js'; + +class TagPlayerPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(scene, config) { + return new TagPlayer(scene, config); + } +} + +export default TagPlayerPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tagplayer.d.ts b/ui/src/phaser3-rex-plugins/plugins/tagplayer.d.ts new file mode 100644 index 000000000..50407ecab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tagplayer.d.ts @@ -0,0 +1,2 @@ +import TagPlayer from './logic/bracketparser/tagplayer/TagPlayer'; +export default TagPlayer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tagplayer.js b/ui/src/phaser3-rex-plugins/plugins/tagplayer.js new file mode 100644 index 000000000..fb8a5d1c1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tagplayer.js @@ -0,0 +1,2 @@ +import TagPlayer from './logic/bracketparser/tagplayer/TagPlayer.js'; +export default TagPlayer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tagtext-plugin.js b/ui/src/phaser3-rex-plugins/plugins/tagtext-plugin.js new file mode 100644 index 000000000..0d2bd240a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tagtext-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/tagtext/tagtext/Factory.js'; +import Creator from './gameobjects/tagtext/tagtext/Creator.js'; +import TagText from './gameobjects/tagtext/tagtext/TagText.js'; +import SetValue from './utils/object/SetValue.js'; + +class TagTextPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexTagText', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.TagText', TagText); + +export default TagTextPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tagtext.d.ts b/ui/src/phaser3-rex-plugins/plugins/tagtext.d.ts new file mode 100644 index 000000000..0c29fd38a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tagtext.d.ts @@ -0,0 +1,2 @@ +import TagText from './gameobjects/tagtext/tagtext/TagText'; +export default TagText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tagtext.js b/ui/src/phaser3-rex-plugins/plugins/tagtext.js new file mode 100644 index 000000000..4a8f69139 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tagtext.js @@ -0,0 +1,2 @@ +import TagText from './gameobjects/tagtext/tagtext/TagText.js'; +export default TagText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tcrp-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/tcrp-plugin.d.ts new file mode 100644 index 000000000..f3c62d468 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tcrp-plugin.d.ts @@ -0,0 +1,18 @@ +import Recorder from './logic/runcommands/tcrp/Recorder'; +import Player from './logic/runcommands/tcrp/Player'; +import RunCommands from './logic/runcommands/RunCommands'; + + +export default class TCRPPlugin extends Phaser.Plugins.BasePlugin { + addRecorder( + parent: Phaser.Scene | Phaser.GameObjects.GameObject, + config?: Recorder.IConfig + ): Recorder; + + addPlayer( + parent: Phaser.Scene | Phaser.GameObjects.GameObject, + config?: Player.IConfig + ): Player + + runCommands: typeof RunCommands; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tcrp-plugin.js b/ui/src/phaser3-rex-plugins/plugins/tcrp-plugin.js new file mode 100644 index 000000000..62f4e67ac --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tcrp-plugin.js @@ -0,0 +1,34 @@ +import TCRP from './tcrp.js'; + +const Recorder = TCRP.Recorder; +const Player = TCRP.Player; + +class TCRPPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + addRecorder(parent, config) { + return new Recorder(parent, config); + } + + addPlayer(parent, config) { + return new Player(parent, config); + } +} + +var methods = { + runCommands: TCRP.RunCommands +} + +Object.assign( + TCRPPlugin.prototype, + methods +); + +export default TCRPPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tcrp.d.ts b/ui/src/phaser3-rex-plugins/plugins/tcrp.d.ts new file mode 100644 index 000000000..3994b2960 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tcrp.d.ts @@ -0,0 +1,11 @@ +import Recorder from './logic/runcommands/tcrp/Recorder'; +import Player from './logic/runcommands/tcrp/Player'; +import RunCommands from './logic/runcommands/RunCommands'; + +declare var TCRP: { + Recorder: typeof Recorder, + Player: typeof Player, + RunCommands: typeof RunCommands +} + +export default TCRP; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tcrp.js b/ui/src/phaser3-rex-plugins/plugins/tcrp.js new file mode 100644 index 000000000..f36ac2f9d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tcrp.js @@ -0,0 +1,9 @@ +import Recorder from './logic/runcommands/tcrp/Recorder.js'; +import Player from './logic/runcommands/tcrp/Player.js'; +import RunCommands from './logic/runcommands/RunCommands.js'; + +export default { + Recorder: Recorder, + Player: Player, + RunCommands: RunCommands +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/textedit-plugin.js b/ui/src/phaser3-rex-plugins/plugins/textedit-plugin.js new file mode 100644 index 000000000..dec283b90 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/textedit-plugin.js @@ -0,0 +1,27 @@ +import { TextEdit, Edit } from './textedit.js'; + +class TextEditPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new TextEdit(gameObject, config); + } +} + +var methods = { + edit: Edit +}; +Object.assign( + TextEditPlugin.prototype, + methods +); + +export default TextEditPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/textedit.d.ts b/ui/src/phaser3-rex-plugins/plugins/textedit.d.ts new file mode 100644 index 000000000..c88bc79d5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/textedit.d.ts @@ -0,0 +1,3 @@ +import TextEdit from './behaviors/textedit/TextEdit'; +import Edit from './behaviors/textedit/Edit'; +export { TextEdit, Edit }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/textedit.js b/ui/src/phaser3-rex-plugins/plugins/textedit.js new file mode 100644 index 000000000..f6eb7551c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/textedit.js @@ -0,0 +1,3 @@ +import TextEdit from './behaviors/textedit/TextEdit.js'; +import Edit from './behaviors/textedit/Edit.js'; +export { TextEdit, Edit }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/textpage-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/textpage-plugin.d.ts new file mode 100644 index 000000000..6feaa2eb7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/textpage-plugin.d.ts @@ -0,0 +1,9 @@ +import TextPage from './textpage'; + +export default class TextPagePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: TextPage.IConfig + ): TextPage; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/textpage-plugin.js b/ui/src/phaser3-rex-plugins/plugins/textpage-plugin.js new file mode 100644 index 000000000..223013c53 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/textpage-plugin.js @@ -0,0 +1,20 @@ +import TextPage from './textpage.js'; + +class TextPagePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new TextPage(gameObject, config); + } + +} + +export default TextPagePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/textpage.d.ts b/ui/src/phaser3-rex-plugins/plugins/textpage.d.ts new file mode 100644 index 000000000..e065456ae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/textpage.d.ts @@ -0,0 +1,2 @@ +import TextPage from './behaviors/textpage/TextPage.js'; +export default TextPage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/textpage.js b/ui/src/phaser3-rex-plugins/plugins/textpage.js new file mode 100644 index 000000000..e065456ae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/textpage.js @@ -0,0 +1,2 @@ +import TextPage from './behaviors/textpage/TextPage.js'; +export default TextPage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/textplayer-plugin.js b/ui/src/phaser3-rex-plugins/plugins/textplayer-plugin.js new file mode 100644 index 000000000..800ab02ae --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/textplayer-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/dynamictext/textplayer/Factory'; +import Creator from './gameobjects/dynamictext/textplayer/Creator.js'; +import TextPlayer from './gameobjects/dynamictext/textplayer/TextPlayer.js'; +import SetValue from './utils/object/SetValue.js'; + +class DynamicTextPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexTextPlayer', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.TextPlayer', TextPlayer); + +export default DynamicTextPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/textplayer.d.ts b/ui/src/phaser3-rex-plugins/plugins/textplayer.d.ts new file mode 100644 index 000000000..6b3d649fc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/textplayer.d.ts @@ -0,0 +1,2 @@ +import TextPlayer from './gameobjects/dynamictext/textplayer/TextPlayer'; +export default TextPlayer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/textplayer.js b/ui/src/phaser3-rex-plugins/plugins/textplayer.js new file mode 100644 index 000000000..3e33cb017 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/textplayer.js @@ -0,0 +1,2 @@ +import TextPlayer from './gameobjects/dynamictext/textplayer/TextPlayer.js'; +export default TextPlayer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texttranslation-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/texttranslation-plugin.d.ts new file mode 100644 index 000000000..825ebfe1d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texttranslation-plugin.d.ts @@ -0,0 +1,38 @@ +import TextTranslation from './texttranslation'; +import { i18n, InitOptions } from 'i18next'; +import { HttpBackendOptions } from 'i18next-http-backend'; + +export default TextTranslationPlugin; + +declare namespace TextTranslationPlugin { + interface CustomInitOptions extends InitOptions, HttpBackendOptions { + + } +} + +declare class TextTranslationPlugin extends Phaser.Plugins.BasePlugin { + i18next: i18n; + + initI18Next( + scene: Phaser.Scene, + config?: TextTranslationPlugin.CustomInitOptions + ): this; + + add( + gameObject: Phaser.GameObjects.GameObject, + config?: TextTranslation.IConfig + ): TextTranslation; + + changeLanguage( + language: string, + onComplete?: Function + ): this; + + setDefaultNamespace(namespace: string): this; + + t( + translationKey: string, + interpolations?: { [name: string]: any } + ): string + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texttranslation-plugin.js b/ui/src/phaser3-rex-plugins/plugins/texttranslation-plugin.js new file mode 100644 index 000000000..befd75d38 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texttranslation-plugin.js @@ -0,0 +1,70 @@ +import EventEmitterMethods from './utils/eventemitter/EventEmitterMethods.js'; +import Awaitloader from './awaitloader.js'; +import i18next from 'i18next'; +import Backend from 'i18next-http-backend'; +import TextTranslation from './texttranslation.js'; + +class TextTranslationPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Event emitter + this.setEventEmitter(); + + this.i18next = i18next; + TextTranslation.setI18Next(i18next); + + // Route 'languageChanged' event + this.onLanguageChanged = (function (lng) { + this.emit('languageChanged', lng); + }).bind(this); + i18next.on('languageChanged', this.onLanguageChanged); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + destroy() { + i18next.off('languageChanged', this.onLanguageChanged); + + super.destroy(); + + this.destroyEventEmitter(); + } + + initI18Next(scene, config) { + Awaitloader.call(scene.load, function (successCallback, failureCallback) { + i18next.use(Backend).init(config, successCallback); + }) + return this; + } + + + add(gameObject, config) { + return new TextTranslation(gameObject, config); + } + + changeLanguage(lng, onComplete) { + i18next.changeLanguage(lng, onComplete); + return this; + } + + setDefaultNamespace(namespace) { + i18next.setDefaultNamespace(namespace); + return this; + } + + t(translationKey, interpolation) { + return i18next.t(translationKey, interpolation); + } +} + +Object.assign( + TextTranslationPlugin.prototype, + EventEmitterMethods +); + +export default TextTranslationPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texttranslation.d.ts b/ui/src/phaser3-rex-plugins/plugins/texttranslation.d.ts new file mode 100644 index 000000000..e63237aee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texttranslation.d.ts @@ -0,0 +1,2 @@ +import TextTranslation from './behaviors/texttranslation/TextTranslation'; +export default TextTranslation; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texttranslation.js b/ui/src/phaser3-rex-plugins/plugins/texttranslation.js new file mode 100644 index 000000000..cfc9efc10 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texttranslation.js @@ -0,0 +1,2 @@ +import TextTranslation from './behaviors/texttranslation/TextTranslation.js'; +export default TextTranslation; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texttyping-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/texttyping-plugin.d.ts new file mode 100644 index 000000000..76268f7e7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texttyping-plugin.d.ts @@ -0,0 +1,9 @@ +import TextTyping from './texttyping'; + +export default class TextTypingPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: TextTyping.IConfig + ): TextTyping; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texttyping-plugin.js b/ui/src/phaser3-rex-plugins/plugins/texttyping-plugin.js new file mode 100644 index 000000000..cf3fcdad8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texttyping-plugin.js @@ -0,0 +1,20 @@ +import TextTyping from './texttyping.js'; + +class TextTypingPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new TextTyping(gameObject, config); + } + +} + +export default TextTypingPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texttyping.d.ts b/ui/src/phaser3-rex-plugins/plugins/texttyping.d.ts new file mode 100644 index 000000000..c4c780a79 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texttyping.d.ts @@ -0,0 +1,2 @@ +import TextTyping from './behaviors/texttyping/TextTyping'; +export default TextTyping; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texttyping.js b/ui/src/phaser3-rex-plugins/plugins/texttyping.js new file mode 100644 index 000000000..c8364837f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texttyping.js @@ -0,0 +1,2 @@ +import TextTyping from './behaviors/texttyping/TextTyping.js'; +export default TextTyping; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/CanvasFrameManager.d.ts b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/CanvasFrameManager.d.ts new file mode 100644 index 000000000..e60fe2777 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/CanvasFrameManager.d.ts @@ -0,0 +1,84 @@ +export default CanvasFrameManager; + +declare namespace CanvasFrameManager { + interface IConfig { + key: string, + width?: number, + height?: number, + cellWidth?: number, + cellHeight?: number, + fillColor?: string + } + + type DrawFrameCallback = ( + canvasElem: HTMLCanvasElement, + context: CanvasRenderingContext2D, + frameSize: { + width: number, + height: number + } + ) => void +} + +declare class CanvasFrameManager { + constructor( + scene: Phaser.Scene, + key: string, + width?: number, + height?: number, + cellWidth?: number, + cellHeight?: number, + fillColor?: string + ); + + constructor( + scene: Phaser.Scene, + config: CanvasFrameManager.IConfig + ); + + readonly key: string; + readonly canvas: HTMLCanvasElement; + readonly context: CanvasRenderingContext2D; + readonly width: number; + readonly height: number; + readonly cellWidth: number; + readonly cellHeight: number; + readonly isFull: boolean; + + destroy(): void; + + stop(): this; + + add( + camera: Phaser.Cameras.Scene2D.BaseCamera + ): this; + + draw( + frameName: string | number, + callback: CanvasFrameManager.DrawFrameCallback, + scope?: object + ): this; + + paste( + frameName: string | number, + gameObject: Phaser.GameObjects.GameObject + ): this; + + addEmptyFrame( + frameName: string | number, + width?: number, + height?: number + ): this; + + updateTexture(): this; + + remove( + frameName: string | number + ): this; + + clear(): this; + + hasFrameName( + frameName: string | number + ): boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/CanvasFrameManager.js b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/CanvasFrameManager.js new file mode 100644 index 000000000..c5c87ce7a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/CanvasFrameManager.js @@ -0,0 +1,131 @@ +import Methods from './methods/Methods.js'; + +const IsPlainObject = Phaser.Utils.Objects.IsPlainObject; +const GetValue = Phaser.Utils.Objects.GetValue; + +class CanvasFrameManager { + constructor(scene, key, width, height, cellWidth, cellHeight, fillColor) { + if (IsPlainObject(key)) { + var config = key; + key = GetValue(config, 'key'); + width = GetValue(config, 'width'); + height = GetValue(config, 'height'); + cellWidth = GetValue(config, 'cellWidth'); + cellHeight = GetValue(config, 'cellHeight'); + fillColor = GetValue(config, 'fillColor'); + } + + if (width === undefined) { + width = 4096; + } + if (height === undefined) { + height = 4096; + } + if (cellWidth === undefined) { + cellWidth = 64; + } + if (cellHeight === undefined) { + cellHeight = 64; + } + + this.texture = scene.sys.textures.createCanvas(key, width, height); + this.canvas = this.texture.getCanvas(); + this.context = this.texture.getContext(); + this.bitmapFontCache = scene.sys.cache.bitmapFont; + + if (fillColor !== undefined) { + var context = this.context; + context.fillStyle = fillColor; + context.fillRect(0, 0, this.canvas.width, this.canvas.height); + } + + this.key = key; + this.width = width; + this.height = height; + this.cellWidth = cellWidth; + this.cellHeight = cellHeight; + this.columnCount = Math.floor(width / cellWidth); + this.rowCount = Math.floor(height / cellHeight); + this.totalCount = this.columnCount * this.rowCount; + + this.frameNames = Array(this.totalCount); + for (var i = 0, cnt = this.frameNames.length; i < cnt; i++) { + this.frameNames[i] = undefined; + } + } + + destroy() { + this.texture = undefined; + this.canvas = undefined; + this.context = undefined; + this.frameNames = undefined; + this.bitmapFontCache = undefined; + } + + getFrameIndex(frameName) { + return this.frameNames.indexOf(frameName); + } + + hasFrameName(frameName) { + return this.getFrameIndex(frameName) !== -1; + } + + addFrameName(index, frameName) { + this.frameNames[index] = frameName; + return this; + } + + get isFull() { + return this.getFrameIndex(undefined) === -1; + } + + getTopLeftPosition(frameIndex, out) { + if (out === undefined) { + out = {}; + } + + var columnIndex = frameIndex % this.columnCount; + var rowIndex = Math.floor(frameIndex / this.rowCount); + out.x = columnIndex * this.cellWidth; + out.y = rowIndex * this.cellHeight; + return out; + } + + updateTexture() { + this.texture.refresh(); + return this; + } + + remove(frameName) { + var index = this.getFrameIndex(frameName); + if (index === -1) { + return this; + } + + this.addFrameName(index, undefined); + this.texture.remove(frameName); + + // Don't clear canvas + + return this; + } + + clear() { + for (var i, cnt = this.frameNames.length; i < cnt; i++) { + var frameName = this.frameNames[i]; + if (frameName !== undefined) { + this.addFrameName(index, undefined); + this.texture.remove(frameName); + } + } + + return this; + } +} + +Object.assign( + CanvasFrameManager.prototype, + Methods +); + +export default CanvasFrameManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/AddEmptyFrame.js b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/AddEmptyFrame.js new file mode 100644 index 000000000..deaf39c36 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/AddEmptyFrame.js @@ -0,0 +1,16 @@ +var AddEmptyFrame = function (frameName, width, height) { + if (width === undefined) { + width = this.cellWidth; + } + if (height === undefined) { + height = this.cellHeight; + } + this.draw(frameName, function (canvas, context, frameSize) { + frameSize.width = width; + frameSize.height = height; + }) + + return this; +} + +export default AddEmptyFrame; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/AddToBitmapFont.js b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/AddToBitmapFont.js new file mode 100644 index 000000000..914a4a952 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/AddToBitmapFont.js @@ -0,0 +1,53 @@ +var AddToBitmapFont = function () { + var textureKey = this.texture.key; + // Don't add a new font data, reuse current font data + var cacheData = this.bitmapFontCache.get(textureKey); + if (!cacheData) { + cacheData = { + data: { + retroFont: true, + font: textureKey, + size: this.cellWidth, + lineHeight: this.cellHeight, + chars: {} + }, + texture: textureKey, + frame: null, + }; + this.bitmapFontCache.add(textureKey, cacheData); + } + var charData = cacheData.data.chars; + + var letters = this.frameNames; + for (var i = 0, cnt = letters.length; i < cnt; i++) { + var char = letters[i]; + if (char === undefined) { + continue; + } + + var frame = this.texture.get(char); + var x = frame.cutX, + y = frame.cutY, + width = frame.cutWidth, + height = frame.cutHeight; + + charData[char.charCodeAt(0)] = { + x: x, y: y, + width: width, height: height, + centerX: x + (width / 2), + centerY: y + (height / 2), + xOffset: 0, + yOffset: 0, + xAdvance: width, + data: {}, + kerning: {}, + u0: frame.u0, + v0: frame.v0, + u1: frame.u1, + v1: frame.v1 + } + } + + return this; +} +export default AddToBitmapFont; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/Draw.js b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/Draw.js new file mode 100644 index 000000000..b19cf1dd5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/Draw.js @@ -0,0 +1,37 @@ +var Draw = function (frameName, callback, scope) { + var index = this.getFrameIndex(frameName); + if (index === -1) { + index = this.getFrameIndex(undefined); + } + if (index === -1) { + console.warn('Does not have free space.'); + return this; + } + + var tl = this.getTopLeftPosition(index); + var frameSize = { + width: this.cellWidth, + height: this.cellHeight + } + + var context = this.context; + context.save(); + context.translate(tl.x, tl.y); + context.clearRect(0, 0, frameSize.width, frameSize.height); + + if (scope) { + callback.call(scope, this.canvas, context, frameSize); + } else { + callback(this.canvas, context, frameSize); + } + // frameSize might be changed + + context.restore(); + + this.texture.add(frameName, 0, tl.x, tl.y, frameSize.width, frameSize.height); + this.addFrameName(index, frameName); + + return this; +} + +export default Draw; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/Methods.js new file mode 100644 index 000000000..6b8aa4b13 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/Methods.js @@ -0,0 +1,14 @@ +import Draw from './Draw.js'; +import Paste from './Paste.js'; +import AddEmptyFrame from './AddEmptyFrame.js'; +import AddToBitmapFont from './AddToBitmapFont.js'; + +var methods = { + draw: Draw, + paste: Paste, + addEmptyFrame: AddEmptyFrame, + + addToBitmapFont: AddToBitmapFont, +} + +export default methods \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/Paste.js b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/Paste.js new file mode 100644 index 000000000..c34118024 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/canvasframemanager/methods/Paste.js @@ -0,0 +1,30 @@ +var Paste = function (frameName, gameObject) { + var srcCanvas = gameObject.canvas; + if (!srcCanvas) { + console.warn(`Can't get canvas of game object.`); + return this; + } + + var srcWidth = srcCanvas.width, + srcHeight = srcCanvas.height; + var dWidth, dHeight; + if ((srcWidth <= this.cellWidth) && (srcHeight <= this.cellHeight)) { + dWidth = srcWidth; + dHeight = srcHeight; + } else { + // Scale down and keep ratio + var scale = Math.max((srcWidth / this.cellWidth), (srcHeight / this.cellHeight)); + dWidth = srcWidth / scale; + dHeight = srcHeight / scale; + } + this.draw(frameName, function (canvas, context, frameSize) { + context.drawImage(srcCanvas, 0, 0, dWidth, dHeight); + + frameSize.width = dWidth; + frameSize.height = dHeight; + }) + + return this; +} + +export default Paste; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/CharacterCache.d.ts b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/CharacterCache.d.ts new file mode 100644 index 000000000..98621ded0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/CharacterCache.d.ts @@ -0,0 +1,61 @@ +import EventEmitter from "../../utils/eventemitter/EventEmitter"; + +export default CharacterCache; + +declare namespace CharacterCache { + interface IConfig { + key: string, + cellWidth: number, + cellHeight: number, + maxCharacterCount?: number, + freqMode?: boolean, + + textObject?: Phaser.GameObjects.GameObject, + + content?: string, + + eventEmitter?: EventEmitter | false, + } + + interface CacheData { + character: string, + freq: number, + alive: boolean, + lock: boolean, + } +} + +declare class CharacterCache extends EventEmitter { + constructor( + scene: Phaser.Scene, + config: CharacterCache.IConfig + ); + + readonly key: string; + readonly cellWidth: number; + readonly cellHeight: number; + readonly inCacheCount: number; + + destroy(): void; + + bindTextObject( + textObject: Phaser.GameObjects.GameObject + ): this; + + overrideBitmapText( + bitmapText: Phaser.GameObjects.GameObject + ): Phaser.GameObjects.GameObject; + + load( + content: string, + lock?: boolean + ): this; + + unlock(): this; + + getAllData( + ): CharacterCache.CacheData[]; + + clear(): this; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/CharacterCache.js b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/CharacterCache.js new file mode 100644 index 000000000..b2943ef2e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/CharacterCache.js @@ -0,0 +1,65 @@ +import EventEmitterMethods from '../../utils/eventemitter/EventEmitterMethods.js'; +import CreateFrameManager from './methods/CreateFrameManager.js'; +import CreateCharacterCollection from './methods/CreateCharacterCollection.js'; +import Methods from './methods/Methods.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class CharacterCache { + constructor(scene, config) { + // Event emitter + var eventEmitter = GetValue(config, 'eventEmitter', undefined); + var EventEmitterClass = GetValue(config, 'EventEmitterClass', undefined); + this.setEventEmitter(eventEmitter, EventEmitterClass); + + this.freqMode = GetValue(config, 'freqMode', true); + + this.frameManager = CreateFrameManager(scene, config); + this.frameManager.addToBitmapFont(); // Add to bitmapfont at beginning + + this.key = this.frameManager.key; + this.cellWidth = this.frameManager.cellWidth; + this.cellHeight = this.frameManager.cellHeight; + + // Create ChacacterCollection + this.characterCollection = CreateCharacterCollection(); + + // Bind text object + var textObject = GetValue(config, 'textObject'); + if (textObject) { + this.bindTextObject(textObject); + } + + this.inCacheCount = 0; + + // Load content + this.load(GetValue(config, 'content', '')); + } + + shutdown() { + this.destroyEventEmitter(); + + this.frameManager.destroy(); + this.characterCollection = undefined; + if (this.textObject) { + this.textObject.destroy(); + } + } + + destroy() { + this.shutdown(); + } + + bindTextObject(textObject) { + this.textObject = textObject; + return this; + } +} + +Object.assign( + CharacterCache.prototype, + EventEmitterMethods, + Methods +); + +export default CharacterCache; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/BitmapTextMethods.js b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/BitmapTextMethods.js new file mode 100644 index 000000000..8821ff747 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/BitmapTextMethods.js @@ -0,0 +1,12 @@ +export default { + overrideBitmapText(bitmapText) { + var self = this; + var setTextSave = bitmapText.setText; + bitmapText.setText = function (text, lock) { + self.load(text, lock); + setTextSave.call(bitmapText, text); + return bitmapText; + } + return bitmapText; + } +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/CharacterQueryMethods.js b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/CharacterQueryMethods.js new file mode 100644 index 000000000..a9bf0b591 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/CharacterQueryMethods.js @@ -0,0 +1,67 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +var CreateCharacterItem = function (character) { + return { + character: character, + freq: 0, + alive: false, + lock: false + } +} + +var GetChatacter = function (collection, character) { + var item = collection.by('character', character); + if (item === undefined) { + item = CreateCharacterItem(character); + collection.insert(item); + } + return item; +} + +var GetInCacheCharacterItems = function (collection, config) { + var excludeCharacters = GetValue(config, 'exclude', undefined); + var lock = GetValue(config, 'lock', undefined); + var freqMode = GetValue(config, 'freq', false); + + var filter = { alive: true }; + + if (excludeCharacters !== undefined) { + if (typeof (excludeCharacters) === 'string') { + excludeCharacters = excludeCharacters.split(); + } + filter.character = { + $nin: excludeCharacters + } + } + + if (lock !== undefined) { + filter.lock = lock; + } + + if (freqMode) { + return collection.chain().find(filter).simplesort('freq', { desc: true }).data(); + } else { + return collection.chain().find(filter).data(); + } +} + +var GetLockedCharacterItems = function (collection) { + return collection.find({ lock: true }); +} + +var GetAllItems = function (collection, config) { + var freqMode = GetValue(config, 'freq', true); + + if (freqMode) { + return collection.chain().simplesort('freq', { desc: true }).data(); + } else { + return collection.chain().data(); + } +} + +export { + GetChatacter, + GetInCacheCharacterItems, + GetLockedCharacterItems, + GetAllItems, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Clear.js b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Clear.js new file mode 100644 index 000000000..fcb92295e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Clear.js @@ -0,0 +1,11 @@ +var Clear = function () { + this.characterCollection.clear(); + + this.frameManager + .clear() + .addToBitmapFont(); + + return this; +} + +export default Clear; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/CreateCharacterCollection.js b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/CreateCharacterCollection.js new file mode 100644 index 000000000..ae5f34082 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/CreateCharacterCollection.js @@ -0,0 +1,17 @@ +import loki from 'lokijs/src/lokijs.js'; + +var CreateCharacterDB = function (db) { + if (db === undefined) { + db = new loki('characters.db', { + env: 'BROWSER' + }); + } + var collection = db.addCollection('characters', { + disableMeta: true, + unique: ['character'], + indices: ['character', 'freq', 'alive', 'lock'] + }); + return collection; +} + +export default CreateCharacterDB; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/CreateFrameManager.js b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/CreateFrameManager.js new file mode 100644 index 000000000..8446ee940 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/CreateFrameManager.js @@ -0,0 +1,20 @@ +import CanvasFrameManager from '../../canvasframemanager/CanvasFrameManager.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var CreateFrameManager = function (scene, config) { + var key = GetValue(config, 'key'); + var cellWidth = GetValue(config, 'cellWidth', 32); + var cellHeight = GetValue(config, 'cellHeight', 32); + var maxCharacterCount = GetValue(config, 'maxCharacterCount', 4096); + + var colCount = Math.ceil(Math.sqrt(maxCharacterCount)); + var rowCount = colCount; + var width = cellWidth * colCount; + var height = cellHeight * rowCount; + + var frameManager = new CanvasFrameManager(scene, key, width, height, cellWidth, cellHeight); + return frameManager; +} + +export default CreateFrameManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/GetAllData.js b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/GetAllData.js new file mode 100644 index 000000000..c8b6e7339 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/GetAllData.js @@ -0,0 +1,7 @@ +import { GetAllItems } from './CharacterQueryMethods.js'; + +var GetAllData = function () { + return GetAllItems(this.characterCollection); +} + +export default GetAllData; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Load.js b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Load.js new file mode 100644 index 000000000..0a8eca12b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Load.js @@ -0,0 +1,92 @@ +import { + GetChatacter, GetInCacheCharacterItems +} from './CharacterQueryMethods.js'; + +var Load = function (content, lock) { + if (Array.isArray(content)) { + content = content.join(''); + } + + if (lock === undefined) { + lock = false; + } + + var insertCharacters = []; + var removeCharacters = []; + + var totalCacheCount = this.frameManager.totalCount; + var aliveCount = GetInCacheCharacterItems(this.characterCollection).length; + var penddingItems = []; + for (var i = 0, cnt = content.length; i < cnt; i++) { + var character = content.charAt(i); + if (character === '\n') { + continue; + } + + var item = GetChatacter(this.characterCollection, character); + + if (this.freqMode) { + item.freq++; + } + item.lock = lock; + if (!item.alive) { + insertCharacters.push(character); + if (totalCacheCount > aliveCount) { + // Has free space, add to cache directly + item.alive = true; + aliveCount++; + this.inCacheCount++; + } else { + penddingItems.push(item); + } + } + this.characterCollection.update(item); + } + + if (penddingItems.length > 0) { + var freeCandidateItems = GetInCacheCharacterItems(this.characterCollection, { + exclude: content, + lock: false, + freq: this.freqMode + }); + for (var i = 0, cnt = penddingItems.length; i < cnt; i++) { + var item = penddingItems[i]; + var freeItem = freeCandidateItems.pop(); + if (freeItem) { + freeItem.alive = false; + this.characterCollection.update(freeItem); + + item.alive = true; + this.characterCollection.update(item); + + removeCharacters.push(freeItem.character); + } else { + console.warn(`Character cache full, can't add '${item.character}' character.`); + } + } + } + + // Update frame-manager + for (var i = 0, cnt = removeCharacters.length; i < cnt; i++) { + this.emit('remove', character, this.textObject); + this.frameManager.remove(removeCharacters[i]); + } + + for (var i = 0, cnt = insertCharacters.length; i < cnt; i++) { + var character = insertCharacters[i]; + this.emit('add', character, this.textObject); + + this.textObject.setText(character); + this.frameManager.paste(character, this.textObject); + } + + if (insertCharacters.length > 0) { + this.frameManager + .updateTexture() + .addToBitmapFont(); + } + + return this; +} + +export default Load; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Methods.js new file mode 100644 index 000000000..b15581ec7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Methods.js @@ -0,0 +1,20 @@ +import Load from './Load.js'; +import Unlock from './Unlock.js'; +import GetAllData from './GetAllData.js'; +import Clear from './Clear.js'; +import BitmapTextMethods from './BitmapTextMethods.js'; + +var Methods = { + load: Load, + unlock: Unlock, + getAllData: GetAllData, + clear: Clear, +} + +Object.assign( + Methods, + BitmapTextMethods +); + + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Unlock.js b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Unlock.js new file mode 100644 index 000000000..813c720c6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/texture/charactercache/methods/Unlock.js @@ -0,0 +1,11 @@ +import { GetLockedCharacterItems } from './CharacterQueryMethods.js'; + +var Unlock = function () { + var items = GetLockedCharacterItems(this.characterCollection); + for (var i = 0, cnt = items.length; i < cnt; i++) { + items[i].lock = false; + } + return this; +} + +export default Unlock; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/awaytime/AwayTime.d.ts b/ui/src/phaser3-rex-plugins/plugins/time/awaytime/AwayTime.d.ts new file mode 100644 index 000000000..5bd8dcc97 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/awaytime/AwayTime.d.ts @@ -0,0 +1,16 @@ +export default AwayTime; + +declare namespace AwayTime { + interface IConfig { + key?: string, + peropd?: number + } +} +declare class AwayTime { + constructor(config?: AwayTime.IConfig); + + readonly awayTime: number; + + setKey(key: string): this; + setPeriod(time: number): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/awaytime/AwayTime.js b/ui/src/phaser3-rex-plugins/plugins/time/awaytime/AwayTime.js new file mode 100644 index 000000000..0d6150b78 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/awaytime/AwayTime.js @@ -0,0 +1,71 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +class AwayTime { + constructor(config) { + this.state = IDLE; + this.setKey(GetValue(config, 'key', 'away')); + this.setPeriod(GetValue(config, 'period', 1000)); + } + + destroy() { + this.stop(); + } + + get awayTime() { + var prevTime = localStorage.getItem(this.key); + this.start(); + if (prevTime == null) { + return 0; + } + prevTime = parseInt(prevTime); + var curTime = this.curTime; + if ((prevTime < 0) || (prevTime > curTime)) { + return 0; + } + // console.log(new Date(prevTime).toLocaleString()); + // console.log(new Date(curTime).toLocaleString()); + return curTime - prevTime; + } + + get curTime() { + return new Date().getTime(); + } + + start() { + this.stop(); + this.updateTime(); + this.timer = setInterval(this.updateTime.bind(this), this.period); + this.state = UPDATING; + return this; + } + + stop() { + if (this.state === IDLE) { + return this; + } + clearTimeout(this.timer); + this.timer = undefined; + this.state = IDLE; + return this; + } + + updateTime() { + localStorage.setItem(this.key, this.curTime); + return this; + } + + setKey(key) { + this.key = key; + return this; + } + + setPeriod(time) { + this.period = time; + return this; + } +} + +const IDLE = 0; +const UPDATING = 1; + +export default AwayTime; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/clock/ArcadeStepClock.d.ts b/ui/src/phaser3-rex-plugins/plugins/time/clock/ArcadeStepClock.d.ts new file mode 100644 index 000000000..dbaaecc82 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/clock/ArcadeStepClock.d.ts @@ -0,0 +1,12 @@ +import BaseClock from "./BaseClock"; + +export default ArcadeStepClock; + +declare namespace ArcadeStepClock { + interface IConfig extends BaseClock.IConfig { + } +} + +declare class ArcadeStepClock extends BaseClock { + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/clock/ArcadeStepClock.js b/ui/src/phaser3-rex-plugins/plugins/time/clock/ArcadeStepClock.js new file mode 100644 index 000000000..fc690ac80 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/clock/ArcadeStepClock.js @@ -0,0 +1,26 @@ +import BaseClock from './BaseClock.js'; + +class ArcadeStepClock extends BaseClock { + startTicking() { + super.startTicking(); + this.scene.physics.world.on('worldstep', this.update, this); + // 'worldstep' event is emitted *after* the bodies and colliders have been updated. + } + + stopTicking() { + super.stopTicking(); + if (this.scene) { // Scene might be destoryed + this.scene.physics.world.off('worldstep', this.update, this); + } + } + + update() { + if ((!this.isRunning) || (this.timeScale === 0)) { + return this; + } + this.tick(1); + return this; + } +} + +export default ArcadeStepClock; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/clock/BaseClock.d.ts b/ui/src/phaser3-rex-plugins/plugins/time/clock/BaseClock.d.ts new file mode 100644 index 000000000..a4800fa28 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/clock/BaseClock.d.ts @@ -0,0 +1,32 @@ +export default BaseClock; + +declare namespace BaseClock { + interface IConfig { + timeScale?: number + } + + namespace Events { + type UpdateCallbackType = (now: number, delta: number) => void; + } +} + +declare class BaseClock extends Phaser.Events.EventEmitter { + constructor( + parent: Phaser.Scene | Phaser.GameObjects.GameObject, + config?: BaseClock.IConfig + ); + + start(startAt?: number): this; + seek(time?: number): this; + pause(): this; + resume(): this; + stop(): this; + tick(delta: number): this; + + readonly now: number; + readonly isRunning: boolean; + + setTimeScale(timeScale: number): this; + timeScale: number; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/clock/BaseClock.js b/ui/src/phaser3-rex-plugins/plugins/time/clock/BaseClock.js new file mode 100644 index 000000000..c92990a2f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/clock/BaseClock.js @@ -0,0 +1,64 @@ +import TickTask from '../../utils/componentbase/TickTask.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class BaseClock extends TickTask { + constructor(parent, config) { + super(parent, config); + + this.resetFromJSON(config); + this.boot(); + } + + resetFromJSON(o) { + this.isRunning = GetValue(o, 'isRunning', false); + this.timeScale = GetValue(o, 'timeScale', 1); + this.now = GetValue(o, 'now', 0); + return this; + } + + toJSON() { + return { + isRunning: this.isRunning, + timeScale: this.timeScale, + now: this.now, + tickingMode: this.tickingMode + }; + } + + // Override + // startTicking() { } + + // Override + // stopTicking() {} + + start(startAt) { + if (startAt === undefined) { + startAt = 0; + } + this.delta = 0; + this.now = startAt; + super.start(); + return this; + } + + seek(time) { + this.now = time; + return this; + } + + setTimeScale(value) { + this.timeScale = value; + return this; + } + + tick(delta) { + delta *= this.timeScale; + this.now += delta; + this.delta = delta; + this.emit('update', this.now, this.delta); + return this; + } +} + +export default BaseClock; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/clock/Clock.d.ts b/ui/src/phaser3-rex-plugins/plugins/time/clock/Clock.d.ts new file mode 100644 index 000000000..a6aae96a8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/clock/Clock.d.ts @@ -0,0 +1,12 @@ +import BaseClock from "./BaseClock"; + +export default Clock; + +declare namespace Clock { + interface IConfig extends BaseClock.IConfig { + } +} + +declare class Clock extends BaseClock { + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/clock/Clock.js b/ui/src/phaser3-rex-plugins/plugins/time/clock/Clock.js new file mode 100644 index 000000000..5e27c7572 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/clock/Clock.js @@ -0,0 +1,25 @@ +import BaseClock from './BaseClock.js'; + +class Clock extends BaseClock { + startTicking() { + super.startTicking(); + this.scene.sys.events.on('update', this.update, this); + } + + stopTicking() { + super.stopTicking(); + if (this.scene) { // Scene might be destoryed + this.scene.sys.events.off('update', this.update, this); + } + } + + update(time, delta) { + if ((!this.isRunning) || (this.timeScale === 0)) { + return this; + } + this.tick(delta); + return this; + } +} + +export default Clock; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/clock/GameClock.js b/ui/src/phaser3-rex-plugins/plugins/time/clock/GameClock.js new file mode 100644 index 000000000..1a1d5a030 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/clock/GameClock.js @@ -0,0 +1,24 @@ +import BaseClock from './BaseClock.js'; +import GetGame from '../../utils/system/GetGame.js' + +class Clock extends BaseClock { + startTicking() { + super.startTicking(); + GetGame(this.parent).events.on('step', this.update, this); + } + + stopTicking() { + super.stopTicking(); + GetGame(this.parent).events.off('step', this.update, this); + } + + update(time, delta) { + if ((!this.isRunning) || (this.timeScale === 0)) { + return this; + } + this.tick(delta); + return this; + } +} + +export default Clock; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/lifetime/LifeTime.d.ts b/ui/src/phaser3-rex-plugins/plugins/time/lifetime/LifeTime.d.ts new file mode 100644 index 000000000..cc1ce926d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/lifetime/LifeTime.d.ts @@ -0,0 +1,34 @@ +import Clock from "../clock/Clock"; + +export default LifeTime; + +declare namespace LifeTime { + interface IConfig { + lifeTime?: number, + destroy?: boolean, + start?: boolean + } + + namespace Events { + type CompleteCallbackType = ( + gameObject: Phaser.GameObjects.GameObject, + lifeTime: LifeTime + ) => void; + } +} + +declare class LifeTime extends Clock { + constructor( + gameObject: Phaser.GameObjects.GameObject, + config?: LifeTime.IConfig + ); + readonly gameObject: Phaser.GameObjects.GameObject; + + setLifeTime(time: number): this; + addToLifeTime(time: number): this; + readonly lifeTime: number; + readonly remainder: number; + readonly isAlive: boolean; + + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/lifetime/LifeTime.js b/ui/src/phaser3-rex-plugins/plugins/time/lifetime/LifeTime.js new file mode 100644 index 000000000..241f12830 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/lifetime/LifeTime.js @@ -0,0 +1,72 @@ +import Clock from '../../clock.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class LifeTime extends Clock { + resetFromJSON(o) { + super.resetFromJSON(o); + this.setLifeTime(GetValue(o, 'lifeTime', 1000)); + this.setDestroyMode(GetValue(o, 'destroy', true)); + if (GetValue(o, 'start', true)) { + this.start(); + } + return this; + } + + toJSON() { + var o = super.toJSON(); + o.lifeTime = this.lifeTime; + return o; + } + + setLifeTime(time) { + this.lifeTime = time; + return this; + } + + addToLifeTime(time) { + this.lifeTime += time; + return this; + } + + setDestroyMode(enable) { + if (enable === undefined) { + enable = true; + } + this.destroyMode = enable; + return this; + } + + get isAlive() { + return this.now < this.lifeTime; + } + + get remainder() { + var remainder = this.lifeTime - this.now; + if (remainder < 0) { + remainder = 0; + } + return remainder; + } + + update(time, delta) { + if (!this.isRunning) { + return this; + } + + super.update(time, delta); + if (!this.isAlive) { + this.complete(); + if (this.destroyMode) { + this.gameObject.destroy(); + } + } + return this; + } + + get gameObject() { + return this.parent; + } +} + +export default LifeTime; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/progresses/Timeline.js b/ui/src/phaser3-rex-plugins/plugins/time/progresses/Timeline.js new file mode 100644 index 000000000..f3dcf1959 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/progresses/Timeline.js @@ -0,0 +1,130 @@ +import Clock from '../clock/Clock.js'; +import Timer from './Timer.js'; +import Pool from './TimerPool.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const TimerPool = new Pool(); + +class Timeline extends Clock { + constructor(parent, config) { + super(parent, config); + + this.addedTimers = []; + this.timers = []; + this.timerPool = GetValue(config, 'pool', TimerPool); + } + + shutdown() { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.timerPool + .freeMultiple(this.addedTimers) + .freeMultiple(this.timers); + + this.timerPool = undefined; + this.addedTimers = undefined; + this.timers = undefined; + + super.shutdown(); + } + + addTimer(config) { + var timer = this.timerPool.allocate(); + if (!timer) { + timer = new Timer(this, config) + } else { + timer + .setTimeline(this) + .reset(config) + } + this.addedTimers.push(timer); + timer.runCallback(timer.onStart); + + if (!this.isRunning) { + this.start(); + } + return timer; + } + + delayCall(delay, callback, args, scope) { + var timer = this.addTimer({ + duration: delay, + onComplete: function (target, t, timer) { + if (args === undefined) { + args = []; + } + args.push(timer); + callback.apply(scope, args); + } + }) + return timer; + } + + delayEvent(delay, eventName) { + this.removeDelayEvent(eventName); + // Clear existed event + + var timer = this.delayCall(delay, function () { + this.removeDelayEvent(eventName); // Clear this timer + this.emit(eventName); + }, [], this); + + this.once(`_remove.${eventName}`, function () { + timer.remove(); + timer = undefined; + }); + return this; + } + + removeDelayEvent(eventName) { + this.emit(`_remove.${eventName}`); + return this; + } + + getTimers(name) { + var timers = []; + + var timerQueues = [this.addedTimers, this.timers]; + for (var ti = 0, tcnt = timerQueues.length; ti < tcnt; ti++) { + var timerQueue = timerQueues[ti]; + for (var i = 0, cnt = timerQueue.length; i < cnt; i++) { + var timer = timerQueue[i]; + if (timer.name === name) { + timers.push(timer); + } + } + } + return timers; + } + + update(time, delta) { + super.update(time, delta); + + if (!this.isRunning) { + return; + } + + this.timers.push(...this.addedTimers); + this.addedTimers.length = 0; + var pendingTimers = []; + for (var i = 0, cnt = this.timers.length; i < cnt; i++) { + var timer = this.timers[i]; + var isStopped = timer.update(this.now, this.delta); + if (isStopped) { + this.timerPool.free(timer); // Free timer + } else { + pendingTimers.push(timer); // Add to timer queue + } + } + this.timers = pendingTimers; + + if ((this.timers.length === 0) && (this.addedTimers.length === 0)) { + this.complete(); // Emit 'complete' event + } + } +} + +export default Timeline \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/progresses/Timer.js b/ui/src/phaser3-rex-plugins/plugins/time/progresses/Timer.js new file mode 100644 index 000000000..1a644ecab --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/progresses/Timer.js @@ -0,0 +1,127 @@ +import Yoyo from '../../utils/math/Yoyo.js'; + +const Clamp = Phaser.Math.Clamp; + +class Timer { + constructor(timeline, config) { + this + .setTimeline(timeline) + .reset(config) + } + + setTimeline(timeline) { + this.timeline = timeline; + return this; + } + + setName(name) { + this.name = name; + return this; + } + + setCallbacks(target, onStart, onProgress, onComplete) { + this.target = target; + this.onStart = onStart; + this.onProgress = onProgress; + this.onComplete = onComplete; + return this; + } + + setDuration(duration, yoyo) { + if (yoyo === undefined) { + yoyo = false; + } + this.duration = duration; + this.remainder = duration; + this.t = 0; + this.yoyo = yoyo; + return this; + } + + setPaused(state) { + this.isPaused = state; + return this; + } + + pause() { + this.isPaused = true; + return this; + } + + resume() { + this.isPaused = false; + return this; + } + + setRemoved(state) { + this.removed = state; + return this; + } + + remove() { + this.removed = true; + return this; + } + + seek(t) { + this.remainder = this.duration * (1 - t); + return this; + } + + reset(o) { + this + .setName(o.name) + .setDuration(o.duration, o.yoyo) + .setCallbacks(o.target, o.onStart, o.onProgress, o.onComplete) + .setPaused(false) + .setRemoved(false) + return this; + } + + onFree() { + this + .setTimeline() + .setCallbacks() + } + + getProgress() { + var value = 1 - (this.remainder / this.duration); + value = Clamp(value, 0, 1); + if (this.yoyo) { + value = Yoyo(value); + } + return value; + } + + setProgress(value) { + value = Clamp(value, 0, 1); + this.remainder = this.duration * (1 - value); + } + + runCallback(callback) { + if (!callback) { + return; + } + callback(this.target, this.t, this); + } + + update(time, delta) { + if (this.removed) { + return true; + } else if (this.isPaused) { + return false; + } + + this.remainder -= delta; + this.t = this.getProgress(); + this.runCallback(this.onProgress); + + var isCompleted = (this.remainder <= 0); + if (isCompleted) { + this.runCallback(this.onComplete); + } + return isCompleted; + } +} + +export default Timer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/progresses/TimerPool.js b/ui/src/phaser3-rex-plugins/plugins/time/progresses/TimerPool.js new file mode 100644 index 000000000..cf77a2178 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/progresses/TimerPool.js @@ -0,0 +1,21 @@ +import Pool from '../../pool.js'; + +class TimerPool extends Pool { + allocate() { + return this.pop(); + } + + free(timer) { + timer.onFree(); + this.push(timer); + } + + freeMultiple(arr) { + for (var i = 0, cnt = arr.length; i < cnt; i++) { + this.free(arr[i]); + } + return this; + } +} + +export default TimerPool; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/realtimetimers/RealTimeTimers.d.ts b/ui/src/phaser3-rex-plugins/plugins/time/realtimetimers/RealTimeTimers.d.ts new file mode 100644 index 000000000..4332e6e5b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/realtimetimers/RealTimeTimers.d.ts @@ -0,0 +1,81 @@ +import { EventEmitter } from 'eventemitter3'; + +export default RealTimeTimers; + +declare namespace RealTimeTimers { + interface ITimer { + name: string | number, + start: number, + period: number, + data?: any + } + + interface IState { + timers?: ITimer[]; + } + + type GetTimestampCallbackType = () => number; + + interface IConfig extends IState { + getTimestampCallback?: GetTimestampCallbackType; + startTimestamp?: number; + } + + interface IPeriod { + day?: number, d?: number, + hour?: number, h?: number, + minute?: number, m?: number, + second?: number, s?: number, + } + + interface IProgress { + name: string | number, + period: number, + elapsed: number, + progress: number, + timer: ITimer + } +} + +declare class RealTimeTimers extends EventEmitter { + constructor( + config?: RealTimeTimers.IConfig + ); + + timers: RealTimeTimers.ITimer[]; + + resetFromJSON(state?: RealTimeTimers.IState): this; + toJSON(): RealTimeTimers.IState; + + setStartTimestamp(timestamp?: number): this; + setGetTimestampCallback(callback?: RealTimeTimers.GetTimestampCallbackType): this; + + addTimer( + name: string | number, + period: number | RealTimeTimers.IPeriod, + data?: any, + currentTimestamp?: number + ): this; + + incTimerPeriod( + name: string | number, + period: number | RealTimeTimers.IPeriod + ): this; + + getExpiredTimers(currentTimestamp?: number): RealTimeTimers.ITimer[]; + popExpiredTimers(currentTimestamp?: number): RealTimeTimers.ITimer[]; + getTimersProgress(currentTimestamp?: number): RealTimeTimers.IProgress[]; + + getTimers(): RealTimeTimers.ITimer[]; + getTimers(name: string | number): RealTimeTimers.ITimer[]; + readonly lastTimer: RealTimeTimers.ITimer; + readonly length: number; + + removeTimers(name: string | number,): this; + removeTimers(timer: RealTimeTimers.ITimer): this; + removeTimers(timers: RealTimeTimers.ITimer[]): this; + + clearTimers(): this; + + emitUpdateEvent(): this; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/time/realtimetimers/RealTimeTimers.js b/ui/src/phaser3-rex-plugins/plugins/time/realtimetimers/RealTimeTimers.js new file mode 100644 index 000000000..90f07be75 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/time/realtimetimers/RealTimeTimers.js @@ -0,0 +1,192 @@ +import EventEmitter from 'eventemitter3'; +import GetValue from '../../utils/object/GetValue.js'; +import GetPeriodMS from '../../utils/time/GetPeriodMS.js'; +import RemoveItems from '../../utils/array/Remove.js'; + +class RealTimeTimers extends EventEmitter { + constructor(config) { + super(); + + var getTimestampCallback = GetValue(config, 'getTimestampCallback'); + if (!getTimestampCallback) { + this.setStartTimestamp(GetValue(config, 'startTimestamp')); + getTimestampCallback = GetCurrentTimestampFromStartCallback.bind(this); + } + this.setGetTimestampCallback(getTimestampCallback); + + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.timers = GetValue(o, 'timers', []); + return this; + } + + toJSON() { + return { + timers: this.timers + } + } + + setStartTimestamp(timestamp) { + if (timestamp === undefined) { + timestamp = new Date().getTime(); + } + this.startTimestamp = timestamp - window.performance.now(); + return this; + } + + setGetTimestampCallback(callback) { + this.getCurrentTimestampCallback = callback; + return this; + } + + addTimer(name, period, data, currentTimestamp) { + if (currentTimestamp === undefined) { + currentTimestamp = this.getCurrentTimestampCallback(); + } + + period = GetPeriodMS(period); + + var timer = { + name: name, + start: currentTimestamp, + period: period, + data: data + } + if (data !== undefined) { + timer.data = data; + } + this._add(timer); + + return this; + } + + incTimerPeriod(name, period) { + period = GetPeriodMS(period); + for (var i = 0, cnt = this.timers.length; i < cnt; i++) { + var timer = this.timers[i]; + if (timer.name === name) { + timer.period += period; + } + } + + this.emitUpdateEvent(); + + return this; + } + + getExpiredTimers(currentTimestamp) { + if (currentTimestamp === undefined) { + currentTimestamp = this.getCurrentTimestampCallback(); + } + + var result = []; + for (var i = 0, cnt = this.timers.length; i < cnt; i++) { + var timer = this.timers[i]; + if (currentTimestamp >= (timer.start + timer.period)) { + result.push(timer); + } + } + return result; + } + + popExpiredTimers(currentTimestamp) { + var result = this.getExpiredTimers(currentTimestamp); + this._remove(result); + return result; + } + + getTimersProgress(currentTimestamp) { + if (currentTimestamp === undefined) { + currentTimestamp = this.getCurrentTimestampCallback(); + } + + var result = []; + for (var i = 0, cnt = this.timers.length; i < cnt; i++) { + var timer = this.timers[i]; + var elapsed = currentTimestamp - timer.start; + var period = timer.period; + elapsed = Math.min(elapsed, period); + var progress = elapsed / period; + result.push({ + name: timer.name, + period: period, + elapsed: elapsed, + progress: progress, + timer: timer + }) + } + return result; + } + + getTimers(name) { + if (name === undefined) { + // Get all timers + return this.timers.slice(); + } + + var result = []; + for (var i = 0, cnt = this.timers.length; i < cnt; i++) { + var timer = this.timers[i]; + if (timer.name === name) { + result.push(timer); + } + } + return result; + } + + removeTimers(timers) { + if (typeof (timers) !== 'object') { // string or number + timers = this.getTimers(timers); + } + if (!Array.isArray(timers)) { + timers = [timers]; + } + this._remove(timers); + return this; + } + + clearTimers() { + var timers = this.getTimers(); + timers.reverse(); + this.removeTimers(timers); + return this; + } + + get length() { + return this.timers.length; + } + + get lastTimer() { + return this.timers[this.timers.length - 1]; + } + + emitUpdateEvent() { + this.emit('update', this.timers); + return this; + } + + // Internal + _add(timer) { + this.timers.push(timer); + + this.emit('add', timer, this.timers); + this.emitUpdateEvent(); + } + + // Internal + _remove(timers) { + RemoveItems(this.timers, timers, function (timer) { + this.emit('remove', timer, this.timers); + }, this); + this.emitUpdateEvent(); + } + +} + +var GetCurrentTimestampFromStartCallback = function () { + return this.startTimestamp + window.performance.now(); +} + +export default RealTimeTimers; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tintrgb-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/tintrgb-plugin.d.ts new file mode 100644 index 000000000..c0c000bd2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tintrgb-plugin.d.ts @@ -0,0 +1,5 @@ +import AddTintRGBProperties from './tintrgb'; + +export default class TintRGBPlugin extends Phaser.Plugins.BasePlugin { + add: typeof AddTintRGBProperties; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tintrgb-plugin.js b/ui/src/phaser3-rex-plugins/plugins/tintrgb-plugin.js new file mode 100644 index 000000000..e0e937ff4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tintrgb-plugin.js @@ -0,0 +1,19 @@ +import AddTintRGBProperties from './tintrgb.js'; + +class TintRGBPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, tintRGB) { + return AddTintRGBProperties(gameObject, tintRGB) + } +} + +export default TintRGBPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tintrgb.d.ts b/ui/src/phaser3-rex-plugins/plugins/tintrgb.d.ts new file mode 100644 index 000000000..bcbec21d3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tintrgb.d.ts @@ -0,0 +1,2 @@ +import AddTintRGBProperties from './behaviors/tintrgb/AddTintRGBProperties'; +export default AddTintRGBProperties; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/tintrgb.js b/ui/src/phaser3-rex-plugins/plugins/tintrgb.js new file mode 100644 index 000000000..9bd820ebd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/tintrgb.js @@ -0,0 +1,2 @@ +import AddTintRGBProperties from './behaviors/tintrgb/AddTintRGBProperties.js'; +export default AddTintRGBProperties; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toggleswitch-plugin.js b/ui/src/phaser3-rex-plugins/plugins/toggleswitch-plugin.js new file mode 100644 index 000000000..d6855fd23 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toggleswitch-plugin.js @@ -0,0 +1,28 @@ +import Factory from './gameobjects/shape/toggleswitch/Factory.js'; +import Creator from './gameobjects/shape/toggleswitch/Creator.js'; +import ToggleSwitch from './gameobjects/shape/toggleswitch/ToggleSwitch.js'; +import ToggleSwitchShapeFactory from './gameobjects/shape/toggleswitch/ToggleSwitchShapeFactory.js'; +import ToggleSwitchShapeCreator from './gameobjects/shape/toggleswitch/ToggleSwitchShapeCreator.js'; +import ToggleSwitchShape from './gameobjects/shape/toggleswitch/ToggleSwitchShape.js'; +import SetValue from './utils/object/SetValue.js'; + +class ToggleSwitchPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexToggleSwitch', Factory, Creator); + pluginManager.registerGameObject('rexToggleSwitchShape', ToggleSwitchShapeFactory, ToggleSwitchShapeCreator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.ToggleSwitch', ToggleSwitch); +SetValue(window, 'RexPlugins.GameObjects.ToggleSwitchShape', ToggleSwitchShape); + +export default ToggleSwitchPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toggleswitch.d.ts b/ui/src/phaser3-rex-plugins/plugins/toggleswitch.d.ts new file mode 100644 index 000000000..a1f13eaf4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toggleswitch.d.ts @@ -0,0 +1,2 @@ +import ToggleSwitch from './gameobjects/shape/toggleswitch/ToggleSwitch'; +export default ToggleSwitch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toggleswitch.js b/ui/src/phaser3-rex-plugins/plugins/toggleswitch.js new file mode 100644 index 000000000..145a30fed --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toggleswitch.js @@ -0,0 +1,2 @@ +import ToggleSwitch from './gameobjects/shape/toggleswitch/ToggleSwitch.js'; +export default ToggleSwitch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toggleswitchshape.d.ts b/ui/src/phaser3-rex-plugins/plugins/toggleswitchshape.d.ts new file mode 100644 index 000000000..6050bdf04 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toggleswitchshape.d.ts @@ -0,0 +1,2 @@ +import ToggleSwitchShape from './gameobjects/shape/toggleswitch/ToggleSwitchShape'; +export default ToggleSwitchShape; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toggleswitchshape.js b/ui/src/phaser3-rex-plugins/plugins/toggleswitchshape.js new file mode 100644 index 000000000..2bea8cb71 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toggleswitchshape.js @@ -0,0 +1,2 @@ +import ToggleSwitch from './gameobjects/shape/toggleswitch/ToggleSwitchShape.js'; +export default ToggleSwitch; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toonifypipeline-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/toonifypipeline-plugin.d.ts new file mode 100644 index 000000000..3df01c04a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toonifypipeline-plugin.d.ts @@ -0,0 +1,35 @@ +// import * as Phaser from 'phaser'; +import ToonifyPostFxPipeline from './toonifypipeline'; + +export default ToonifyPipelinePlugin; + +declare namespace ToonifyPipelinePlugin { + + interface IConfig { + edgeThreshold?: number, + hueLevels?: number, + sLevels?: number, + vLevels?: number, + edgeColor?: number, + + name?: string, + } + +} + +declare class ToonifyPipelinePlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: ToonifyPipelinePlugin.IConfig + ): ToonifyPostFxPipeline; + + remove( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): this; + + get( + gameObject: Phaser.GameObjects.GameObject, + name?: string + ): ToonifyPostFxPipeline | ToonifyPostFxPipeline[]; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toonifypipeline-plugin.js b/ui/src/phaser3-rex-plugins/plugins/toonifypipeline-plugin.js new file mode 100644 index 000000000..b6d2bc05b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toonifypipeline-plugin.js @@ -0,0 +1,14 @@ +import ToonifyPostFxPipeline from './toonifypipeline.js'; +import BasePostFxPipelinePlugin from './utils/renderer/postfxpipeline/BasePostFxPipelinePlugin.js'; +import SetValue from './utils/object/SetValue.js'; + +class ToonifyPipelinePlugin extends BasePostFxPipelinePlugin { + constructor(pluginManager) { + super(pluginManager); + this.setPostPipelineClass(ToonifyPostFxPipeline, 'rexToonifyPostFx'); + } +} + +SetValue(window, 'RexPlugins.Pipelines.ToonifyPostFx', ToonifyPostFxPipeline); + +export default ToonifyPipelinePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toonifypipeline.d.ts b/ui/src/phaser3-rex-plugins/plugins/toonifypipeline.d.ts new file mode 100644 index 000000000..61b0178bb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toonifypipeline.d.ts @@ -0,0 +1,2 @@ +import ToonifyPostFxPipeline from './shaders/toonify/ToonifyPostFxPipeline'; +export default ToonifyPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toonifypipeline.js b/ui/src/phaser3-rex-plugins/plugins/toonifypipeline.js new file mode 100644 index 000000000..6c9888ac4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toonifypipeline.js @@ -0,0 +1,2 @@ +import ToonifyPostFxPipeline from './shaders/toonify/ToonifyPostFxPipeline.js'; +export default ToonifyPostFxPipeline; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/touchcursor-plugin.js b/ui/src/phaser3-rex-plugins/plugins/touchcursor-plugin.js new file mode 100644 index 000000000..0aba4335b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/touchcursor-plugin.js @@ -0,0 +1,20 @@ +import TouchCursor from './touchcursor.js'; + +class TouchCursorPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new TouchCursor(gameObject, config); + } + +} + +export default TouchCursorPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/touchcursor.js b/ui/src/phaser3-rex-plugins/plugins/touchcursor.js new file mode 100644 index 000000000..e67f6a191 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/touchcursor.js @@ -0,0 +1,2 @@ +import TouchCursor from './input/touchcursor/TouchCursor.js'; +export default TouchCursor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toucheventstop-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/toucheventstop-plugin.d.ts new file mode 100644 index 000000000..e97fd0390 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toucheventstop-plugin.d.ts @@ -0,0 +1,9 @@ +import TouchEventStop from './toucheventstop'; + +export default class TouchEventStopPlugin extends Phaser.Plugins.BasePlugin { + add( + gameObject: Phaser.GameObjects.GameObject, + config?: TouchEventStop.IConfig + ): TouchEventStop; + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toucheventstop-plugin.js b/ui/src/phaser3-rex-plugins/plugins/toucheventstop-plugin.js new file mode 100644 index 000000000..6e08f2478 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toucheventstop-plugin.js @@ -0,0 +1,18 @@ +import TouchEventStop from './toucheventstop.js' + +class TouchEventStopPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new TouchEventStop(gameObject, config); + } +} + +export default TouchEventStopPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toucheventstop.d.ts b/ui/src/phaser3-rex-plugins/plugins/toucheventstop.d.ts new file mode 100644 index 000000000..4f5054671 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toucheventstop.d.ts @@ -0,0 +1,2 @@ +import TouchEventStop from './input/toucheventstop/TouchEventStop'; +export default TouchEventStop; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/toucheventstop.js b/ui/src/phaser3-rex-plugins/plugins/toucheventstop.js new file mode 100644 index 000000000..18c34e149 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/toucheventstop.js @@ -0,0 +1,2 @@ +import TouchEventStop from './input/toucheventstop/TouchEventStop.js'; +export default TouchEventStop; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/touchhelper-plugin.js b/ui/src/phaser3-rex-plugins/plugins/touchhelper-plugin.js new file mode 100644 index 000000000..2cc84cbc9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/touchhelper-plugin.js @@ -0,0 +1,40 @@ +import HitTest from './utils/input/HitTest.js'; +import IsPointerInHitArea from './utils/input/IsPointerInHitArea.js'; +import IsPointerInBounds from './utils/input/IsPointerInBounds.js'; +import TouchGroup from './input/touchgroup/TouchGroup.js'; + +class TouchHelperPlugin extends Phaser.Plugins.BasePlugin { + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + + this.touchGroup = new TouchGroup(this.game); + } + + destroy() { + this.touchGroup.destroy(); + super.destroy(); + } + + isAtTop(groupName, key) { + return this.touchGroup.isAtTop(groupName, key); + } +} + +var methods = { + hitTest: HitTest, + isPointerInHitArea: IsPointerInHitArea, + isPointerInBounds: IsPointerInBounds, +}; + +// mixin +Object.assign( + TouchHelperPlugin.prototype, + methods +); + +export default TouchHelperPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/touchstate-plugin.js b/ui/src/phaser3-rex-plugins/plugins/touchstate-plugin.js new file mode 100644 index 000000000..ca52f1a4a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/touchstate-plugin.js @@ -0,0 +1,20 @@ +import TouchState from './touchstate.js'; + +class TouchStatePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(gameObject, config) { + return new TouchState(gameObject, config); + } + +} + +export default TouchStatePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/touchstate.d.ts b/ui/src/phaser3-rex-plugins/plugins/touchstate.d.ts new file mode 100644 index 000000000..c07d164e0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/touchstate.d.ts @@ -0,0 +1,2 @@ +import TouchState from './input/touchstate/TouchState'; +export default TouchState; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/touchstate.js b/ui/src/phaser3-rex-plugins/plugins/touchstate.js new file mode 100644 index 000000000..6b7bd8a1f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/touchstate.js @@ -0,0 +1,2 @@ +import TouchState from './input/touchstate/TouchState.js'; +export default TouchState; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/transitionimage-plugin.js b/ui/src/phaser3-rex-plugins/plugins/transitionimage-plugin.js new file mode 100644 index 000000000..c9e2f6f0c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/transitionimage-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/container/transitionimage/Factory.js'; +import Creator from './gameobjects/container/transitionimage/Creator.js'; +import TransitionImage from './gameobjects/container/transitionimage/TransitionImage.js'; +import SetValue from './utils/object/SetValue.js'; + +class TransitionImagePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexTransitionImage', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.TransitionImage', TransitionImage); + +export default TransitionImagePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/transitionimage.d.ts b/ui/src/phaser3-rex-plugins/plugins/transitionimage.d.ts new file mode 100644 index 000000000..eed572ed4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/transitionimage.d.ts @@ -0,0 +1,2 @@ +import TransitionImage from './gameobjects/container/transitionimage/TransitionImage'; +export default TransitionImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/transitionimage.js b/ui/src/phaser3-rex-plugins/plugins/transitionimage.js new file mode 100644 index 000000000..834cb9c8d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/transitionimage.js @@ -0,0 +1,2 @@ +import TransitionImage from './gameobjects/container/transitionimage/TransitionImage.js'; +export default TransitionImage; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/triangle-plugin.js b/ui/src/phaser3-rex-plugins/plugins/triangle-plugin.js new file mode 100644 index 000000000..4588dabd6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/triangle-plugin.js @@ -0,0 +1,23 @@ +import Factory from './gameobjects/shape/triangle/Factory.js'; +import Creator from './gameobjects/shape/triangle/Creator.js'; +import Triangle from './gameobjects/shape/triangle/Triangle.js'; +import SetValue from './utils/object/SetValue.js'; + +class TrianglePlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + + // Register our new Game Object type + pluginManager.registerGameObject('rexTriangle', Factory, Creator); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } +} + +SetValue(window, 'RexPlugins.GameObjects.Triangle', Triangle); + +export default TrianglePlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/triangle.d.ts b/ui/src/phaser3-rex-plugins/plugins/triangle.d.ts new file mode 100644 index 000000000..09b36274c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/triangle.d.ts @@ -0,0 +1,2 @@ +import Triangle from './gameobjects/shape/triangle/Triangle'; +export default Triangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/triangle.js b/ui/src/phaser3-rex-plugins/plugins/triangle.js new file mode 100644 index 000000000..40d238fbd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/triangle.js @@ -0,0 +1,2 @@ +import Triangle from './gameobjects/shape/triangle/Triangle.js'; +export default Triangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/uniqueitemlist-plugin.d.ts b/ui/src/phaser3-rex-plugins/plugins/uniqueitemlist-plugin.d.ts new file mode 100644 index 000000000..1268d5814 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/uniqueitemlist-plugin.d.ts @@ -0,0 +1,11 @@ +import UniqueItemList from './uniqueitemlist'; + +export default class UniqueItemListPlugin extends Phaser.Plugins.BasePlugin { + add( + config?: UniqueItemList.IConfig + ): UniqueItemList; + + add( + items?: any[] + ): UniqueItemList; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/uniqueitemlist-plugin.js b/ui/src/phaser3-rex-plugins/plugins/uniqueitemlist-plugin.js new file mode 100644 index 000000000..0efc883fd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/uniqueitemlist-plugin.js @@ -0,0 +1,20 @@ +import UniqueItemList from './uniqueitemlist.js'; + +class UniqueItemListPlugin extends Phaser.Plugins.BasePlugin { + + constructor(pluginManager) { + super(pluginManager); + } + + start() { + var eventEmitter = this.game.events; + eventEmitter.on('destroy', this.destroy, this); + } + + add(config) { + return new UniqueItemList(config); + } + +} + +export default UniqueItemListPlugin; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/uniqueitemlist.d.ts b/ui/src/phaser3-rex-plugins/plugins/uniqueitemlist.d.ts new file mode 100644 index 000000000..a6068cd13 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/uniqueitemlist.d.ts @@ -0,0 +1,2 @@ +import UniqueItemList from './data/uniqueitemlist/UniqueItemList'; +export default UniqueItemList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/uniqueitemlist.js b/ui/src/phaser3-rex-plugins/plugins/uniqueitemlist.js new file mode 100644 index 000000000..2ae0901a7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/uniqueitemlist.js @@ -0,0 +1,2 @@ +import UniqueItemList from './data/uniqueitemlist/UniqueItemList.js'; +export default UniqueItemList; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/actions/AlignConst.js b/ui/src/phaser3-rex-plugins/plugins/utils/actions/AlignConst.js new file mode 100644 index 000000000..b3793d8b8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/actions/AlignConst.js @@ -0,0 +1,18 @@ +const ALIGN = Phaser.Display.Align; +export default { + center: ALIGN.CENTER, + left: ALIGN.LEFT_CENTER, + right: ALIGN.RIGHT_CENTER, + top: ALIGN.TOP_CENTER, + bottom: ALIGN.BOTTOM_CENTER, + + 'left-top': ALIGN.TOP_LEFT, + 'left-center': ALIGN.LEFT_CENTER, + 'left-bottom': ALIGN.BOTTOM_LEFT, + 'center-top': ALIGN.TOP_CENTER, + 'center-center': ALIGN.CENTER, + 'center-bottom': ALIGN.BOTTOM_CENTER, + 'right-top': ALIGN.TOP_RIGHT, + 'right-center': ALIGN.RIGHT_CENTER, + 'right-bottom': ALIGN.BOTTOM_RIGHT +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/actions/AlignIn.js b/ui/src/phaser3-rex-plugins/plugins/utils/actions/AlignIn.js new file mode 100644 index 000000000..7107ee4f5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/actions/AlignIn.js @@ -0,0 +1,9 @@ +import GlobZone from './GlobZone.js'; +import QuickSet from '../align/align/in/QuickSet.js'; + +var AlignIn = function (child, x, y, width, height, align) { + GlobZone.setPosition(x, y).setSize(width, height); + QuickSet(child, GlobZone, align); +} + +export default AlignIn; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/actions/GlobZone.js b/ui/src/phaser3-rex-plugins/plugins/utils/actions/GlobZone.js new file mode 100644 index 000000000..bf130785d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/actions/GlobZone.js @@ -0,0 +1,12 @@ +import NOOP from '../object/NOOP.js'; +var globZone = new Phaser.GameObjects.Zone({ + sys: { + queueDepthSort: NOOP, + events: { + once: NOOP + } + } +}, 0, 0, 1, 1); +globZone.setOrigin(0); + +export default globZone; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/actions/RotateObjectAround.js b/ui/src/phaser3-rex-plugins/plugins/utils/actions/RotateObjectAround.js new file mode 100644 index 000000000..05831cc73 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/actions/RotateObjectAround.js @@ -0,0 +1,9 @@ +const RotateAround = Phaser.Math.RotateAround; + +var RotateObjectAround = function (gameObject, x, y, angle) { + RotateAround(gameObject, x, y, angle); + gameObject.rotation += angle; + return gameObject; +} + +export default RotateObjectAround; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/actions/SortByDisplayOrder.js b/ui/src/phaser3-rex-plugins/plugins/utils/actions/SortByDisplayOrder.js new file mode 100644 index 000000000..6393910c8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/actions/SortByDisplayOrder.js @@ -0,0 +1,8 @@ +var SortByDisplayOrder = function (gameObjects) { + if (gameObjects.length < 2) { + return gameObjects; + } + var input = gameObjects[0].scene.input; + return input.sortGameObjects(gameObjects); +} +export default SortByDisplayOrder; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/const.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/const.js new file mode 100644 index 000000000..0faea699d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/const.js @@ -0,0 +1,122 @@ +var ALIGN_CONST = { + + /** + * A constant representing a top-left alignment or position. + * @constant + * @name Phaser.Display.Align.TOP_LEFT + * @since 3.0.0 + * @type {integer} + */ + TOP_LEFT: 0, + + /** + * A constant representing a top-center alignment or position. + * @constant + * @name Phaser.Display.Align.TOP_CENTER + * @since 3.0.0 + * @type {integer} + */ + TOP_CENTER: 1, + + /** + * A constant representing a top-right alignment or position. + * @constant + * @name Phaser.Display.Align.TOP_RIGHT + * @since 3.0.0 + * @type {integer} + */ + TOP_RIGHT: 2, + + /** + * A constant representing a left-top alignment or position. + * @constant + * @name Phaser.Display.Align.LEFT_TOP + * @since 3.0.0 + * @type {integer} + */ + LEFT_TOP: 3, + + /** + * A constant representing a left-center alignment or position. + * @constant + * @name Phaser.Display.Align.LEFT_CENTER + * @since 3.0.0 + * @type {integer} + */ + LEFT_CENTER: 4, + + /** + * A constant representing a left-bottom alignment or position. + * @constant + * @name Phaser.Display.Align.LEFT_BOTTOM + * @since 3.0.0 + * @type {integer} + */ + LEFT_BOTTOM: 5, + + /** + * A constant representing a center alignment or position. + * @constant + * @name Phaser.Display.Align.CENTER + * @since 3.0.0 + * @type {integer} + */ + CENTER: 6, + + /** + * A constant representing a right-top alignment or position. + * @constant + * @name Phaser.Display.Align.RIGHT_TOP + * @since 3.0.0 + * @type {integer} + */ + RIGHT_TOP: 7, + + /** + * A constant representing a right-center alignment or position. + * @constant + * @name Phaser.Display.Align.RIGHT_CENTER + * @since 3.0.0 + * @type {integer} + */ + RIGHT_CENTER: 8, + + /** + * A constant representing a right-bottom alignment or position. + * @constant + * @name Phaser.Display.Align.RIGHT_BOTTOM + * @since 3.0.0 + * @type {integer} + */ + RIGHT_BOTTOM: 9, + + /** + * A constant representing a bottom-left alignment or position. + * @constant + * @name Phaser.Display.Align.BOTTOM_LEFT + * @since 3.0.0 + * @type {integer} + */ + BOTTOM_LEFT: 10, + + /** + * A constant representing a bottom-center alignment or position. + * @constant + * @name Phaser.Display.Align.BOTTOM_CENTER + * @since 3.0.0 + * @type {integer} + */ + BOTTOM_CENTER: 11, + + /** + * A constant representing a bottom-right alignment or position. + * @constant + * @name Phaser.Display.Align.BOTTOM_RIGHT + * @since 3.0.0 + * @type {integer} + */ + BOTTOM_RIGHT: 12 + +}; + +export default ALIGN_CONST; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/BottomCenter.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/BottomCenter.js new file mode 100644 index 000000000..cf4cc3191 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/BottomCenter.js @@ -0,0 +1,16 @@ +import GetBottom from '../../bounds/GetBottom.js'; +import GetCenterX from '../../bounds/GetCenterX.js'; +import SetBottom from '../../bounds/SetBottom.js'; +import SetCenterX from '../../bounds/SetCenterX.js'; + +var BottomCenter = function (gameObject, alignIn, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetCenterX(gameObject, GetCenterX(alignIn) + offsetX); + SetBottom(gameObject, GetBottom(alignIn) + offsetY); + + return gameObject; +}; + +export default BottomCenter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/BottomLeft.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/BottomLeft.js new file mode 100644 index 000000000..21ce980bd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/BottomLeft.js @@ -0,0 +1,16 @@ +import GetBottom from '../../bounds/GetBottom.js'; +import GetLeft from '../../bounds/GetLeft.js'; +import SetBottom from '../../bounds/SetBottom.js'; +import SetLeft from '../../bounds/SetLeft.js'; + +var BottomLeft = function (gameObject, alignIn, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetLeft(gameObject, GetLeft(alignIn) - offsetX); + SetBottom(gameObject, GetBottom(alignIn) + offsetY); + + return gameObject; +}; + +export default BottomLeft; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/BottomRight.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/BottomRight.js new file mode 100644 index 000000000..d354a4ead --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/BottomRight.js @@ -0,0 +1,16 @@ +import GetBottom from '../../bounds/GetBottom.js'; +import GetRight from '../../bounds/GetRight.js'; +import SetBottom from '../../bounds/SetBottom.js'; +import SetRight from '../../bounds/SetRight.js'; + +var BottomRight = function (gameObject, alignIn, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetRight(gameObject, GetRight(alignIn) + offsetX); + SetBottom(gameObject, GetBottom(alignIn) + offsetY); + + return gameObject; +}; + +export default BottomRight; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/Center.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/Center.js new file mode 100644 index 000000000..7226c6b18 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/Center.js @@ -0,0 +1,14 @@ +import CenterOn from '../../bounds/CenterOn.js'; +import GetCenterX from '../../bounds/GetCenterX.js'; +import GetCenterY from '../../bounds/GetCenterY.js'; + +var Center = function (gameObject, alignIn, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + CenterOn(gameObject, GetCenterX(alignIn) + offsetX, GetCenterY(alignIn) + offsetY); + + return gameObject; +}; + +export default Center; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/LeftCenter.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/LeftCenter.js new file mode 100644 index 000000000..65ebb4e54 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/LeftCenter.js @@ -0,0 +1,16 @@ +import GetCenterY from '../../bounds/GetCenterY.js'; +import GetLeft from '../../bounds/GetLeft.js'; +import SetCenterY from '../../bounds/SetCenterY.js'; +import SetLeft from '../../bounds/SetLeft.js'; + +var LeftCenter = function (gameObject, alignIn, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetLeft(gameObject, GetLeft(alignIn) - offsetX); + SetCenterY(gameObject, GetCenterY(alignIn) + offsetY); + + return gameObject; +}; + +export default LeftCenter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/QuickSet.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/QuickSet.js new file mode 100644 index 000000000..4209bcb3e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/QuickSet.js @@ -0,0 +1,28 @@ +import ALIGN_CONST from '../const.js'; +import BottomCenter from './BottomCenter.js'; +import BottomLeft from './BottomLeft.js'; +import BottomRight from './BottomRight.js'; +import Center from './Center.js'; +import LeftCenter from './LeftCenter.js'; +import RightCenter from './RightCenter.js'; +import TopCenter from './TopCenter.js'; +import TopLeft from './TopLeft.js'; +import TopRight from './TopRight.js'; + +var AlignInMap = []; + +AlignInMap[ALIGN_CONST.BOTTOM_CENTER] = BottomCenter; +AlignInMap[ALIGN_CONST.BOTTOM_LEFT] = BottomLeft; +AlignInMap[ALIGN_CONST.BOTTOM_RIGHT] = BottomRight; +AlignInMap[ALIGN_CONST.CENTER] = Center; +AlignInMap[ALIGN_CONST.LEFT_CENTER] = LeftCenter; +AlignInMap[ALIGN_CONST.RIGHT_CENTER] = RightCenter; +AlignInMap[ALIGN_CONST.TOP_CENTER] = TopCenter; +AlignInMap[ALIGN_CONST.TOP_LEFT] = TopLeft; +AlignInMap[ALIGN_CONST.TOP_RIGHT] = TopRight; + +var QuickSet = function (child, alignIn, position, offsetX, offsetY) { + return AlignInMap[position](child, alignIn, offsetX, offsetY); +}; + +export default QuickSet; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/RightCenter.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/RightCenter.js new file mode 100644 index 000000000..fcb907c3f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/RightCenter.js @@ -0,0 +1,16 @@ +import GetCenterY from '../../bounds/GetCenterY.js'; +import GetRight from '../../bounds/GetRight.js'; +import SetCenterY from '../../bounds/SetCenterY.js'; +import SetRight from '../../bounds/SetRight.js'; + +var RightCenter = function (gameObject, alignIn, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetRight(gameObject, GetRight(alignIn) + offsetX); + SetCenterY(gameObject, GetCenterY(alignIn) + offsetY); + + return gameObject; +}; + +export default RightCenter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/TopCenter.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/TopCenter.js new file mode 100644 index 000000000..439be5f24 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/TopCenter.js @@ -0,0 +1,16 @@ +import GetCenterX from '../../bounds/GetCenterX.js'; +import GetTop from '../../bounds/GetTop.js'; +import SetCenterX from '../../bounds/SetCenterX.js'; +import SetTop from '../../bounds/SetTop.js'; + +var TopCenter = function (gameObject, alignIn, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetCenterX(gameObject, GetCenterX(alignIn) + offsetX); + SetTop(gameObject, GetTop(alignIn) - offsetY); + + return gameObject; +}; + +export default TopCenter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/TopLeft.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/TopLeft.js new file mode 100644 index 000000000..049232fd2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/TopLeft.js @@ -0,0 +1,16 @@ +import GetLeft from '../../bounds/GetLeft.js'; +import GetTop from '../../bounds/GetTop.js'; +import SetLeft from '../../bounds/SetLeft.js'; +import SetTop from '../../bounds/SetTop.js'; + +var TopLeft = function (gameObject, alignIn, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetLeft(gameObject, GetLeft(alignIn) - offsetX); + SetTop(gameObject, GetTop(alignIn) - offsetY); + + return gameObject; +}; + +export default TopLeft; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/TopRight.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/TopRight.js new file mode 100644 index 000000000..093ec8629 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/TopRight.js @@ -0,0 +1,16 @@ +import GetRight from '../../bounds/GetRight.js'; +import GetTop from '../../bounds/GetTop.js'; +import SetRight from '../../bounds/SetRight.js'; +import SetTop from '../../bounds/SetTop.js'; + +var TopRight = function (gameObject, alignIn, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetRight(gameObject, GetRight(alignIn) + offsetX); + SetTop(gameObject, GetTop(alignIn) - offsetY); + + return gameObject; +}; + +export default TopRight; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/index.js new file mode 100644 index 000000000..8ea788121 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/in/index.js @@ -0,0 +1,23 @@ +import BottomCenter from './BottomCenter.js'; +import BottomLeft from './BottomLeft.js'; +import BottomRight from './BottomRight.js'; +import Center from './Center.js'; +import LeftCenter from './LeftCenter.js'; +import QuickSet from './QuickSet.js'; +import RightCenter from './RightCenter.js'; +import TopCenter from './TopCenter.js'; +import TopLeft from './TopLeft.js'; +import TopRight from './TopRight.js'; + +export default { + BottomCenter: BottomCenter, + BottomLeft: BottomLeft, + BottomRight: BottomRight, + Center: Center, + LeftCenter: LeftCenter, + QuickSet: QuickSet, + RightCenter: RightCenter, + TopCenter: TopCenter, + TopLeft: TopLeft, + TopRight: TopRight +}; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/index.js new file mode 100644 index 000000000..95c68ab97 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/index.js @@ -0,0 +1,17 @@ +import AlignIn from './in/index.js'; +import AlignTo from './to/index.js'; +import CONST from './const.js'; + +var Align = { + In: AlignIn, + To: AlignTo +}; + +// Merge in the consts +Object.assign( + Align, + CONST +); + + +export default Align; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/BottomCenter.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/BottomCenter.js new file mode 100644 index 000000000..e15bea2b6 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/BottomCenter.js @@ -0,0 +1,16 @@ +import GetBottom from '../../bounds/GetBottom.js'; +import GetCenterX from '../../bounds/GetCenterX.js'; +import SetCenterX from '../../bounds/SetCenterX.js'; +import SetTop from '../../bounds/SetTop.js'; + +var BottomCenter = function (gameObject, alignTo, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetCenterX(gameObject, GetCenterX(alignTo) + offsetX); + SetTop(gameObject, GetBottom(alignTo) + offsetY); + + return gameObject; +}; + +export default BottomCenter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/BottomLeft.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/BottomLeft.js new file mode 100644 index 000000000..2cddf7a30 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/BottomLeft.js @@ -0,0 +1,16 @@ +import GetBottom from '../../bounds/GetBottom.js'; +import GetLeft from '../../bounds/GetLeft.js'; +import SetLeft from '../../bounds/SetLeft.js'; +import SetTop from '../../bounds/SetTop.js'; + +var BottomLeft = function (gameObject, alignTo, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetLeft(gameObject, GetLeft(alignTo) - offsetX); + SetTop(gameObject, GetBottom(alignTo) + offsetY); + + return gameObject; +}; + +export default BottomLeft; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/BottomRight.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/BottomRight.js new file mode 100644 index 000000000..bf6e93ccd --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/BottomRight.js @@ -0,0 +1,16 @@ +import GetBottom from '../../bounds/GetBottom.js'; +import GetRight from '../../bounds/GetRight.js'; +import SetRight from '../../bounds/SetRight.js'; +import SetTop from '../../bounds/SetTop.js'; + +var BottomRight = function (gameObject, alignTo, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetRight(gameObject, GetRight(alignTo) + offsetX); + SetTop(gameObject, GetBottom(alignTo) + offsetY); + + return gameObject; +}; + +export default BottomRight; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/LeftBottom.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/LeftBottom.js new file mode 100644 index 000000000..a46e06282 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/LeftBottom.js @@ -0,0 +1,16 @@ +import GetBottom from '../../bounds/GetBottom.js'; +import GetLeft from '../../bounds/GetLeft.js'; +import SetBottom from '../../bounds/SetBottom.js'; +import SetRight from '../../bounds/SetRight.js'; + +var LeftBottom = function (gameObject, alignTo, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetRight(gameObject, GetLeft(alignTo) - offsetX); + SetBottom(gameObject, GetBottom(alignTo) + offsetY); + + return gameObject; +}; + +export default LeftBottom; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/LeftCenter.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/LeftCenter.js new file mode 100644 index 000000000..84abdee8e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/LeftCenter.js @@ -0,0 +1,16 @@ +import GetCenterY from '../../bounds/GetCenterY.js'; +import GetLeft from '../../bounds/GetLeft.js'; +import SetCenterY from '../../bounds/SetCenterY.js'; +import SetRight from '../../bounds/SetRight.js'; + +var LeftCenter = function (gameObject, alignTo, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetRight(gameObject, GetLeft(alignTo) - offsetX); + SetCenterY(gameObject, GetCenterY(alignTo) + offsetY); + + return gameObject; +}; + +export default LeftCenter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/LeftTop.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/LeftTop.js new file mode 100644 index 000000000..3abc8dc58 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/LeftTop.js @@ -0,0 +1,16 @@ +import GetLeft from '../../bounds/GetLeft.js'; +import GetTop from '../../bounds/GetTop.js'; +import SetRight from '../../bounds/SetRight.js'; +import SetTop from '../../bounds/SetTop.js'; + +var LeftTop = function (gameObject, alignTo, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetRight(gameObject, GetLeft(alignTo) - offsetX); + SetTop(gameObject, GetTop(alignTo) - offsetY); + + return gameObject; +}; + +export default LeftTop; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/RightBottom.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/RightBottom.js new file mode 100644 index 000000000..817514603 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/RightBottom.js @@ -0,0 +1,16 @@ +import GetBottom from '../../bounds/GetBottom.js'; +import GetRight from '../../bounds/GetRight.js'; +import SetBottom from '../../bounds/SetBottom.js'; +import SetLeft from '../../bounds/SetLeft.js'; + +var RightBottom = function (gameObject, alignTo, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetLeft(gameObject, GetRight(alignTo) + offsetX); + SetBottom(gameObject, GetBottom(alignTo) + offsetY); + + return gameObject; +}; + +export default RightBottom; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/RightCenter.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/RightCenter.js new file mode 100644 index 000000000..826ecf0c1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/RightCenter.js @@ -0,0 +1,16 @@ +import GetCenterY from '../../bounds/GetCenterY.js'; +import GetRight from '../../bounds/GetRight.js'; +import SetCenterY from '../../bounds/SetCenterY.js'; +import SetLeft from '../../bounds/SetLeft.js'; + +var RightCenter = function (gameObject, alignTo, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetLeft(gameObject, GetRight(alignTo) + offsetX); + SetCenterY(gameObject, GetCenterY(alignTo) + offsetY); + + return gameObject; +}; + +export default RightCenter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/RightTop.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/RightTop.js new file mode 100644 index 000000000..aeb11052a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/RightTop.js @@ -0,0 +1,16 @@ +import GetRight from '../../bounds/GetRight.js'; +import GetTop from '../../bounds/GetTop.js'; +import SetLeft from '../../bounds/SetLeft.js'; +import SetTop from '../../bounds/SetTop.js'; + +var RightTop = function (gameObject, alignTo, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetLeft(gameObject, GetRight(alignTo) + offsetX); + SetTop(gameObject, GetTop(alignTo) - offsetY); + + return gameObject; +}; + +export default RightTop; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/TopCenter.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/TopCenter.js new file mode 100644 index 000000000..1838f5395 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/TopCenter.js @@ -0,0 +1,16 @@ +import GetCenterX from '../../bounds/GetCenterX.js'; +import GetTop from '../../bounds/GetTop.js'; +import SetBottom from '../../bounds/SetBottom.js'; +import SetCenterX from '../../bounds/SetCenterX.js'; + +var TopCenter = function (gameObject, alignTo, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetCenterX(gameObject, GetCenterX(alignTo) + offsetX); + SetBottom(gameObject, GetTop(alignTo) - offsetY); + + return gameObject; +}; + +export default TopCenter; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/TopLeft.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/TopLeft.js new file mode 100644 index 000000000..dbb6f9587 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/TopLeft.js @@ -0,0 +1,16 @@ +import GetLeft from '../../bounds/GetLeft.js'; +import GetTop from '../../bounds/GetTop.js'; +import SetBottom from '../../bounds/SetBottom.js'; +import SetLeft from '../../bounds/SetLeft.js'; + +var TopLeft = function (gameObject, alignTo, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetLeft(gameObject, GetLeft(alignTo) - offsetX); + SetBottom(gameObject, GetTop(alignTo) - offsetY); + + return gameObject; +}; + +export default TopLeft; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/TopRight.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/TopRight.js new file mode 100644 index 000000000..acfa36730 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/TopRight.js @@ -0,0 +1,16 @@ +import GetRight from '../../bounds/GetRight.js'; +import GetTop from '../../bounds/GetTop.js'; +import SetBottom from '../../bounds/SetBottom.js'; +import SetRight from '../../bounds/SetRight.js'; + +var TopRight = function (gameObject, alignTo, offsetX, offsetY) { + if (offsetX === undefined) { offsetX = 0; } + if (offsetY === undefined) { offsetY = 0; } + + SetRight(gameObject, GetRight(alignTo) + offsetX); + SetBottom(gameObject, GetTop(alignTo) - offsetY); + + return gameObject; +}; + +export default TopRight; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/index.js new file mode 100644 index 000000000..37ffb80ba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/align/to/index.js @@ -0,0 +1,27 @@ +import BottomCenter from './BottomCenter.js'; +import BottomLeft from './BottomLeft.js'; +import BottomRight from './BottomRight.js'; +import LeftBottom from './LeftBottom.js'; +import LeftCenter from './LeftCenter.js'; +import LeftTop from './LeftTop.js'; +import RightBottom from './RightBottom.js'; +import RightCenter from './RightCenter.js'; +import RightTop from './RightTop.js'; +import TopCenter from './TopCenter.js'; +import TopLeft from './TopLeft.js'; +import TopRight from './TopRight.js'; + +export default { + BottomCenter: BottomCenter, + BottomLeft: BottomLeft, + BottomRight: BottomRight, + LeftBottom: LeftBottom, + LeftCenter: LeftCenter, + LeftTop: LeftTop, + RightBottom: RightBottom, + RightCenter: RightCenter, + RightTop: RightTop, + TopCenter: TopCenter, + TopLeft: TopLeft, + TopRight: TopRight +}; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/CenterOn.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/CenterOn.js new file mode 100644 index 000000000..17ae1bc25 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/CenterOn.js @@ -0,0 +1,9 @@ +import SetCenterX from './SetCenterX.js'; +import SetCenterY from './SetCenterY.js'; + +var CenterOn = function (gameObject, x, y) { + SetCenterX(gameObject, x); + return SetCenterY(gameObject, y); +}; + +export default CenterOn; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetBottom.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetBottom.js new file mode 100644 index 000000000..2f7b9f609 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetBottom.js @@ -0,0 +1,8 @@ +import { GetDisplayHeight } from '../../size/GetDisplaySize.js'; + +var GetBottom = function (gameObject) { + var height = GetDisplayHeight(gameObject); + return (gameObject.y + height) - (height * gameObject.originY); +}; + +export default GetBottom; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetCenterX.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetCenterX.js new file mode 100644 index 000000000..73ef7b411 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetCenterX.js @@ -0,0 +1,8 @@ +import { GetDisplayWidth } from '../../size/GetDisplaySize.js'; + +var GetCenterX = function (gameObject) { + var width = GetDisplayWidth(gameObject); + return gameObject.x - (width * gameObject.originX) + (width * 0.5); +}; + +export default GetCenterX; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetCenterY.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetCenterY.js new file mode 100644 index 000000000..682b3ed78 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetCenterY.js @@ -0,0 +1,8 @@ +import { GetDisplayHeight } from '../../size/GetDisplaySize.js'; + +var GetCenterY = function (gameObject) { + var height = GetDisplayHeight(gameObject); + return gameObject.y - (height * gameObject.originY) + (height * 0.5); +}; + +export default GetCenterY; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetLeft.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetLeft.js new file mode 100644 index 000000000..55ef53bf8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetLeft.js @@ -0,0 +1,8 @@ +import { GetDisplayWidth } from '../../size/GetDisplaySize.js'; + +var GetLeft = function (gameObject) { + var width = GetDisplayWidth(gameObject); + return gameObject.x - (width * gameObject.originX); +}; + +export default GetLeft; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetOffsetX.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetOffsetX.js new file mode 100644 index 000000000..c04d096c5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetOffsetX.js @@ -0,0 +1,8 @@ +import { GetDisplayWidth } from '../../size/GetDisplaySize.js'; + +var GetOffsetX = function (gameObject) { + var width = GetDisplayWidth(gameObject); + return width * gameObject.originX; +}; + +export default GetOffsetX; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetOffsetY.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetOffsetY.js new file mode 100644 index 000000000..6a2a36316 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetOffsetY.js @@ -0,0 +1,8 @@ +import { GetDisplayHeight } from '../../size/GetDisplaySize.js'; + +var GetOffsetY = function (gameObject) { + var height = GetDisplayHeight(gameObject); + return height * gameObject.originY; +}; + +export default GetOffsetY; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetRight.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetRight.js new file mode 100644 index 000000000..0771e0dc9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetRight.js @@ -0,0 +1,8 @@ +import { GetDisplayWidth } from '../../size/GetDisplaySize.js'; + +var GetRight = function (gameObject) { + var width = GetDisplayWidth(gameObject); + return (gameObject.x + width) - (width * gameObject.originX); +}; + +export default GetRight; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetTop.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetTop.js new file mode 100644 index 000000000..a77ea4988 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/GetTop.js @@ -0,0 +1,8 @@ +import { GetDisplayHeight } from '../../size/GetDisplaySize.js'; + +var GetTop = function (gameObject) { + var height = GetDisplayHeight(gameObject); + return gameObject.y - (height * gameObject.originY); +}; + +export default GetTop; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetBottom.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetBottom.js new file mode 100644 index 000000000..0085c7ef8 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetBottom.js @@ -0,0 +1,9 @@ +import { GetDisplayHeight } from '../../size/GetDisplaySize.js'; + +var SetBottom = function (gameObject, value) { + var height = GetDisplayHeight(gameObject); + gameObject.y = (value - height) + (height * gameObject.originY); + return gameObject; +}; + +export default SetBottom; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetCenterX.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetCenterX.js new file mode 100644 index 000000000..a099e1ba5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetCenterX.js @@ -0,0 +1,11 @@ +import { GetDisplayWidth } from '../../size/GetDisplaySize.js'; + +var SetCenterX = function (gameObject, x) { + var width = GetDisplayWidth(gameObject); + var offsetX = width * gameObject.originX; + gameObject.x = (x + offsetX) - (width * 0.5); + + return gameObject; +}; + +export default SetCenterX; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetCenterY.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetCenterY.js new file mode 100644 index 000000000..acc5f58bb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetCenterY.js @@ -0,0 +1,11 @@ +import { GetDisplayHeight } from '../../size/GetDisplaySize.js'; + +var SetCenterY = function (gameObject, y) { + var height = GetDisplayHeight(gameObject); + var offsetY = height * gameObject.originY; + gameObject.y = (y + offsetY) - (height * 0.5); + + return gameObject; +}; + +export default SetCenterY; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetLeft.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetLeft.js new file mode 100644 index 000000000..33b557b1a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetLeft.js @@ -0,0 +1,9 @@ +import { GetDisplayWidth } from '../../size/GetDisplaySize.js'; + +var SetLeft = function (gameObject, value) { + var width = GetDisplayWidth(gameObject); + gameObject.x = value + (width * gameObject.originX); + return gameObject; +}; + +export default SetLeft; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetRight.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetRight.js new file mode 100644 index 000000000..b9f114676 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetRight.js @@ -0,0 +1,10 @@ +import { GetDisplayWidth } from '../../size/GetDisplaySize.js'; + +var SetRight = function (gameObject, value) { + var width = GetDisplayWidth(gameObject); + gameObject.x = (value - width) + (width * gameObject.originX); + + return gameObject; +}; + +export default SetRight; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetTop.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetTop.js new file mode 100644 index 000000000..f4d8a9ade --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/SetTop.js @@ -0,0 +1,9 @@ +import { GetDisplayHeight } from '../../size/GetDisplaySize.js'; + +var SetTop = function (gameObject, value) { + var height = GetDisplayHeight(gameObject); + gameObject.y = value + (height * gameObject.originY); + return gameObject; +}; + +export default SetTop; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/index.js new file mode 100644 index 000000000..12fb73327 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/bounds/index.js @@ -0,0 +1,33 @@ +import CenterOn from './CenterOn.js'; +import GetBottom from './GetBottom.js'; +import GetCenterX from './GetCenterX.js'; +import GetCenterY from './GetCenterY.js'; +import GetLeft from './GetLeft.js'; +import GetOffsetX from './GetOffsetX.js'; +import GetOffsetY from './GetOffsetY.js'; +import GetRight from './GetRight.js'; +import GetTop from './GetTop.js'; +import SetBottom from './SetBottom.js'; +import SetCenterX from './SetCenterX.js'; +import SetCenterY from './SetCenterY.js'; +import SetLeft from './SetLeft.js'; +import SetRight from './SetRight.js'; +import SetTop from './SetTop.js'; + +export default { + CenterOn: CenterOn, + GetBottom: GetBottom, + GetCenterX: GetCenterX, + GetCenterY: GetCenterY, + GetLeft: GetLeft, + GetOffsetX: GetOffsetX, + GetOffsetY: GetOffsetY, + GetRight: GetRight, + GetTop: GetTop, + SetBottom: SetBottom, + SetCenterX: SetCenterX, + SetCenterY: SetCenterY, + SetLeft: SetLeft, + SetRight: SetRight, + SetTop: SetTop +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/align/index.js b/ui/src/phaser3-rex-plugins/plugins/utils/align/index.js new file mode 100644 index 000000000..2752176a0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/align/index.js @@ -0,0 +1,7 @@ +import Align from './align/index.js'; +import Bounds from './bounds/index.js'; + +export default { + Align: Align, + Bounds: Bounds +}; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/arcade/BuildArcadeObject.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/arcade/BuildArcadeObject.d.ts new file mode 100644 index 000000000..9368049ac --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/arcade/BuildArcadeObject.d.ts @@ -0,0 +1,9 @@ +export default function BuildArcadeObject( + gameObject: Phaser.GameObjects.GameObject, + isStatic?: boolean +): Phaser.GameObjects.GameObject; + +export default function BuildArcadeObject( + gameObject: Phaser.GameObjects.GameObject[], + isStatic?: boolean +): Phaser.GameObjects.GameObject[]; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/arcade/BuildArcadeObject.js b/ui/src/phaser3-rex-plugins/plugins/utils/arcade/BuildArcadeObject.js new file mode 100644 index 000000000..8828065d7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/arcade/BuildArcadeObject.js @@ -0,0 +1,43 @@ +const Components = Phaser.Physics.Arcade.Components; + +var BuildArcadeObject = function (gameObject, isStatic) { + if (!Array.isArray(gameObject)) { + Build(gameObject, isStatic); + } else { + var gameObjects = gameObject; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + Build(gameObjects[i], isStatic); + } + } + + return gameObject; +}; + +var Build = function (gameObject, isStatic) { + if (!gameObject.body) { + if (isStatic === undefined) { + isStatic = false; + } + gameObject.scene.physics.add.existing(gameObject, isStatic); + } + + Object.assign( + gameObject, + Components.Acceleration, + Components.Angular, + Components.Bounce, + Components.Debug, + Components.Drag, + Components.Enable, + Components.Friction, + Components.Gravity, + Components.Immovable, + Components.Mass, + Components.Size, + Components.Velocity + ); + + return gameObject; +}; + +export default BuildArcadeObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/arcade/Helpers.js b/ui/src/phaser3-rex-plugins/plugins/utils/arcade/Helpers.js new file mode 100644 index 000000000..fa070a888 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/arcade/Helpers.js @@ -0,0 +1,56 @@ +var SetVelocity = function (gameObject, vx, vy, onChange) { + var body = gameObject.body; + var preVx = body.velocity.x, + preVy = body.velocity.y; + if ((vx === preVx) && (vy === preVy)) { + return + } + body.setVelocity(vx, vy); + if (onChange) { + onChange(vx, vy, preVx, preVy); + } +} + +var SetAcceleration = function (gameObject, ax, ay, onChange) { + var body = gameObject.body; + var preAx = body.acceleration.x, + preAy = body.acceleration.y; + if ((ax === preAx) && (ay === preAy)) { + return; + } + body.setAcceleration(ax, ay); + if (onChange) { + onChange(ax, ay, preAx, preAy); + } +} + +var SetAngularVelocity = function (gameObject, av, onChange) { + var body = gameObject.body; + var preAv = body.angularVelocity; + if (av === preAv) { + return; + } + body.setAngularVelocity(av); + if (onChange) { + onChange(av, preAv); + } +} + +var SetAngularAcceleration = function (gameObject, aa, onChange) { + var body = gameObject.body; + var preAa = body.angularAcceleration + if (aa === preAa) { + return; + } + body.setAngularAcceleration(aa); + if (onChange) { + onChange(aa, preAa); + } +} + +export { + SetVelocity, + SetAcceleration, + SetAngularVelocity, + SetAngularAcceleration +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/array/Add.js b/ui/src/phaser3-rex-plugins/plugins/utils/array/Add.js new file mode 100644 index 000000000..375160018 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/array/Add.js @@ -0,0 +1,111 @@ +/** + * @author Richard Davey + * @copyright 2020 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Adds the given item, or array of items, to the array. + * + * Each item must be unique within the array. + * + * The array is modified in-place and returned. + * + * You can optionally specify a limit to the maximum size of the array. If the quantity of items being + * added will take the array length over this limit, it will stop adding once the limit is reached. + * + * You can optionally specify a callback to be invoked for each item successfully added to the array. + * + * @function Phaser.Utils.Array.Add + * @since 3.4.0 + * + * @param {array} array - The array to be added to. + * @param {any|any[]} item - The item, or array of items, to add to the array. Each item must be unique within the array. + * @param {number} [limit] - Optional limit which caps the size of the array. + * @param {function} [callback] - A callback to be invoked for each item successfully added to the array. + * @param {object} [context] - The context in which the callback is invoked. + * + * @return {array} The input array. + */ +var Add = function (array, item, limit, callback, context) +{ + if (context === undefined) { context = array; } + + if (limit > 0) + { + var remaining = limit - array.length; + + // There's nothing more we can do here, the array is full + if (remaining <= 0) + { + return null; + } + } + + // Fast path to avoid array mutation and iteration + if (!Array.isArray(item)) + { + if (array.indexOf(item) === -1) + { + array.push(item); + + if (callback) + { + callback.call(context, item); + } + + return item; + } + else + { + return null; + } + } + + // If we got this far, we have an array of items to insert + + // Ensure all the items are unique + var itemLength = item.length - 1; + + while (itemLength >= 0) + { + if (array.indexOf(item[itemLength]) !== -1) + { + // Already exists in array, so remove it + item.splice(itemLength, 1); + } + + itemLength--; + } + + // Anything left? + itemLength = item.length; + + if (itemLength === 0) + { + return null; + } + + if (limit > 0 && itemLength > remaining) + { + item.splice(remaining); + + itemLength = remaining; + } + + for (var i = 0; i < itemLength; i++) + { + var entry = item[i]; + + array.push(entry); + + if (callback) + { + callback.call(context, entry); + } + } + + return item; +}; + +module.exports = Add; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/array/CSVToArray.js b/ui/src/phaser3-rex-plugins/plugins/utils/array/CSVToArray.js new file mode 100644 index 000000000..2565bd55c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/array/CSVToArray.js @@ -0,0 +1,81 @@ +// copy from +// http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm +// This will parse a delimited string into an array of +// arrays. The default delimiter is the comma, but this +// can be overriden in the second argument. +function CSVToArray(strData, strDelimiter, convert, convertScope) { + // Check to see if the delimiter is defined. If not, + // then default to comma. + strDelimiter = (strDelimiter || ","); + // Create a regular expression to parse the CSV values. + var objPattern = new RegExp( + ( + // Delimiters. + "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" + + // Quoted fields. + "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" + + // Standard fields. + "([^\"\\" + strDelimiter + "\\r\\n]*))" + ), + "gi" + ); + // Create an array to hold our data. Give the array + // a default empty first row. + var arrLastRow = []; + var arrData = [ + arrLastRow + ]; + // Create an array to hold our individual pattern + // matching groups. + var arrMatches = null; + // Keep looping over the regular expression matches + // until we can no longer find a match. + while (arrMatches = objPattern.exec(strData)) { + // Get the delimiter that was found. + var strMatchedDelimiter = arrMatches[1]; + // Check to see if the given delimiter has a length + // (is not the start of string) and if it matches + // field delimiter. If id does not, then we know + // that this delimiter is a row delimiter. + if ( + strMatchedDelimiter.length && + (strMatchedDelimiter != strDelimiter) + ) { + // Since we have reached a new row of data, + // add an empty row to our data array. + arrLastRow = []; + arrData.push(arrLastRow); + } + // Now that we have our delimiter out of the way, + // let's check to see which kind of value we + // captured (quoted or unquoted). + if (arrMatches[2]) { + // We found a quoted value. When we capture + // this value, unescape any double quotes. + var strMatchedValue = arrMatches[2].replace( + new RegExp("\"\"", "g"), + "\"" + ); + } else { + // We found a non-quoted value. + var strMatchedValue = arrMatches[3]; + } + // Now that we have our value string, let's add + // it to the data array. + + // rex: convert value + if (convert) { + if (convertScope) { + strMatchedValue = convert.call(convertScope, strMatchedValue); + } else { + strMatchedValue = convert(strMatchedValue); + } + } + arrLastRow.push(strMatchedValue); + // arrData[arrData.length - 1].push(strMatchedValue); + } + // Return the parsed data. + return (arrData); +}; + +export default CSVToArray; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/array/Copy.js b/ui/src/phaser3-rex-plugins/plugins/utils/array/Copy.js new file mode 100644 index 000000000..2a974579d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/array/Copy.js @@ -0,0 +1,14 @@ +var Copy = function (dest, src, startIdx, endIdx) { + if (startIdx === undefined) { + startIdx = 0 + }; + if (endIdx === undefined) { + endIdx = src.length; + } + dest.length = endIdx - startIdx; + for (var i = 0, len = dest.length; i < len; i++) { + dest[i] = src[i + startIdx]; + } + return dest; +}; +export default Copy; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/array/Fill.js b/ui/src/phaser3-rex-plugins/plugins/utils/array/Fill.js new file mode 100644 index 000000000..4579cda3f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/array/Fill.js @@ -0,0 +1,14 @@ +var Fill = function (arr, value, startIdx, endIdx) { + if (startIdx === undefined) { + startIdx = 0; + } + if (endIdx === undefined) { + endIdx = arr.length - 1; + } + for (var i = startIdx; i <= endIdx; i++) { + arr[i] = value; + } + return arr; +} + +export default Fill; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/array/GetRandom.js b/ui/src/phaser3-rex-plugins/plugins/utils/array/GetRandom.js new file mode 100644 index 000000000..ecc6aec49 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/array/GetRandom.js @@ -0,0 +1,29 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Returns a Random element from the array. + * + * @function Phaser.Utils.Array.GetRandom + * @since 3.0.0 + * + * @param {array} array - The array to select the random entry from. + * @param {integer} [startIndex=0] - An optional start index. + * @param {integer} [length=array.length] - An optional length, the total number of elements (from the startIndex) to choose from. + * + * @return {*} A random element from the array, or `null` if no element could be found in the range given. + */ +var GetRandom = function (array, startIndex, length) +{ + if (startIndex === undefined) { startIndex = 0; } + if (length === undefined) { length = array.length; } + + var randomIndex = startIndex + Math.floor(Math.random() * length); + + return (array[randomIndex] === undefined) ? null : array[randomIndex]; +}; + +export default GetRandom; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/array/Remove.js b/ui/src/phaser3-rex-plugins/plugins/utils/array/Remove.js new file mode 100644 index 000000000..6b3969c6f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/array/Remove.js @@ -0,0 +1,85 @@ +/** + * @author Richard Davey + * @copyright 2019 Photon Storm Ltd. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +import SpliceOne from './SpliceOne.js'; + +/** + * Removes the given item, or array of items, from the array. + * + * The array is modified in-place. + * + * You can optionally specify a callback to be invoked for each item successfully removed from the array. + * + * @function Phaser.Utils.Array.Remove + * @since 3.4.0 + * + * @param {array} array - The array to be modified. + * @param {*|Array.<*>} item - The item, or array of items, to be removed from the array. + * @param {function} [callback] - A callback to be invoked for each item successfully removed from the array. + * @param {object} [context] - The context in which the callback is invoked. + * + * @return {*|Array.<*>} The item, or array of items, that were successfully removed from the array. + */ +var Remove = function (array, item, callback, context) +{ + if (context === undefined) { context = array; } + + var index; + + // Fast path to avoid array mutation and iteration + if (!Array.isArray(item)) + { + index = array.indexOf(item); + + if (index !== -1) + { + SpliceOne(array, index); + + if (callback) + { + callback.call(context, item); + } + + return item; + } + else + { + return null; + } + } + + // If we got this far, we have an array of items to remove + + var itemLength = item.length - 1; + + while (itemLength >= 0) + { + var entry = item[itemLength]; + + index = array.indexOf(entry); + + if (index !== -1) + { + SpliceOne(array, index); + + if (callback) + { + callback.call(context, entry); + } + } + else + { + // Item wasn't found in the array, so remove it from our return results + item.pop(); + } + + itemLength--; + } + + return item; +}; + +export default Remove; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/array/Shuffle.js b/ui/src/phaser3-rex-plugins/plugins/utils/array/Shuffle.js new file mode 100644 index 000000000..0dfe9cf20 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/array/Shuffle.js @@ -0,0 +1,30 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Shuffles the contents of the given array using the Fisher-Yates implementation. + * + * The original array is modified directly and returned. + * + * @function Phaser.Utils.Array.Shuffle + * @since 3.0.0 + * + * @param {array} array - The array to shuffle. This array is modified in place. + * + * @return {array} The shuffled array. + */ +var Shuffle = function (array) { + for (var i = array.length - 1; i > 0; i--) { + var j = Math.floor(Math.random() * (i + 1)); + var temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + + return array; +}; + +export default Shuffle; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/array/SpliceOne.js b/ui/src/phaser3-rex-plugins/plugins/utils/array/SpliceOne.js new file mode 100644 index 000000000..78c496315 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/array/SpliceOne.js @@ -0,0 +1,40 @@ +/** + * @author Richard Davey + * @copyright 2018 Photon Storm Ltd. + * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} + */ + +/** + * Removes a single item from an array and returns it without creating gc, like the native splice does. + * Based on code by Mike Reinstein. + * + * @function Phaser.Utils.Array.SpliceOne + * @since 3.0.0 + * + * @param {array} array - The array to splice from. + * @param {integer} index - The index of the item which should be spliced. + * + * @return {*} The item which was spliced (removed). + */ +var SpliceOne = function (array, index) +{ + if (index >= array.length) + { + return; + } + + var len = array.length - 1; + + var item = array[index]; + + for (var i = index; i < len; i++) + { + array[i] = array[i + 1]; + } + + array.length = len; + + return item; +}; + +export default SpliceOne; diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/BooleanBuffer.js b/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/BooleanBuffer.js new file mode 100644 index 000000000..904bb1499 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/BooleanBuffer.js @@ -0,0 +1,54 @@ +const COLS = 8; +const SHIFT = 3; + +class BooleanBuffer { + constructor(size) { + this.resize(size); + } + + destroy() { + this._rows = undefined; + this._buf = undefined; + this._bin = undefined; + } + + get(offset) { + var row = offset >> SHIFT; + var col = offset % COLS; + var bit = 1 << col; + return (this._bin[row] & bit) > 0; + } + + set(offset, value) { + var row = offset >> SHIFT; + var col = offset % COLS; + var bit = 1 << col; + if (value) { + this._bin[row] |= bit; + } else { + bit = 255 ^ bit; + this._bin[row] &= bit; + } + return this; + } + + fill(value) { + value = (value) ? 255 : 0; + for (var i = 0, cnt = this._rows; i < cnt; i++) { + this._bin[i] = value; + } + return this; + } + + resize(size) { + var rows = (size >> SHIFT) + 1; + if (rows !== this._rows) { + this._rows = rows; + this._buf = new ArrayBuffer(this._rows); + this._bin = new Uint8Array(this._buf); + } + return this; + } +} + +export default BooleanBuffer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/ByteBuffer.js b/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/ByteBuffer.js new file mode 100644 index 000000000..440dc8566 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/ByteBuffer.js @@ -0,0 +1,38 @@ +class ByteBuffer { + constructor(size) { + this.resize(size); + } + + destroy() { + this._rows = undefined; + this._buf = undefined; + this._bytes = undefined; + } + + get(offset) { + return this._bytes[offset]; + } + + set(offset, value) { + this._bytes[offset] = value; + return this; + } + + fill(value) { + for (var i = 0, cnt = this._rows; i < cnt; i++) { + this._bytes[i] = value; + } + return this; + } + + resize(size) { + if (size !== this._rows) { + this._rows = size; + this._buf = new ArrayBuffer(this._rows); + this._bytes = new Uint8Array(this._buf); + } + return this; + } +} + +export default ByteBuffer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/FourBytesBuffer.js b/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/FourBytesBuffer.js new file mode 100644 index 000000000..f14b0a516 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/FourBytesBuffer.js @@ -0,0 +1,38 @@ +class FourBytesBuffer { + constructor(size) { + this.resize(size); + } + + destroy() { + this._rows = undefined; + this._buf = undefined; + this._colors = undefined; + } + + get(offset) { + return this._colors[offset]; + } + + set(offset, value) { + this._colors[offset] = value; + return this; + } + + fill(value) { + for (var i = 0, cnt = this._rows; i < cnt; i++) { + this._colors[i] = value; + } + return this; + } + + resize(size) { + if (size !== this._rows) { + this._rows = size; + this._buf = new ArrayBuffer(this._rows * 4); + this._colors = new Uint32Array(this._buf); + } + return this; + } +} + +export default FourBytesBuffer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/Uint8ArrayReader.js b/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/Uint8ArrayReader.js new file mode 100644 index 000000000..f42c3a878 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/Uint8ArrayReader.js @@ -0,0 +1,66 @@ +import ByteArrayToUint32 from '../math/ByteArrayToUint32.js'; + +class Uint8ArrayReader { + constructor(buf) { + this.buf = buf; + this.lastPointer = this.buf.length; + this.pointer = 0; + } + + seek(value) { + this.pointer = value; + return this; + } + + seekBack(value) { + this.pointer -= value; + return this; + } + + seekForward(value) { + this.pointer += value; + return this; + } + + readUint8() { + var data = this.buf[this.pointer]; + this.pointer++; + return data; + } + + readUint32(bigEndian) { + if (bigEndian === undefined) { + bigEndian = false; + } + + return ByteArrayToUint32( + this.readUint8(), this.readUint8(), this.readUint8(), this.readUint8(), + bigEndian + ); + } + + readString(size) { + var s = ''; + for (var i = 0; i < size; i++) { + s += String.fromCharCode(this.readUint8()); + } + return s; + } + + readUint8Array(size) { + var data; + if (size !== undefined) { + data = this.buf.slice(this.pointer, this.pointer + size); + } else { + data = this.buf.slice(this.pointer); + } + this.pointer += data.length; + return data; + } + + get outOfArray() { + return this.pointer >= this.lastPointer; + } +} + +export default Uint8ArrayReader; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/Uint8ArrayWriter.js b/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/Uint8ArrayWriter.js new file mode 100644 index 000000000..c3f203bba --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/arraybuffers/Uint8ArrayWriter.js @@ -0,0 +1,43 @@ +import Uint32ToByteArray from '../math/Uint32ToByteArray.js'; + +class Uint8ArrayWriter { + constructor(size) { + this.buf = new Uint8Array(size); + this.pointer = 0; + } + + seek(pointer) { + this.pointer = pointer; + return this; + } + + writeUint8(value) { + this.buf[this.pointer] = value; + this.pointer++; + return this; + } + + writeUint8Array(buf) { + this.buf.set(buf, this.pointer); + this.pointer += buf.length; + return this; + } + + writeUint32(value, bigEndian) { + var buf = Uint32ToByteArray(value, bigEndian) + this.writeUint8Array(buf); + return this; + } + + writeString(s) { + var buf = (new TextEncoder()).encode(s); + this.writeUint8Array(buf); + return this; + } + + get outOfArray() { + return this.pointer === this.buf.length; + } +} + +export default Uint8ArrayWriter; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/audio/howlersoundmanager/HowlerSound.js b/ui/src/phaser3-rex-plugins/plugins/utils/audio/howlersoundmanager/HowlerSound.js new file mode 100644 index 000000000..9e9abbdee --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/audio/howlersoundmanager/HowlerSound.js @@ -0,0 +1,74 @@ +class HowlerSound { + constructor(manager, key) { + this.manager = manager; + this.key = key; + this.soundBuffer = manager.get(key); + + // Store id number instead of sound instance object + this.id = this.soundBuffer.play(); + } + + play() { + this.soundBuffer.play(this.id); + return this; + } + + pause() { + this.soundBuffer.pause(this.id); + return this; + } + + stop() { + this.soundBuffer.stop(this.id); + return this; + } + + mute() { + this.soundBuffer.mute(true, this.id); + return this; + } + + unMute() { + this.soundBuffer.mute(false, this.id); + return this; + } + + setVolume(volume) { + this.soundBuffer.volume(volume, this.id); + return this; + } + + fade(from, to, duration) { + this.soundBuffer.fade(from, to, duration, this.id); + return this; + } + + setRate(rate) { + this.soundBuffer.rate(rate, this.id); + return this; + } + + seek(time) { + this.soundBuffer.seek(time, this.id); + return this; + } + + setLoop(looping) { + if (looping === undefined) { + looping = true; + } + + this.soundBuffer.loop(looping, this.id); + return this; + } + + get playing() { + return this.soundBuffer.playing(this.id); + } + + get duration() { + return this.soundBuffer.duration(this.id); + } +} + +export default HowlerSound; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/audio/howlersoundmanager/HowlerSoundManager.js b/ui/src/phaser3-rex-plugins/plugins/utils/audio/howlersoundmanager/HowlerSoundManager.js new file mode 100644 index 000000000..c501d84a0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/audio/howlersoundmanager/HowlerSoundManager.js @@ -0,0 +1,108 @@ +import { Howl, Howler } from 'howler'; +import HowlerSound from './HowlerSound.js'; +import IsFunction from '../../object/IsFunction.js'; +import GetValue from '../../object/GetValue.js'; + +class HowlerSoundManager { + constructor(config) { + this.defaultConfig = { + html5: GetValue(config, 'html5', false), + xhr: GetValue(config, 'xhr', null), + } + + this.soundBuffers = {}; + } + + load(key, src, config, onLoaded, onLoadError) { + if (IsFunction(config)) { + onLoadError = onLoaded; + onLoaded = config; + config = undefined; + } + + + if (this.exists(key)) { + this.unload(key); + } + + if (config === undefined) { + config = Object.assign({}, this.defaultConfig) + } else { + config = Object.assign({}, this.defaultConfig, config) + } + + config.src = src; + + var sound = new Howl(config); + this.soundBuffers[key] = sound; + + if (onLoaded) { + sound.once('load', onLoaded); + } + if (onLoadError) { + sound.once('loaderror', onLoadError); + } + + return this; + } + + unload(key) { + if (!this.exists(key)) { + return this; + } + + this.soundBuffers[key].unload(); + delete this.soundBuffers; + + return this; + } + + exists(key) { + return this.soundBuffers.hasOwnProperty(key); + } + + has(key) { + return this.soundBuffers.hasOwnProperty(key); + } + + get(key) { + return this.soundBuffers[key]; + } + + play(key, oneShot) { + if (oneShot === undefined) { + oneShot = false; + } + if (!this.exists(key)) { + return null; + } + + if (oneShot) { + return this.get(key).play(); + } else { + return new HowlerSound(this, key) + } + } + + setVolume(volume) { + Howler.volume(volume); + return this; + } + + mute() { + Howler.mute(true); + return this; + } + + unMute() { + Howler.mute(false); + return this; + } + + stop() { + Howler.stop(); + return this; + } +} + +export default HowlerSoundManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/audio/ogg-opus-decoder/ogg-opus-decoder.js b/ui/src/phaser3-rex-plugins/plugins/utils/audio/ogg-opus-decoder/ogg-opus-decoder.js new file mode 100644 index 000000000..b17029e78 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/audio/ogg-opus-decoder/ogg-opus-decoder.js @@ -0,0 +1,4171 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@eshaz/web-worker')) : + typeof define === 'function' && define.amd ? define(['exports', '@eshaz/web-worker'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["ogg-opus-decoder"] = {}, global.Worker)); +})(this, (function (exports, NodeWorker) { 'use strict'; + + function WASMAudioDecoderCommon(decoderInstance) { + // setup static methods + const uint8Array = Uint8Array; + const float32Array = Float32Array; + + if (!WASMAudioDecoderCommon.modules) { + Object.defineProperties(WASMAudioDecoderCommon, { + modules: { + value: new WeakMap(), + }, + + setModule: { + value(Ref, module) { + WASMAudioDecoderCommon.modules.set(Ref, Promise.resolve(module)); + }, + }, + + getModule: { + value(Ref, wasmString) { + let module = WASMAudioDecoderCommon.modules.get(Ref); + + if (!module) { + if (!wasmString) { + wasmString = Ref.wasm; + module = WASMAudioDecoderCommon.inflateDynEncodeString( + wasmString + ).then((data) => WebAssembly.compile(data)); + } else { + module = WebAssembly.compile( + WASMAudioDecoderCommon.decodeDynString(wasmString) + ); + } + + WASMAudioDecoderCommon.modules.set(Ref, module); + } + + return module; + }, + }, + + concatFloat32: { + value(buffers, length) { + let ret = new float32Array(length), + i = 0, + offset = 0; + + while (i < buffers.length) { + ret.set(buffers[i], offset); + offset += buffers[i++].length; + } + + return ret; + }, + }, + + getDecodedAudio: { + value: (errors, channelData, samplesDecoded, sampleRate, bitDepth) => ({ + errors, + channelData, + samplesDecoded, + sampleRate, + bitDepth, + }), + }, + + getDecodedAudioMultiChannel: { + value( + errors, + input, + channelsDecoded, + samplesDecoded, + sampleRate, + bitDepth + ) { + let channelData = [], + i, + j; + + for (i = 0; i < channelsDecoded; i++) { + const channel = []; + for (j = 0; j < input.length; ) channel.push(input[j++][i] || []); + channelData.push( + WASMAudioDecoderCommon.concatFloat32(channel, samplesDecoded) + ); + } + + return WASMAudioDecoderCommon.getDecodedAudio( + errors, + channelData, + samplesDecoded, + sampleRate, + bitDepth + ); + }, + }, + + /* + ****************** + * Compression Code + ****************** + */ + + decodeDynString: { + value(source) { + const output = new uint8Array(source.length); + const offset = parseInt(source.substring(11, 13), 16); + const offsetReverse = 256 - offset; + + let escaped = false, + byteIndex = 0, + byte, + i = 13; + + while (i < source.length) { + byte = source.charCodeAt(i++); + + if (byte === 61 && !escaped) { + escaped = true; + continue; + } + + if (escaped) { + escaped = false; + byte -= 64; + } + + output[byteIndex++] = + byte < offset && byte > 0 ? byte + offsetReverse : byte - offset; + } + + return output.subarray(0, byteIndex); + }, + }, + + inflateDynEncodeString: { + value(source) { + source = WASMAudioDecoderCommon.decodeDynString(source); + + return new Promise((resolve) => { + // prettier-ignore + const puffString = String.raw`dynEncode0014u‡*t“““t“““““t““““$#“U¤¤“U¤¤3yƒ†„‰zzss|yu„svu‡yÚ&ˆ“4<054<,5T44^T44<(6U~J(44< ~A544U~6J0444ˆ†545 444J0444‰J,4U“4ˆU“‡…Ò“7U45“4U4Z“4U4U^/6545T4T44BUˆ~64CU~O4U54U~5 U5T4B4Z!4U~5U5U5T4U~6U4ZTU5U5T44~4O4U2ZTU5T44Z!4B6T44Uˆ~64B6U~O44Uˆ~4O4U~54U~5 44~C4~54U~5 44~5454Uˆ4B6Ub!444~UO4U~5 “U5“4U4ZTUŠ#44U$4†64<4~B6^“4<444~Uˆ~B4U~54Uˆ544~544~Uˆ5 µ“Uä#UJUè#5TT4U0ZTTUX5U5T4T4Uà#~4OU4U $~Cˆ4~54U~5 T44$6U\!TTT4UaT4<6T4<64<Z!44~4N4<U~5 4U”Z!4U±_TUŠ#44U•Uˆ6UÔ~B$544$6U\!4Uˆ6U¤#~B44Uä#~B$~64<6_TU‰#444U”~B~6~54<Y!44<_!T4Y!4<64~444~AN44<U~6J4U5 44J4U”[!U#44UŠO4U~54U~5 U54 “7U6844J44J 4UJ4UJ04VK(44<J44<J$4U´~54U~5 4U¤~5!TTT4U$5"U“5TTTTTTT4U$"4VK,U54<(6U~64<$6_!4< 64~6A54A544U~6#J(U’54A4U‡[!44J(44#~A4Uˆ6U“‡UŠU…[!44†64~64_!4<64~54<6T4<4]TU5 T4Y!44~44~AN4U~54U~54U5 44J(44J UÄA!U5U”#UôJU"UÔJUœ#UÔ"JU˜#U´"JT4U´ZTU5T4UôZTU5T4UDZTU5T4U$[T44~UO4U~5 UÔUô4U~U´$.U5T4UP[T4U~4~UO4U~5 U˜#<Uœ#<4U~U2$.UÄUN 44 ~UO4U~5 44!~UO4U~5 4U~4~UO4U~5 44J44J(U5 44U¤~J@44Uä~J<44UD~J844U~J44U$54U$5U‘54U$54U1^4U1^†!4U•~54U~5U”54U~6U4U^/65T4T4U$54U~4BUˆ~4O4U54U~5 UU'464U'_/54UˆU”~5T4T4U~4BUˆ~UO4U54U~5 U54Uä~4U¤~4U~U'$!44~5U5T44\T44U<~$6U\!4U#aT4U~4Uˆ~4O4U~5 U5U5U5TTT4U$"4YTU5 4Uˆ4~C5U5 U5U5444$4~64~\TUŽ5 4U~4Uˆ~5T4Y!44O4U~54U~54U5 4CYTU‹5 4Uä~4U¤~4U~4$6TU54U\!44Bæ4Bä~[!4U~4UD~4U~4Uˆ~4$6TUŒ54U\!44B†4B„~[!44U<~4U4~$5 4U"U˜#$544"†Y!454U^!44<J44<(J454U~84­U”N!#%'+/37?GOWgw‡—·×÷Uä;U”9$%& !"#`; + + WASMAudioDecoderCommon.getModule(WASMAudioDecoderCommon, puffString) + .then((wasm) => WebAssembly.instantiate(wasm, {})) + .then(({ exports }) => { + // required for minifiers that mangle the __heap_base property + const instanceExports = new Map(Object.entries(exports)); + + const puff = instanceExports.get("puff"); + const memory = instanceExports.get("memory")["buffer"]; + const dataArray = new uint8Array(memory); + const heapView = new DataView(memory); + + let heapPos = instanceExports.get("__heap_base"); + + // source length + const sourceLength = source.length; + const sourceLengthPtr = heapPos; + heapPos += 4; + heapView.setInt32(sourceLengthPtr, sourceLength, true); + + // source data + const sourcePtr = heapPos; + heapPos += sourceLength; + dataArray.set(source, sourcePtr); + + // destination length + const destLengthPtr = heapPos; + heapPos += 4; + heapView.setInt32( + destLengthPtr, + dataArray.byteLength - heapPos, + true + ); + + // destination data fills in the rest of the heap + puff(heapPos, destLengthPtr, sourcePtr, sourceLengthPtr); + + resolve( + dataArray.slice( + heapPos, + heapPos + heapView.getInt32(destLengthPtr, true) + ) + ); + }); + }); + }, + }, + }); + } + + Object.defineProperty(this, "wasm", { + enumerable: true, + get: () => this._wasm, + }); + + this.getOutputChannels = (outputData, channelsDecoded, samplesDecoded) => { + let output = [], + i = 0; + + while (i < channelsDecoded) + output.push( + outputData.slice( + i * samplesDecoded, + i++ * samplesDecoded + samplesDecoded + ) + ); + + return output; + }; + + this.allocateTypedArray = (len, TypedArray, setPointer = true) => { + const ptr = this._wasm._malloc(TypedArray.BYTES_PER_ELEMENT * len); + if (setPointer) this._pointers.add(ptr); + + return { + ptr: ptr, + len: len, + buf: new TypedArray(this._wasm.HEAP, ptr, len), + }; + }; + + this.free = () => { + this._pointers.forEach((ptr) => { + this._wasm._free(ptr); + }); + this._pointers.clear(); + }; + + this.codeToString = (ptr) => { + const characters = [], + heap = new Uint8Array(this._wasm.HEAP); + for (let character = heap[ptr]; character !== 0; character = heap[++ptr]) + characters.push(character); + + return String.fromCharCode.apply(null, characters); + }; + + this.addError = (errors, message, frameLength) => { + errors.push({ + message: message, + frameLength: frameLength, + frameNumber: decoderInstance._frameNumber, + inputBytes: decoderInstance._inputBytes, + outputSamples: decoderInstance._outputSamples, + }); + }; + + this.instantiate = () => { + const _module = decoderInstance._module; + const _EmscriptenWASM = decoderInstance._EmscriptenWASM; + const _inputSize = decoderInstance._inputSize; + const _outputChannels = decoderInstance._outputChannels; + const _outputChannelSize = decoderInstance._outputChannelSize; + + if (_module) WASMAudioDecoderCommon.setModule(_EmscriptenWASM, _module); + + this._wasm = new _EmscriptenWASM(WASMAudioDecoderCommon).instantiate(); + this._pointers = new Set(); + + return this._wasm.ready.then(() => { + if (_inputSize) + decoderInstance._input = this.allocateTypedArray( + _inputSize, + uint8Array + ); + + // output buffer + if (_outputChannelSize) + decoderInstance._output = this.allocateTypedArray( + _outputChannels * _outputChannelSize, + float32Array + ); + + decoderInstance._inputBytes = 0; + decoderInstance._outputSamples = 0; + decoderInstance._frameNumber = 0; + + return this; + }); + }; + } + + const getWorker = () => globalThis.Worker || NodeWorker; + + class WASMAudioDecoderWorker extends getWorker() { + constructor(options, name, Decoder, EmscriptenWASM) { + if (!WASMAudioDecoderCommon.modules) new WASMAudioDecoderCommon(); + + let source = WASMAudioDecoderCommon.modules.get(Decoder); + + if (!source) { + const webworkerSourceCode = + "'use strict';" + + // dependencies need to be manually resolved when stringifying this function + `(${((_Decoder, _WASMAudioDecoderCommon, _EmscriptenWASM) => { + // We're in a Web Worker + + // setup Promise that will be resolved once the WebAssembly Module is received + let decoder, + moduleResolve, + modulePromise = new Promise((resolve) => { + moduleResolve = resolve; + }); + + self.onmessage = ({ data: { id, command, data } }) => { + let messagePromise = modulePromise, + messagePayload = { id }, + transferList; + + if (command === "init") { + Object.defineProperties(_Decoder, { + WASMAudioDecoderCommon: { value: _WASMAudioDecoderCommon }, + EmscriptenWASM: { value: _EmscriptenWASM }, + module: { value: data.module }, + isWebWorker: { value: true }, + }); + + decoder = new _Decoder(data.options); + moduleResolve(); + } else if (command === "free") { + decoder.free(); + } else if (command === "ready") { + messagePromise = messagePromise.then(() => decoder.ready); + } else if (command === "reset") { + messagePromise = messagePromise.then(() => decoder.reset()); + } else { + // "decode": + // "decodeFrame": + // "decodeFrames": + Object.assign( + messagePayload, + decoder[command]( + // detach buffers + Array.isArray(data) + ? data.map((data) => new Uint8Array(data)) + : new Uint8Array(data) + ) + ); + // The "transferList" parameter transfers ownership of channel data to main thread, + // which avoids copying memory. + transferList = messagePayload.channelData.map( + (channel) => channel.buffer + ); + } + + messagePromise.then(() => + self.postMessage(messagePayload, transferList) + ); + }; + }).toString()})(${Decoder}, ${WASMAudioDecoderCommon}, ${EmscriptenWASM})`; + + const type = "text/javascript"; + + try { + // browser + source = URL.createObjectURL(new Blob([webworkerSourceCode], { type })); + } catch { + // nodejs + source = `data:${type};base64,${Buffer.from( + webworkerSourceCode + ).toString("base64")}`; + } + + WASMAudioDecoderCommon.modules.set(Decoder, source); + } + + super(source, { name }); + + this._id = Number.MIN_SAFE_INTEGER; + this._enqueuedOperations = new Map(); + + this.onmessage = ({ data }) => { + const { id, ...rest } = data; + this._enqueuedOperations.get(id)(rest); + this._enqueuedOperations.delete(id); + }; + + new EmscriptenWASM(WASMAudioDecoderCommon).getModule().then((module) => { + this._postToDecoder("init", { module, options }); + }); + } + + async _postToDecoder(command, data) { + return new Promise((resolve) => { + this.postMessage({ + command, + id: this._id, + data, + }); + + this._enqueuedOperations.set(this._id++, resolve); + }); + } + + get ready() { + return this._postToDecoder("ready"); + } + + async free() { + await this._postToDecoder("free").finally(() => { + this.terminate(); + }); + } + + async reset() { + await this._postToDecoder("reset"); + } + } + + /* ************************************************** + * This file is auto-generated during the build process. + * Any edits to this file will be overwritten. + ****************************************************/ + + function EmscriptenWASM(WASMAudioDecoderCommon) { + + function ready() {} + + function abort(what) { + throw what; + } + + for (var base64ReverseLookup = new Uint8Array(123), i = 25; i >= 0; --i) { + base64ReverseLookup[48 + i] = 52 + i; + base64ReverseLookup[65 + i] = i; + base64ReverseLookup[97 + i] = 26 + i; + } + + base64ReverseLookup[43] = 62; + + base64ReverseLookup[47] = 63; + + if (!EmscriptenWASM.wasm) Object.defineProperty(EmscriptenWASM, "wasm", {get: () => String.raw`dynEncode0095 %j©Ö¥Õ= [ücò¬ qrØÍ TÑ®]µ+¡]™ÚÿKM&ƒìÃÅFJ•´õ1_§= ¾ŠA-T¸½Ê"ÖþkÝä8…Äå¼?ÙqV2;ƒCÚxC:ëañjk©(4'êÅCB%‚²œ2 CÚ£Ñû{lȬyÝï#‘=M®QaQa!“ø....®CŒiI\£’±âêì2Ïé?Oˆ;~À£Ã½“­´Ý•ân3ِ»öôáq±Í»»*Ù{@ï5/–Ã[Ó&ˆéè½­RmŠÙ„$W}•±ó?—áϘ<"ø¸ýÑÌI™u"«\Þ=}LÍ¥–ÅèU=M1÷ŒÆ#¼‘âöóKÜ¿@–ÐEÄ#í=}XÒòv÷5^ +¯RG˜Ño–³fûAâBâžuOð˃ÜOso±ª®tþ#ñHR¾í¦Ê‹ó@ÐÆ=MQ Ù0ï0ñ°§¬•ž+rêTðdeôNØK<0›ž‹êä™R%åÍô#†µfÐ9‡©Û8˜]¬¨/Ë ´1oË2mu>%Ý>HX›$¾…𩼔 aŠtè¡eËR«/ÃS̞(˜=}Z‹Ë1qåèˆ6»3@¥…tŲ£wsűÛXö+œ°“‡Š~ÅâX“Å¢ŸÑÀ38“5¿P•õþŒ9õixÅ¥êm¯¿›”zÄx–÷ˆ_VðC¦ÝPêp=M@#)’LDë˜Â= ‘óíœy°¦›?Y‘·m•ÎÜø(}µø%̕ËðÖÔkN"€èb{Iã$ÞÍtN9Nó˜ýkÙPý/´¤ûÂòÿl»ˆXÖ÷g/óTànn3-©i­9×ÁI.•Š€´”öu¢ G¼X ¹Šö7ž¦—t×]é¯à= yâ6§Dû×ZD—æÌLë‹s©ØäƧ؉+pªE9¡t©ò=}}™}Û8Ä&ÅÛlÂaÍ¿SÈ cx#-º\P>~ˊU¡®©ÄoÙ¥_\0¥žðùßüpT™Ô}­äX»qNYÀÐ=M0ý@— Þ­ž €­.âátv&÷¿Lg‡¯I-—ÒüFðÄø¶ ¤¢/:2{Hdh:f$Œ´mD.©qk׿rkí?Ò(Íóç‚oGžV«¾Sa[±=}+' ‰{¤¸»)&õf³)&Ue¡]àGÀ˜%W=MIèÚ=}ÎmÇ= ý•ݟ-ye)Gö^*;½]˜- ó›­ÿš§³Ú¾U1)ŠŽ—V@Dcj§íV¹)= 5ÓÙ’PVÐÅc=ME—1f„Ãêf’¢á´Õñù¨La[ȝOÿ+o{Ðè0ˆßeX¥Á×Þe-G<¨‰[.>ÆtXç·qçkûä&¿§ªò€éµ˜%$¦DÕÿP·À̝9»—ìkþ—€¹É= '†Wå"ãS°‹-XÚõýqV¦E×UzXMƒx­ âÝí£tI­k_m¢×‘Tš‹£L8q¨šTÆ= )ÿ·'Eê–×½X{sÁi»3P6Ku«Fù[œ·À_r?ß*8ó·囨õY^Ùø¨^fÅeáô„ÊÂ>ÌÕÞD¼õ‘íÁ6C×åÈÕW&T³›‹bUr™âu.¡!gTÏ‚ÞC¨Ô‹—\͕~a•×»“n«ÙñßÖ¤½ôšu›qgñ ù7}r"Iut‹òÝ ‡F"A-­Õå+º×9ʬjÜøƒõW"ÎQ5\Tš×ríÜ=}B4+_jX>Ù¡ŠÊœˆ}Uù[øÞ˜Ó£…ÍæàIå_{!¢qÚõ@ü2®>(Úg{ôÜ/^Tš5TXµ10– ¢ù³ˆõç—RÝSâg±ÉéõßPí‡=M¶"°A'?ì»;!‰=MærŸ!³·%søšsÃÏ«+±SéìÀ°©•ß•v±Œ%¥Ie¿å׃ +ÅZ ̊è„þ©=}Hg;þ*‰’6A+s'0D ùŽü—\ûwÎ.0LP"'“àý›¯Ð–ØÙý˜ÝDH¨pt./Ÿ¯öO>™ „*á†Â–4Çrœ0º’Lxþï-SëÍù· +w>LâŽR“´el‡â¼7ââ^7¹ew'ꦛÎÒ¼¢åÆ–XÐ5ÕRQÅփ.OUïa!‚aɅ¡ 1ÏX«¢ì‡cÒÅÂ@„Á×HOJ†#‚Hc¯R= Íˀ¦ÇFN×ÙÑ0›oš ٛq[Mj•AoÿQÔô»R²Ky¥‚áñeNRJŽ^!ÍɚäæÌe6Ò†s|sP3@Eú‰;þë4õ"…éfÄóà~À,ÀŠA=}JƊ¯öL;Ä +숆çÆT#|jQCvò$µ;çûvSÜëfÅëó?=}„lôšä"¤éÒÁèî¸5x¶©˜‰ÑCúÙµE"•Ýãï$]D7ÅըŻ5˜hP³c?¤—ì9Õ¦ìهf—?sR¬~ÒËÒºEg˜ Ñù¹¶”X½ÎÓ±Ñςªn³yœ“ƒ!= 8Â(Šñˆ<[µ{$.‰q^o[Å:|À|0»¬oÅ Ãs•]‹þí£E”ށrXËt›Œé) ¶K9-ñT[rCÕ}vŠN{€Ð5¬m)&ëÄö0±R‘™fy uAÛ ãR= Rä¡rwìòá0ËT¯I—e +=}­Ï\H¦üØaòUÝօŒW9Efüb“!¿·(Ø¦» óx®u”L«G ²’Äk;Ã6¨1<½Ý[[ê[¢"qR_öÀRØZ€S/€|2bg•M#ҙ ÀÑ}÷¦¥ ߅MëÓx¸”šƒcΎDŸ0ò¡“±)~<â㋶æV”4&&ø¨×R~Úë†ô=}{<—¥~9}VÄj¨Wã- ƒÓ©—Ð섮<€È/UmE,?žÈP†3#­Ä‰²Þ½ùÜÚ#íÜ<¸¥&ØçˆAŒg–s"‡Q »#9Ä- ӗ†n!=M! \ FŽ pÈâdÓIZ¡ÚöD}n¸,ÈâIü³ÕÀQ™ÀÃËÜtr‰û “×ѝ✴ÊÃV$†ÎàŠ!KF#»¬ë¯b4KŒNYb³bU…ñ *56PÃE‘aËF-m:‘XÁüLÊ>ìrrÓž-ÜeG· rŠL¶¹É¥P°kW8Nª0 Ý·¸ÃáavÇÀyS{|H= Ödq¼¸!ø¨³U'18²^˜p¨¡<#ŽH[³E)e±+Ô¶ò͓= 5ëº!¾éAϖ*ôaEî§8RAϬAyΕ²Õïs®¿é7œû{²Õ–µ#ȉ=}·¥B<‹öˆMÑQ¦åÃǃ9= –zÒQzón"¹CŸcˆ§“e±|צ-VáéT®[NWk@\ƒÛÆ%&³­‡Ína†»2b/EX„,an8±ñ®«Of Ãò,=M¿Îud×Ía +y£ˆ„¢^ªƒUÿÿL¬é½ QC”—¤ø4üc¨s"⯝“K¼ÑW4:Þ÷Ä\bŠÔô€•cBCŽ+1Æ·O9Ž´€H{kHÂòÄõUïØpiBΗ‘]¶ð…÷³Á$yö/EoNô÷ˆã랍u<4%á‚åc‹­ÒŠø§Î[,l¨Î× áÖ™~‘Ç7+ֈ^ûk§PÜ'Ãi©QžDÌ8´T½¾bP›ñðÔ†©¢&˜²š·ÓSyU\!=}+= ›= &„›úGoæ\S:b¬Qû-\G¯_í<Ì&^ã¦®ÆÁÃe¯ö³b;ËxÀªW.^EE™;Cr³M€yGù|70É8‰ñ‰’8{ ²ÎºíËÒ0ÊZ -±myAVs‰Ÿc{ƒŸM)™6¿8ë+’îÊ^ñ‰ôÛÇѪ—/ÑXYa±ŠsNÁØ¿Ëc¼!¹­Á\Cx_‘h8Ïqœ“¢ãÚÊn™äÔãý»oކ´žCõªÕ‡{‹´PoÅ å¶ïá7Øpb’Ÿ ÄgcBÄ NéçÃRrp–Œ1 “Ü9°E®uåk¡ûŪ2Œ¶2yýªˆj>wPOtË̛‚X–¡z”ÙÁº¶{«+'PÌÈ)«»‰¥æ¦¯†¦Ð؅@£5\¦^’= Öù—òŒnf¼Ü]$Ì%´dѳr%ya«Z—ôô™*Úå¥~5“Üv6“˜5P6ŸšNʜŽÔDœ^ÑB̚^0 »ž× þ*ª«Åd1~E+PûÁNo&¶#: ˆP+Íp¡D‚ß“}[¹ƒAÞPÈõ›uça¹(:î ¼¶3Æo_=MàïE04u]Oˆ¡³Û=M]i¥²˜û×Ñ´z]­‘^Èb´1VÑ9ÄpùgqÏh‘_®Þ»Gqy,¦ZSyßèuA/†¡ÛÉWGäSDނܕ†6=}œFŸõ4SvÖèøºøi‘þ(b¨Í•àMí¥ö©·‰÷?ŽžI9ZFדT£*Šj×䛴Ö÷Ô"^£á€€n“= –ó·|݀ñ ‰±9ÖP¡ [‡ç/kÉÃÝnÅhHþù›Š–øC ·û5ü›Ã›=}´S}Ã|®áÝèÌ?^hŒôi‹pn‡n«Ts0xøÓ½GÖX—™°ÁÜ èšë›²ãg]HBͰ¿Jxö5­€{T1â>(銙ÇèSº ±nzké©«ˆ=  'ÅxÛa¶Xt’AN3IŠô*ŸHuøÅ…l1×±žÝî©EvÆýJ=}”Ï,ՋîömÙ|ð£í”"»*ÚFé¿oeõö$Q&ÛSŸ¬­@j±~$Îàg‰i#ä³jªuÆB®Áj—£Þ’ɲW¾h´‡Ã=}ӆ= ¢ï€üÔ+“~ÅM\é_– –zaˆn=  0±Th¡–“´Û>æÔâi_õF£°/œšD¡ªE58z¡ý´gñeMut‡°JÜC £Q6)e¨=}¡q³^Ÿ +ó›Ycïi¯ÆÇM¹ìDÎ= ƒ?hœLZ5 —݉œ£Êh•‹î{WЙ/=}Å®å3ùâ(XÇDö½,¦îkÈ)GÜø©_€Ltc =MÁKUm\ӆH# ;ù9äèGÄgø@ÆaB2õvÇ [Ÿ•4R—Ր¦¸â¶fúØüd:d\ˆÌzžK»,w¯¾2wœZÒkŒº~…Ó¹ÈËz0xºAêXrZ*µÌ#äö¶ÉÛ$Ë܋Õõ| áív£$Ëz‚ÞzG€é-Ÿ½„06°|&gåüÿrïᱬ“}pV«/L>·¡S”êÑËmÆõ–Rt3mJ±ð¾Ó•^€6ùhîˆ2DÆâÊÎFpù^°bAöŽAý m&óa|ÇkEÈþ9J°é|»ç1À\ðOTÒ-o¨eŒÛ0tþÞ p†Ÿ-3= 'ÇaBÞ¿!â^»GhODWñÁï8§<Øif¡ ‚†{ü>…Œ/…ª—çRèúÖ vÔÁNä[ïÜa/Dš]¦h­þbíãMžz–ÓA¸@;%™ƒçåÃz:éóß¶PÈ'L;õðß= çY8H®t*ÌXò^ùaƒPîá&.»p„QZʾ•?+PÐit¬»ùY6yÊÎm›éìÏ\:÷ŸéñXØIúë¸oþš«GH®äjVò¶@,i#6£HÃÎåùy +HŠ YŠù2/.ob•—÷×m¾+çŒÒ|ÒB±¼ +îØnËr (žôbg྅ù¨Oãzérò§˜¨›NrêÝ@ö™ó˜klùŸÍÑ'¿Ù„÷[u((êƒ4|¼æÅ@°ÅBz«%dÍËâ6‰…Òª´´w9ñ@¾RíUa¾/°üÊFé—)€âÉÊ$_Ïj‰½®EµKØIº5dú(¿fLž c%ËUÔ הd ±Ã†ñ\ é§CáM€}phÕ$×ë½vßxÑ"‘GÐâyqþ^ÒTÅ91®βXørdn—0UÖÓàvb0§/YnÎL}µç×=}€ËÛzá?³ð7›|Ë)pØói-íL9™cv ŠÓõۃ¦@L\ƒ w&søñc7Ï 0k\ª¿>±Ž~ñœëò£©«}Þ{}ÜíÚä‚y[¿Þ±¨âBà™d4Ì¥På&l¤øÕ¤Ø½:Ê\Š0^upqpx“X}mWRbfêË4|[IœåÞ^ænË4ÑP˜ü*ÞÀ* (çá?¾ä»fÓÌ.…é1Äw§.ÝóÔA\Vᾕ¶Ÿýe»ãZx¾€¡4žòžß’cô„j$ Àrè¶= fûÛ6Ýڏ[ª\ô#ƒÌPÑ^¥c(%^ÀêE´d0d΍Xd¼‰0ð‰^=MCÞ¼eõ[˺\ÆY½4öÄEÐàm²eþÛHƒ(F¨Ç;lb¥—ÞPªÄà6Î$¦™~Â};…8øÕ- ÅÃv÷—˜á=M™õïr/Åʎ„bQª>­/但LÐÆî”¤ìÇËŒÍd覐šWÕzL .ðœ¾X¡{ä\ó(=}‡–Ï…Y0„åé=}dS”É +âk,*¾ |„ð”Í-6åé«r|Š=}"î”]™›ŸÐÒÿãeæŠ8E¡ö½XµLžüâ6—qY;±l'\€=}0ï\iϼ“ >pÓÓ[Íø~Lšñw®K7»&6oÂÙ¢rÎÐó Š*@½TrÞpìì<Ã΃±·†ôË®‰í_&´6K€>…Œ/9ÙSʹ»çÖ<-Ö”$k´žûbÄ‚d ¤Örß»×â*¿ÁÈ:¿Y±|®LdÈE>NªiFÆdåÉä׉ükÚ¨.,6ñoU8m/(uTŽÐÐB@ý«dÁ§0  ˆùÔ÷Ã|q´Å¹CƒW Þ×üäó’\‰b~C@܁_P‚¿RVw–5–ëûç}ªÚw?&;/«þ¯“74NbYKWj•õ|Œ„U—š±¡uƒú=}ëQ».otä¹œîÞÝþ áªeÿS@æ:m}½lÞu˜Æ6I䙖NÃñËæîë2ÁÎ wt— +•<•…–-E›h0Êúoc‡~hôP=Mtܑ”{õþN¼À¶¬š)êF1T$n>(GêßVã_ê\‚?(:('I@+CÉüµ=}ê—ûêÝiíôG®ÊãWÔ;AhèG·«^?xܳ€Kڇî= Í8+lisjúÖÖ8V'ûD«Òµÿ +„³ä7cÙHP˜Y$ʓT²”ê$Ѫ“˜ÞN}ô#{ğ‘ρÏCÖÓ |é¬Ríi÷Pã[9çÞR¡gÿO›3vØ3S¿Q •/͝L7åÁ¹- o¾ÞyI!= ê™³£Ke=Mš÷× +yCrËQ³lc=M…?Sò:ËBlûRáþ„~‘ËcSó0Gšsy:›oH3c.òþ<šçTjç:D]Þ­ïpL¶ÕÈ**ºÎ~£#Zž"ôKEšFÿWѾ®Ðnrí|®fOê©¥0+€Œp'afÆÑŒFiØb±íq'ŸjNU$?uˆZ¹r>+ûdq3²ùíðF­‘äI^뿪®ä)­»!:×Õ૖ŒzT¯–Rz‡fnƒëáa.09M|½ >%ìÐ= µs2å?"5>rVŠÔ­ +Çô #îŠ0‹ }q,ü1 +,Š\ˆjª³¡0HkGƾŒ7Žï l’ÈiÒð'f̬ qzͼAéè{Zù ?Lμ0Нcû¹jCkÕt(sü/¹ŸV?š9x¢ íw|»œúÈ&ŸÕ¸Q̪sËs£„QŽqÌÉúŒïù¡“!3ôÜ¥Ýi=MŽOHi¦…Gxā„iIG(D9±ñ'ÓBSPµ>…‘p…<‰õº31E…Käg¨u'‰h/9äý'óh¹R7ޏM»þ8ޏ9»~ +âg0/9´j¹RVû5>Ólé4ÿ?$ÊjÜ/˜9 +tüݑî¦âgW¹òGmùc/1bh;‡??Ɏ]HÉ}Z‰Ø¢U†.7\ˆÉÉSÀ¢z/mîaÕªèI0Ðjb6¼@pÐ.p àËÊ@D }ON爕Ð/—Oº÷rq8r±O ç‰û,wejºòó„{ej©Übþ9|üÉÒìác|Å^ÿÐz1W4މƒuÇrñ>\PÎ +CàdÀr±OÏl:|J£rŽñ5¼OÎ tÁÒ/lɶQü= ì«Ó2ˆ×è͋ÊÔ® ͂–B¢QçqèÈ8Zb¼âÄW†3*¨‚Qñǘ ªu1îÞɞmÄs+w÷®fxÝÂÉÄ.*~¨ŸÔWTC3È·Uä³}ï´?÷¿0¯¼SàOR„ëuôV1 +RÃqÅ«óŽÖ ÔµŽ´€…áÀß.}Mv@uì#]–'Ì.Ççù qÒï5À(œ9LJë„8á#gŠn6Èº¤è!w;1 NłjËù±oP"H÷Ãë БØdEë¿wM¨9µluW.7¦ÿhÓûšXäü5 w=}—>Ü*Qf¾ð9ÛÝ*ös÷ÄÃÃãuÂÄ#4ŽŒr댞â-p%©~÷†•<œEä—AÄÅ)¦ H:CAtÈ]Ò#¡Á1ФM™ÿú{M;‰Äò%®å:x¬ü²à½'å1üg•jéã¡âBŸ>:‰ê¸)1Ø’åÁÆÛ©nì¼)ºÂ‹ÔC)ER!ôý&û*ÀAj,ÃÒFCEƒ*þ¼–EyŸ©ÏqÞÜ=M9åR”fÐ ”ƒ˜”ƒÊ”ˆèŒr…ÐŒ‘Z?èTnçGßyQ’{´CtG2«vy,LcE iíV÷¸?pVDÛPæe‡ãÍÌTcŸ‰Ý~Óµp=MýiŒçÞ+llJS‚<ÇÊP-žÉ\M íÂTÅßùD$·ÑžÛ÷î)þWâàSõoÙä=}‰ݨà˜dgÿh%JFVž6I®N2Eø+åäꆶ–ÓÌà öTÃO©á%u|ÿ)íÑrþïˆÛhϾÈ2ƒùǾø^IÂm*”Â",‡¦—vCRh +´JŸõOS{-è’äOAÁ‹5Àë;¼jî[_5 ™À!B-– Zù U_F£$l²ÏcF sዛMZmþq*Ùà}³ÒÒ< ÇÜy³åÜÉ ¬»ÖP-ã±¥}‡"²Ç‚£o('ö¨~Yiô| u$»q“‰ÜШCd¦…j”Ã/—žÑ‹ž€ý>ȳÛ*4¦ZBjba'?äzå™õÖ¬ÏÜ¿°+ÑQ{¹¥Odl°h‹ P_$PóÈÜiн̪ _ù«S‰nM9—Çošd¹ós»‰<ã±T«¢ Œe[ÇÇú¡ˆø@#H÷ðÛAæ@xZ óãÆN°] +î!‡Ï7‹Ì[æ^§[‰[ƕÊòU]ûïëK秗£ºÎ8Ì#ÄýØ/B%Är€ù…?ÊFÚH5}Dj-{/ìç!æ{îAû*ô$­#è=M†É@[(ŧþã6¨#«M1“N¯m+k2öÜëÜiˆŠ,!&"©x­›À ̨ع!|§kÁB#°Ýd^{;ßfsý½ l+DäG'‹))!Ú±Kố³‘‰³Pþé4ø'bûîl³ßE!ÿóÄx¿Š‘[ƒu&ãí™mG©x?é,BÎ/–§+‚OQ~ç½áèŠøï—mN|/܁¦ò8püjð«¸~gÀ°nŠ‘˜›š†O£ù"X0¢#Í«ò—Êãv×üøË“'ÀæGi;ÐÕs¶k‡¾70ÑAŽ£'øQô ö:À˜ÁȉQ4–?Ä%ñ»K?(«ƒCqSÂ#KlÁW®)">b¡^¸»-î«w5ëK®ƒM¬p·2ƛ«>®Õ–päém«äÝ3G0]DF=MŠ˜»–í´œ9;Dg/xÛúJäƒj•ðx¯Ã ù£ÃÑ~úFZ€£Ö㍮ꬮù‡íbdÂùˆöðž‚ßIí¹ŸuóÇ9Üh#qZ'~Qéf$íÉÀџ í†òF=MÇã@d‹J}´á3öÒãÍ˗FKa=M©B”=}zt”4ïâƒqªÓO@:ZÉgnÙ&Œ|MÖNÜñWU8ײ´N”ˆÔ”ˆÞ£j 3>‡¦ëkÑw»‚­B%øb8ŸÖŸ¶³Bˆ&{¦: l¾EÕu8¶­“pHY2ܞíj„5È=}tÞ<=}tÚ¼BtB“íT\"B&Xû©н‡£pCRh³nJ ±‹lsø| ‚‚,†4e90Òï8È85Šø‹‹ñ%»IìLjé­Õî°¦ƒHË>ÐÁ(»ÜC.˰r5–´R²=M‘akYÆFÓq¹á$¶t$>$ÆR5B¬5œÄš’Pž6ÒìÖ¸eË%^ð7Ó!Ÿ&S®‹8lá?l6 ֏ˆ„òX*ÖòO„Ür,Çð@l±áqA¥¢è[¥j%†dmÇF*ˆ°î®7יÖ|Û -a?œ]ß´Ž›‡Uø\p›•Äu¬ôŸÍ^u¬‡“-xà +îõ‹#{±QÆR¯=M4ˆ‚·o~NNz+ t"¹‹­÷N¥ë#«*JC# ËeÓÿÐ1®ð¢¬X ’ðx|ÈÖȱ>¬õ­w% 7cYºÍ×àn£MU°$ì$8Ž"…ÀÁ20棲¦…@<ŠG§âƂQgóâßÔÉzítwL!c;Š›â=}Ê~Ó©HCjM_5üj£nØ3BIŒÍÛ¨àуOè(Ü­=  ¥‹Þ·ah¦÷yÎ&ÆØ­‚Ñz؈°ZnÆÙƒÎ!Ì ÿl!•S —\bH†Šß9žX£gŽUDjײ +Â…m’”uKĔfò/ÍØ„áí½üàÁE;·¯:™ó0j|ÅaÈϮϧe*í&yÿj²•F4»x¬ŒbKyµ^$£ËÝß zBödC§;W(®d{p%wר]¬K¶ â\ؽÏeZ·øÿز…s$;S ÷X8†ü§$MŠC%ÿÒô=}L3²´Å胮䢯là‚ï\–è*í4{Nå™—*B¤[‚cÂ¥U= ÏLÌǸ”ëò!Â^DJ?Iʰ½nXÔ´0²©ÒÿMٟ5h2ØÖ$oøŸTıb!…\WiStàðT2Œì@jO–Ô²[T¾g}!áà ÷%zP܋}Óm1pB´8çЎÉÑMè•Ò\VÏH‘á¨"¯5ݎ +~Ù¸îó ©·øO²A±ÙSåF› ä¾A¤AÎãK›?M½P3ł÷Ìþ~q~h‰âpkp–üïÌÍ/Ï÷6tC ,ìo_ޅÓÀ·I.¬¶è;6uw¿UÒšÃf=M <¢€yß©~¼Œ–´™=}ÐUæ u’@Èn† ›±üü}c5¥ Í ›¤® +$²AˆK=MýÀU¬O׈Kü.|ËwÕêV]‡ '"ˆñˆ}HÁ–·£ýi¥$—³x²ÅšuNüîáU–¥z3 ß&©<•Õ©çñfl£@DÜs^iVq·»˜Ý = Z ¿›²&mKCqÔë£Þ²O0Hh±Àz Ø6X(@gŒa ò8¢äf¯iìôé¼M7ÄcЧ¼=}PƔ†FV7+X÷Y½p Ls$#Wõݬþy}¯ýÏ{,ԟYyîÔKe–0ÝB÷æ³cgŒ½9£LýÉÔÚ +m{ÄùãnaºÖ8£žCdŽ‹ã«û¾D³=M+’*ü®]Š +àg~îHºø‚¿ÂKЦHÞډ^óÀøÖ‚Îy˜|±›œ¬‹ÏÌRcⅈ"8ì÷ù;x¤”¤«$ŠSlTN­Á«ã Ô“H-9M3´˜LbÐÏ{M´k÷w,ÎQ¤q閪L*ô)cº 1= —[ÞÏä#}‡í$¡ËàT­.í_HíTq¼ðë +o?¸É7ڐô˜ Ór@ã½ÈÝõq}ë•:(µÕœFâô G´³[ë–OC]Cu%2;„~€é‹mºˆy ݌f›ö´\Bí[u6×ß.Ë.ïš)"w6;Ô?¸ìø=Mr‹9é–D[ظ“ë0ÕA²¡%¢a»”×Ùkv‚cŽ>¡,SL#‘f/]çŠÆVè<Ê'³Hу‚F£\„ºŸä‹yÞÊs˜‚˜ôФTVŠSb9f¾t ®’…o朰}Gž¥['€Áî'¥wͧhÊ}yöÎe¡þ¯ûùàÏ +߆LTV€l0ÛXÞZM+­Ý¨\?úÀÅa#ýw߁É!ù.w}Y*©›xü•ÔJÖôúõ’e'QșŒ¯:7nÇyrËÍÿ¼o‹’/Ð[rŸæ¿DCéBìåËìíQ,i)PâþôpÛæDIÌ"Ё˜mëbŒÐ¡ÚL‘L®Óƒ…kRçz„o¨W|Š@ +°Hä"&¹‘ˆVØÑø2†@^€0´ú˜Â„­†Æ@DƒXù‚¸ÐÇBD¨ÐÄ UzŒkZ‹´èžÍ¡ÙQ#m +ëÇ,ƒ®;ò<9ŠGJò.j•¬.øµÒKíø°‡x£±õoP£)Ɨ!Ÿ¢ÿ×þ&HEj~͸\i1ÜÃbÓÅEC3s°E´yaZŒb ¬êA¶ÅÓ/(!¤ð2ž$iÝt¼P W©OS«#nËR^#„­áÀzeú™2K.¹ÏÊ/§ÇwU¦Õ}ÙÂýš³p)õu;™"LÊÉ?»À]ĵÌØãDL3 ÀM0Ϗ5Ce¬Åä2ŒŽ*or¹Ýp÷Wø€µZ˜ 6”)*¸»ã¶xY^p]…aÿlö˜‚­oâ¬sC%Ø\wd!iAÉŸg³»^g—žâYoË}˜EÛ¨$ÿÎä[8›¹Rcµ€É¥™ÊEyÜ*,ÿl_ÞHªUBmUÚýiÑÉÿº®g7¾%µ¥žè̆ºµ +Â>½åڜ٣NRœž—+¿M=}š¶<ý͍ØQ²Š[\·Yõ³÷EÜX·õ-âCF%&iQ\¨o!ù´ÛÚØJ~ñ[Y›×fX2øËå¿:³ì"òÖ¢Z‚%B‚^˺ìÌs:#× +Àµîã?Y&0•ï\ôVãkySL±ððåïÌK$ê]Ú¨ŽÆ‘âÉ CîÏø¨ŽÚùýú±⁽·$3ûYÂFˆ;£ƒA—Ë2'#ÿì$߁sÀ³£ À}¦Î&¦#€Â#ƒ£±\©ŽrËFéÚ<Áïç蝪O+3R•nûbÕH! +´d*ž_fÛô¦+lvï‹?í÷í¥“¦ÜE½ü؉ûC÷]ܾ–¶óQ$3„bŠJ1„û2±ºoŸÃ#!vrUy€h?typ-]ökí&ÿßóàž»Ò’˜%ÎhõÁعՏX´M2sYªcÅV¥òEƒa *ð\úÓ4{¾.= ÆaےÏÜ0 W€9EhˆnùŸ+FñÙAàØ$JMº$¢Ü¡›IÕáÇA JlvøƒP§õøÆ< •^µÂšó þ²hozâ#åŒÞA vÂù&7~-šñѪ6Á |’w÷/-åÆ®Î—•#Uº~Á/i€Áý²,‰ÿ Ã†àKX«o\»H{®5‰}ä߁ÝʨÌN-o= ÇG(é<ˆEûÀf%ïJ'ªÖ£M ¸ð¶˜§áÔfuì44Ë0*@ú¹+ô¶= äQrg¾EÿHZgÐþŒµ›%þRkÄ£×ÖòÇs"Eýhãµ8-ÈÜ 6I,[>eÓܘh¦®®/)_uáû²Ì˜/ÎR,HjÛvËãCVǙã,´h%bJ§x H׏\©ÚÜ$m[Mҋ¹8GF4. X‘gåe$¬ul7Â&»R]âì·¨>à°Ì ÅÞ¾ÍöÅ.æn&ëÆÝß ¸EØk:fÍÓÇç™î̄ØçÀé°À†ÂØ•óQ)Rfƒ2±nFÇÐü®Yk™Ü¢ã91ÿÜ<|F:¬wmM¨G(ëòþ€~º aØäɸ)½i öykÚÜ93L…â××<è±±Je5ô¼¢¾®XºÂ»¾C&é¥|¢4Ê£ÏÈ)qûÊ»"-äŠlm*Á¹lÀæ_tzÌX‹¸=}žË/3´àa£¸›§Æ{cÝÐðjù»¸(zjÇÿ!âÂè¾RÞ}˜Üî!yE„NÑ#[à{Þ<¬T,ÜLˆLˆ¦ñ/óþx ÓõwSœZʨ®5 +ϞIí”ßþÃ9š«zìão–½TΩƒØâ„Øm}[€IÔ½³D„«lFºi¯P«‰â9QCA̹ïð|‚èsÇÔ?h´TNt9Fje5õ’ÅjøÕ[lÎ= }«b€Üb°ó•iƒœJ;ÛèÑá~ Aó8·Èß·_ω¦sצ Ž­üB5m + +¡ô˜ÜÍÎYvY)K¥yžL¶2·…ë±Xf'/pvqÇÿ+f_â_¨Û#:ð“à‹¯õærm°M1S˜_V^ۈ«ëòdŠ¡T ÌA= ©ðAЋ¯€÷5†* Ò!d{½Ñh{©ü>>´IùD.iIióÞ]K F^ˆØyhû"îŒp/]=M˜^½„‘ƒÖ +òF0Exd?ÿ[è.{‡ü}œè±*‰å­2µKžE¥Èq6tªpoîL@_fƒËð \°®ZÓ3­“…Ï[|ß põ™—‘¡'ns¥å—*6R³ÀÞ)I^T&ŠÜßÖt^èâ 7j£4і¬érIÃmYÇ åʂÖl2R搆p /–žk4•ý=M8XÀ;Àï‹=M-Åeü,û³ƒ+Š>ŠXµ®ˆé¸Cp%iÓùLº£¨’?B*AÈbçþš"¸H¢=MƒÏê´VnìÚòÊ^ÊÊß´ »† Ï¬Ø K"׳3ES˜úÒÒçG|—IÐa')~?dzÇvwãK@ÚÔeˆ‰sW‚¨ˆÆÏŸf©#\’Óá‰|¯ÆaAO¯³Üï Œ +ËߏqeÌõŸwŸY +„h/a’݆<ÃÉÉ‹@šå„]Q"¶gAðX$ìasFP/JûV×éûZ^M‹E]½±= T[þä¹5˜Éªqµåɰ(¤ŽEŒl¾LPpãÑû´@É_ê=M‚*€t¸')JÌ?Ñ|…žkÌz™Û$¨ßóýKnY¼Í·õÝl†pӁ4rMÒ +D?pÝäÒ÷À²Ã‹+%&™´ÿZ¶Æ¤Uuv@ —Xì@öèÔ×ô¯<BÓ׀P¯7†#½ ÆZÇÇàr0Ô0ܦ‰:Y4g1U‚ËD:¼º™EYóV-Û¬‡M6gÉ)É.Z®)Gý±NN1çÙàÑðفf®•ö<ºýl‰ÞÑg_.“ +ŠqAO[Ee¼ÆÝÇKzË=MÆÊkÖ"³·^˜òÄ7Ôz‹O€¤VsÅç=M˜«6xÂ]Óv¤ûÕsì¢V ïc¡C™æƒ¿Ïyr2z²³.Š—å*m¦úp¥²Çmïx£ ¼8Ü® ‚Îq¾oðçÎOKAïØ¼QZš+*ÛkÚ»†&Ø~{I÷¤Bþ,e˜ÍýtWÄô$W” Þ= ò1Vp†bEw˜”Nt‘eVÐsZV©G %’­HYvûdiPö‹ÏŠ´—g™4|¹*w¨À³5¥1Š=M:““Ÿš7ŸLoزs›}ž[ᘏO%=}<§=}™ËSk€ðԝ¢'Á¹Ò¶¨é:¡8çȅ>¾EMjxd}Š,É$EæÉo®w?Kb¤†³¡¦¯IdcO= 1 ,<Ì>«É>ԏfYˆrD)≧n™n€±µrˆÌÉüPº„!Ò¤ÚÑ[ðÑï,%×=M8ªjÃî…æ%¬±Ou<úÉê]sa€Z‰+ú¥Ð5·klÒN<ە­ÄpÕÞֱØcD9¾ÒPn‹(êÀÐèëɵüU2uOúX´{>?,ŸR é²~ózËp˜[ùã 9Vàlh‘žt]4HÕã¤×•ùÔÍå'BGí‡l¿+„Á/W\Ñj2~_Z:øŸÑ™£Um‚Áeµ„ïù•Πœ­˜¤¡éܝÅ×¶$¦ï¦Œ+T>ˆ¶ÖÓ +M+<ËQ  +• ATÈCòT´ùB‚üë=M1‡þ'óЫ– ]Ì—] Ëb˜Ý¾Š§+Äc= ~m0½’: %%詚Kº%ª5ILÃ5UN¨ï¢ Í1P§œÇyîåÄ\ˆHÂ4*Þm‹ä1«S|‚m˜Zy…b+½DI&x~”ˆóª¥q©ž>ç€=}‚j¬ÅÝd Šy÷ÒÀk(HA(ůEÆt½Þ·@ß\WPö\ßù%P™åî·ä,£t½T¿8³·gÞv›5†È4wÄQ2„—+žI–Ô„zoÊ?xåŸâ‹Ò9G$šZû©¥vÒ[æ]mI ðBÞþ„ÒÑ¿ì…Æ“”L6µ@Tˆ È.)t¦\~:¯1ò1—Á±%gÄbøݪ8vxâö–(ª'ð$Œ¢òhh…ÉÉØáŒCš/Ý·ZpZßëa;#-O´¡—Z¡„Þ>Ç 7ú9pD’áIL Q„€¬<_žú¤ùÀÓ.ÜÕ9œaüþÌÁk6h $u[©&0.gÞ]I§ ƒëoˆ£®ªØDàAx¬“.xý.ˆÊ=MðòŽËD‡Ã\’à~è½KŸÜ0ÐþÔb<:þ<±v@ %6°öò P›Ï¯kÈ?À6ҟ¾ +¬èô,9§Ó“'†(¡3ù›µÂxLñ®¦òWÙåuûb/t¾ùUuh×x|ôxKÅl¼;É™–·%D™õyËÒ¿sâF*_Ô~²k5±í^!礦eAxèœÔR¶®VLƒÀ= 9ŠÞŸÌKIñçÔq؈Db|‹³­tUmLNnE¢=MÌo\¢ á?ìDé3|‹{|ª^œ&„“r›ÎEnIbrûx‡’”ƒ‡ŠPgòÔ¸“oû’Š 济D8ƒCjò†›±ÕœÏ‚ØD½Ž¡Û¹ž.üþÏÇG情!=MQ‰ÓIQâZû¢Ö];¸‚Tq¥3VœÕó¤ü¢ºòϺÛ,#ôìp)¨&ðü¯A?óxtý‹’çÏä<…‚>?Ì]I@¬ ºÁËZÝç¡O:¹ß\4Õ­Wœ\L<Âf®\]_\Æ–ØU&(JU0¯1h¸³}E´2š+º‹‡à5ÁÔ¶â)Bº¨¡Â“ï¹w h°Þ².Vò¯‘²ê¸x°¢Aú2•@,»?CäN¸é6é2ìFFc;흠#&òèU™[Ò5‰M㛠+jÈÂu§Þá![–L!x«8ÈqŸ^w"*Á—‰µ,*Oï Ê'–Eå4Âê„Õ +9»ÒPНL´žDƒ•ŽÅJýŒç¬¹_£Qa>éõþF Ž©Ü F½\ Ϟbä-ŒIù9»9›¦Ó8öT¹>qXÝ“Ä%>ñÿ Gnˆp6@.ðm뱫]8nLÙ¶&H䀻uÔ= 35­¨ÿ')αFÛ r˜bÝˆ+^Rà‘]õà/nmÚh4û‰wÕ°&´ku]m¦GUŽnžŽ3¦Ýù;‘&¹…¡Ç Úån!úŒ?´o¨¨+jØÁ†i¢± ‹ä}ã³ã&ØÐxŽò+½2á¼+Z±­Ð¾.)Æ\˜jLJÀý·Î\ëEãuHþ7°ç8:&'.§Cóç}ˆ·r5ò1ïaZþ§¥'fð«HÃÓèM¨ ¡©žlkžÄ¥íä19í¿ZäŒ#ý ùédaTk +xtƒy.XŒ×PB·CnŒ„ëæÒýŒédbùG­GŒæäžZ5_‹aÚÍ +ïyħÈíyŸ¿&2ulŒ´‡üþ—Š0ƒHÑÕÖè7~‰±š9±Éò _ñËD£)H5Ùv,Uµo wËMì²Å²Q[gÞ( 22xˆ=}MÙ2kíërCÈZ˜9!C%ߋY΅?40±Žs¶˜ùTƒl7žœË…¿[tDÝ= ;“'w£.—¤ÞygZÉ1D20¤¸¨úš{ ú\<¸˜mUåõÇøÌÈH…èíŸS=}ÈÏ´‹bÿ×€Ã?1RC,(xzz}Ù·w TÂ6<5Ã¥[?/Ø'8à\(õºæ6òÇ (a!_Üc«C|KkÆH ®[oäÙv¡9œ:ï ™î¬E‘R℠"ä›!iJP‘}ƒ‚¡íº=Mˆ³ëÏڞ”uYý´´ý·ÑCmȒÓÉ›Û4¾ˆ—¡º+´4ë»3ögeâñIö’èVñœð=M£pRçõh–¸¢¯¶–{Ï)¬'ÊN°óŽ ÄqX0íܖy§<Ô;ø[ûKο(öøOíåû+Œ«ì¯§ý®Ÿœ·Yh»Ø~Ù*à9¸AY=}ÿQÀ@F]¿è'muӉ•3JÔ¢øõ¡4GÎ1l÷«ÈjÚ=MÉ@ (¶H¡ÁƒßªH—g.’ñ…ã²/.–V52¬¼ÄLÏ/Š[T›ß-þ&Ë@®z»æôYNß»ÅÈh5øÐ!5Ì1´ ƒvm§£…ÞªˆÕ&>Á·ô×HŸé…Cý„AwUÀœÜnýa$“ü*eÞÊ©)Æ2ÚÇ2ÚÓâ %“òüï;­žŒbg©f.•Õ‡5E‚èàIýÐ\¢„ǺÚ×#…êsÓl:Ù"þåTçà<Ýüh¯fŸÄr‡ãüV‡£l\YÝ;Sï= ÐæEQX©œÂ» = Ð†3z2=}JQ$Ø[m-FV‡ã™= Ðf1z2½ís¶»éqXRxªƒñb‡cÍ7aïרr\Oŧ.' /)*wF·ùGÝ]ºüÝ*'Júm¹Öpǂñ(Z-i÷ð÷)bA©^$e7Þèçȋ6àïº÷.®ËºÆr5¥ÈÞ)Þq§Ñv³Qº=Mo§Ñ2¦ÑšVh,µ=M¯3S©Vø¥Ñ ü¹=M±Cò!³ÐVnZšVȼM4N¦0šCt&1T˜&$Ä,dz<Ék©®ta-äëX¢žMHÑ:mÐ õ¾é¥kNÝ5‡´ùåÿ­ÑzäQÿŒ“㿁 +á€ðÍ’ +°> †õ<¾\TŒ¯²¸zPÊ´‘BÒt¯T«© Àg<Û(†Ç”½HüÆSø±»] !/ º¦ð^FI= 7O6=}LsÕ%'Ô¿ßSⳋ&ÈuD>²¤å%C,­΍,¦”‰ÁóÔñ(ŠA„0Ìì‚ËÀdj@»W!¸;tv=M‰ú®²qJºðSÅq])šqSª¿ß¤—lÃcæ‹X&§îtxþ6£ìÑEÕõvV—^ÇOv¥-Ä6 mn M*¶±)= RX3Lñ|x1—<6²¦Ç…MO@íÃUfk™FªÈ/ûëÁeÞ&È/{ëÁemcñ.Í§ÝÆ?5ÝC±)8·”Ã̊!ÿ@18%¡®¤æÒñ1ª= ‡¢Íwùü¶“jÀʎc‰*¨ÔߐuÁsdHxõ9[ù"ùˆ¼4z0Å$ØÍ¿6¦qÏ*äL7à"1‘^ˆøë¸Þ2Ñ¥Ôyf7¶ÙZ +7’Ÿ|Š9‘8*=M”:˜äNcßEêkHª)·4ºÜAÞq~"¦Z¨ìVv@ŠºÃ­e¤+ïJÌ7¿Ö͑^OVAôNÂj®ˆ2Ô|I9vcP̜½¶­£äSלÀe?±º¾?: 0™ý‰W¯I©öü«¸L~íH2H¶= KjóÒwFþHEiø– Yø {µ2€/˜ #–=}r$$•2X¡%X+²=McøéŸÄ£oÝëטW3=}¤=M7?®fÎnEE¨è–Ww9g±ÕY¯SK*äz^˲4в4pæm±ÓLÐa–uU±h$ÍÓ ƒöõ¤Hâ >®­+ ë@=M°Âç{r˒‡mÇT²qeÂõzW$'/Ø ?;N!‡¨'ƒ¯ÍU…úv喣§z*·šž§kø‘¯ÉÊ2˜µ×åJäkÕúDFXdʸv4m¶3Ø®JÒ<1̦ UÝVó®šSa£?â¨SR±Ã+¸1þ@£yI°Šhø¥žbkªSçß¿K–×•dh, +%ú%ƒõ¡ÐIÊ¿0M[¥è<Cøò,ø´Ùòå轇¢q·ÆzC¢ +%–þõŒqsNÎ÷¾x½á[Q0¶¸óö\¾1ÝgÍüô˜UŸûs®=Mlu ‹®å N>ºQÚ¢' ,LsEÈ…Çý]q=M_ånCnJXKšdKL¬ ÃJ²¦š‡øÀ¬ ²ü =}öÛ_]an·"®ïÙ@¦:òÚsøóàî¨"j€Ó~w@€öSå~jc®Üß}˜zÂÜ[¡$چµî"×7TWp¥/K6z7î8:ýE™zü¯h+qÇQšr¥Ê²‡Ëw˜Fj÷¹möàæ ;÷IΐlP›¿{¬Ž2aƒ7Àli¯%¯îHªÜ5…‹C.6FٌÒ}©š±¥«BC‹+§ 7ªï"ytÚîr•æžÜY±Ûð=MDœÞª¤åÄ ód¸úð5E¦§S@Ã7©B'¶ý³¯õô¥\-úËÕ:›%‰a¿g@–›ýŸ\wµÍ[©ËW +7ãÒ7÷+v6Ü҉ês‚‹ŸR²ô.ð$q|ŸvàKO8yFLéÒ ÓßvAÐì*UTªv.ÎÜêŠ0å 4ý +˜ï2{9 : RXRg'e®$^ér{ã ­{Ãñ=M$͵°„[= bbâ×;¥5øy6KP³ m +ì—û–eYÞù.W¹‰mÃÌLñü7¨}B¼ ‚ï +n˜ Y#ØGiÀÜX‹Ÿ‡fËPÁÏL‚ïB“ fˆezKHó<&ØXæ +X[åÛÌÖC{{ò*rŠï3Bó—6Ü­6¸¹ÓûÌ .­á ÷ Xeåµô´îŸj]©ž!Eƒâ]úñ„úí%·«nòl·—µÜ7—u]9>dœ2Ä"aï†8úr¶±–2ZlÖßð:¤áÞ´QÒ² ýžw›!|µÆO,[F ¾ªCá&Š,ãÜ[Àè² úÁ±¼º‚ú4$¤Bý«ÙP§%= _ÒÞþ(€˜S©>ç.(ˆŠóÛΩì"Àïy’¹áèàq®ôµ›#å~ãÊç—h2;Ì<2vDŽ®ÇP= {Ù‘>܊²/!‚Kü.#ÅJçhÞf#:ˆ 7H8Ž kän·‘dŒĊgOñÎHÉÉäåù “ñF’”÷v(eؐ%ùë÷že)Ñó.óŠ8.œcÍ'Nµù_¢ÙÚy¬<ÈØs4Q Ý¼«ò¦°‚cďq$sø•ØÙv6ï BA»Éҍªx  + ‡…ÕW=}F´PÚâ%+ô¯Ú^è+bsA'R‹˜ÃœÆ.¬6N‘Ö$'o†¶/L5(Ð>Úà‚ô/)"¶pÖO‹1^mÍFjìM¶ÖxZ,–7‹ò­‹íi¯9ºd…?¦¿ŸL¨#X*c éB€E‡L澬h.¬gÆ'Â{ƒ”¨ýN¬¹„§ˆ§º‡Œ©'õñÅ>ƒk „ïi#µ#YhÞØ)I內áŠ5/çgäa©¥”¶˜œ%ž•}¨'šcKó×ýæëÜÖûˆ—ÖLÛ&4é~§>~©‚òçA·ËÊÜWʧø·;= ;#>I8¸ÔïG jh¹\Ð{³LËIG‹¹EJ’4ã•;c./~ ÷YVÅ,Y<-Yåt÷·"Å-﹤„Â5Ò [ðÔíãcor½‹6~2¥œ‡éE©µ )V•þ¸ç|9ŒV?Ќ/6éièÛsΈ£ø%ùY·ñÀIûI Þ­ËG  +«KcòûAü=M+ñ=Mž~4$y%“~ÔJŒµ7(>K+.p×'‹é޹ßdºZnHß׍6úIXOá<=}ì~9ŠÓÏ)ed~AXÖu¯±=}îO¢8/ªÓS»f/ò1@É= qùKV¤ëÄÌ@m"UPۄS= bÓa…Chðò¿LÅSAqFEháãHwNÕ¿o+ ØÃG¯]:×K…ñë1HË4= ÷Ь†áÌ|®É*Š~µ÷ÎyGûjÀ‰ñË&þzM]C+;ÏÉÎ(çë=M}û†=}ôw½ªéäAym,FYŸ XŽÔ:·_ñè. OºŸ"Ä}»ÿ}Ú p;7HÓôY“Z·Ë« í0Ä7ÕÚ92*?Y‡NQl)ÌÛS×"ŽCvÖC3|›ÒªÑO³áYý…fՎhuÄqô™”U’˜¹d+Ùmivm8|Üï'ÀšÒÇìRx̓æ‰Ã¯P’£‹)ãV¦Á4hv«Ì7èeÞNË·Ýt$boX¿_ze¾FASq9ωe€¥+òÇÔ¢~f0ôôy«‰ ÄÌòDó_çz˜DŽÔ‘d9ÏCNnoµ¸ šû*Qu“û͓‹÷‘¬  ýÈÕÅ= Uë¤Ð3L©ÿº÷«dû— ً|"máŒy˜ïø’=M¡6 âÃÐÙ= ›dwÃÒæKçLÆÓ?béŠ;åJ»˜ÝÈÓU” ‹‡úJ¯š,R» +·¹¸Ž!h~5BKòºHô´HnàIý·_Š‡‹0<†ÔƒäÙ²ãÏ詓®†ºŠFoâ[ÜO0ë9j’T5¾‰ _o¹)èB±æÞa™K±P5ý­4OW1+ê=}[b¨‘z?{•ÊÇÊòèYPÉQ%õ˜Ìç“A=M—·µÒÈw–3âÏøç¿Ì¯°ƒ*€emBÙIÉ~ÞÈÕ®¼V‘ÁÁ€éý>°CI›a¨%Èýx– ++¼áü·ÃVõFHtPDtu†–ðÒݑb­?Iz…ËF +‰1ƒÈÜñ¸Äþ\¶¿C؊âœ9MP{®Ð¹XmÀ¨£Á¦è”1jOÔøŠ¿r4G´r4Eƒˆ»¸Ë€?á;_ݧÙ)ôØâQúké²^í£Ÿ¶¾Ù¬H}H}¶Å ó2ÚD}ÿ«r4ڌÒO 0¡Ô‰*4ú±é‘vԒ‚„mWÖ1\=MáG,ÿø+ÿØ>å¶ôòâ8cWÏø2"Gs¿uÄ2„8©=Mú Á¾á8qRB˜¦¡"TŒ-Ð<ܧ+ÖÔÕo¶ÆKwÑ×ùª׎٠¤í¤§‚£I<ë4Î>eB$°ñéPêBÍQÓԛHËf"f¬SË¿¥³ò´;+&«V0[¦·è¤Õeýãœez“ c1ü‹=}(«4§#Î™Áï•4ÔúýÊ´YӋˆ2H I.ÃL\4´2-*™"“†”–}ÄÛZ7‹É¿p°®æž ¹VM9&S]ÿíaûÇkû&{ +Ï= }Cg,‘s\Ç= Smދþ%ôé~ÀÒò‚ù„€ÏÂOIêŽßæ<€x7S[¬)o!Ž›VC–×S× +~E'StFc×B^c+t0Ùóäù#ê°+íÑÊÓQ ÔV…€øPÂÑ(ü±Ñá…M ÞpÓHI)Tπ¾\ÀÍ@ w±¦PþÐ!œ1U,žâ3“YUÚFú“èW<¾‹îÐ}ÉdáßÞ d4_”È“±Dr*Ë1#H…wÚ~‡@َœ‡d_v“¶²ÄšûCÚTüÛ¯AXV“^ŽNuAEÄ/U.ÖA^dzÂN¾H€øGž‘¿ÝbmojFhò£ý“R©&ç}®ô:ŀvI|D +’×þZç¹ïANƒ‘«§U~*„AOÑò爛lxûµzãÓb ØÖáIք‡¤âMÙX®?›–б¨f¹ +Û\¢) ÎþÍK6ËW­µèJBÝ>Sùýù‘ÞÂÝêZÓìoA:§N郠"B7©?áñÖ:“6ÊÛ¡Ù,eXš‰Û—²[@¤ß'h2¸I¯«xқN^\6 +æL£}¬^u··ûNŐ{W¾÷9Ýn’0Kõ(ÀvçTu@Xz£Œ!u›ìÀ±ƒÞ“é윀Ðm4vdojV›£ &Ô0ìœðEÄvd@ê£KX´PÏm?¶#ñ²bVÓ,Ôlò²XVÓ(Ô\C£K~õ„yd@Ðmø‘Î +E˜£‹ë“q +EØ£‹Û“q +ÅEH‘NŒõâ(¦wɳ•(ÏíÏT×õ«›?Aêú˜jDWkqWÍjJú ¹"è-Yûò¸(P[Ðôëd=Mz +!LSQ†[,lÂcSQz + œóóLMŒz + ÐôóLM’z + (4Kðù÷ + + ”laW(õ¬³]pâÊø.àZ×ÅaDƒµ¶Ž+HÎӟÐWv=  @᣿ZѳÔN§µ +w?•N˜¡CŸåÍÕ +êS˜Aòªõu•0ìší˜ìš=}/CíwڕrK¢BĊa^!Æz UÈGÎURìšÝ²uc@—y£ê–ŸœÍêS˜1M¿UU–#òªõ^–CP¯Õµp&¬† ŒŸ3ƒ;?4SϱÓwèÜÌQ›´ß´%†xôÙö=MkLµ+Õ·€çï¶ÿt ïJv){Tt:%Ût9¾ +‰=}>/KÙ–h”ŠWÇlû:ªÂÔÆ¡]†èÚ¬É,L†ô’ôϖÕtífHÃsX&_âE€3£E 9‡®çÒ¶j&_Œ%g·R_±Àñ®_uV%B@¬£ç÷ÂNFÂAÇò¹"ti +“ÅsÃäJ,ÅÚO%ÏÒ8ÚûŒÉ„Wi G˜¸¸_®DÛOÀghŽqQe±ŽÎv̰µì!QÎQZaðUp4…£y:mëcM‹e[ìs©ƒå‹K,º]§ôý˜P­î]çPQb‡,³‰=}I‰™\ ÝÔÃô'$â9thóÇr\Ú?ù {€·:MÕÜA@'cî̬€š'˳‡‚¨?'¶¯4¬ +7þ(¼Ã…xdíÙ¸«}ÁºiHcè$lžô“FZ±z>HÌG£›™¾“<É®‡d5!í¸ñ¬y{sF“µÆˆÌxMй݀ óÆ?8XùRS/ßgŒ§^qµêò +73ÿ€ÿkÏk”ãÓއ2ätL‘*ŒÚäs¬zô0¿ôl‡AP t5¨*=M‘ô$;<¢ó1»&ª_åyIîE4MóÚÑ27zóÇy¡„Ù¼ïúmÏV菛áŠišÓê ] »8ìi^5è·^Œ +x‡çÉtÿœöþÞÎE0dRû#9´¯;uµ«Ä¢=MÖÃÁæCXEx¤èùÖê¬u¾BvÅÝØ†ÁøÔ½”‹ô–oTŒ¤óïáø:æ r h„õN‰~¡Š÷¥”eì“Ê<‰[”‹4{<ÆN7X4».íP3™×¤¥‚2ìòZ,‹C'n¢å›ø<í{ý×?ø+ùî­$Á3òE¬= ¶ïbÇ;›•Ô“¡«îqÒS°šçÆz)ÈPž—yrOžßüÌց¼½&(­ùWâ¥Åi¢)Sè¿ú%Ý):5ÕGE_všùÍO›¡!S( Ï©M9^ù“Š'ׁÏó“":¬¸9º£g¶ o‡hý:<¢)xy…S}àŽˆªäËØJö†yAd‡N¶œ,vrC­7·>Td·Î\:Ú'æÇª®Feî&bA±'õýjìµÄ=MD½Êíó¾zªZ9Ë寠ÏÌkþZ(T(œÁ¢d¿¦_Vd±ÈdHGAC½ˆü ÜßÕaÍï”3Z S‰>ÀM\p­èYr¸o9yå,8íŒ$8½P=M«ƒ#{ªvPÄ©Fz÷#5üÌÈ»¼ŒcÓR›Îàí×Ǽxp†§ycßÝóñ׎ìx½<%‰ ÃêÎ_VÈ>I2°é:Ù·¯<,èµh4€éúhf]Û( %w¡´˜œ?uÝ$Íf}¡@LWQ³móїàN„2†ÏßR6_0#¡|*p¾_& ƒ% ƒwÈObýZuý %/½õŽb#ää¥ìß&“OõLD7ýþŸÝî£ÞŒvä ÁšIZc·= Læ˜!F\•#¦ÑŠ.ÂÀ$·„»y”l€q4F҈Ñ,“Ó¨‹T +ôŽÝ)O²™TØûÒSXý¥2uþzq_X~´Ix‰|Þ 4Îȅ’wy‹}®¶”ԏøŽ’ûqj‚ÕŽ­‘_¹%åŽ#×OµÃ74ƒ=M«•‘UƱzi»jÊB&ÔòfÞ´'ÉÉÄb°N±Â¿ˆbÃ/ÔKEߎU"x7_M6µò÷nüˆù\ʇm åTwågÜ<”LpéY «Ä 0Z p³ÜñÞ½ûÕ°‡+;s¦ÈÉoU©Z¢1ànɕ ºE‰´ªÙJŒ£l\t %¤Ô +0w52æfš_ÒÆLQÊ­]bœ´ø#úÿ\O‚9ʤg>ÿy?ªêÜ”¢öd“lôg2e¿•æ4íî¢~ÃaAT!*x1¡YÛx7Yíû£y’¬‡+â!:0ÙG=MvºF»´î:‚¬Ô˜„}„ϨЬóy’†-½py·øš‘„*ȉ÷¦ÅöýèxdjÙÖÇۉñsö¥ uh”½ö­‰2€élDjsÎkŽÏüªÃ’OÉ å܈j>˾çNNddF9>^Gߪkàx×W‰QH8í!?È +ÐðVÀº.g—c¨v®øü6aª¯†D¦„éÝä:1¤“*‡Vû9©Š0Í?i Ý5¼‡H(h£jÄB¨Óùcc$G†Tù[›jvôY卵8™ÆêáXÙd½&ú©؈ße£v¡m?Ìj"=}Pç·2V’Jê9”­«iˆ—/ó‹’= ¡ž!'>óY\>àÖ(†¤§ÇəÄד’ÇNFF҂€&6“dDlFkN½|\&ÏjÚZ’-ˆ?Åõ= lWHxîtŸµVäFô¬9HXž‹1rj"¯¡}ŸÛò˜©†ÔH/ðKçídW¢C_»BGÈñ¹­sMÁ9Eizëc:Þ0S¤}Òo Jáw=}덽ÉóÎ:VÐq'UécÄ?µ$)·µŒKœÇ7¦iýàô)L;(IŸ½FëbÖôÄ=}ÐP‹¼¯¾’×ÕërfJ]ÜBŸ™C~柤a÷ӓ…@®#-œVÕwޙz+„ÖöøÈ¶4Q¢"Ò©a–Ô08âÓÝpöh%X6àýƒ6šˆÂ‹‡ÒiwÉÃ-Tb ÈyðÆj«KŽCÆ 8¢‚œa›aö>böÐyEÞm‚« ¥ÍUeØ}ç]–˜BÏãæá Ër>q¾_KêØø‡ƅâ<ÈäùýUö¡ýƒæ,½åm¢/¯x$öp@\ŒGùò‹jW N*W1¬›™'OÓ)µÄ%2«=}ˆIžlƙý,Â*2+¬±ò-5 =Mþ^mžÙº·ÛWcd—MФýy÷NàÈj«êȖĚÒÁ«Q9Ó½‰»5–¶ûzûI£±½¥¶ŠUqlëÒ öMzLZBÏiLO‰òqyèùð<ñ ¾29úôÊróIŒ*ÆßôßÏæ1)әÛj؇æÄ£ÆÀ=MyIý…·…È·.ÖQ“…4“Å-›Á†@V¿ðøÉXécé2˃ðhÍ?\°~gt,ŠüCJ–58â:â€éÿ§5e*O UD  iø=M4j%¾¦ñs”.Œ’Z¶iâP“í¡ú‹às±…·€‚nò)æe¾^ZMû€O'ÿ–ÝL­î·Î(êçv†µk0r=}¤Î‹žò.m,ëHxßnêµ|Çéš4[ƒ'eê³ÿdãp3 ‡R¤ˆplZ¾7‡‡ÈÓ™ö¾›¯¯­iò¤ø?I~KŸ=M\Óì|ïx)ýJø‡ïN€ÿ6Äea@Øî®÷ª,Áát2³¤Ú¸;Od,[ç“h ô•¯®3êv™¢À£O&KëêFˆÛÊÑl±læLºVéâ!:G0Ùu^—ÕóO€u}Ý%‰¾ Žk懿 ˆû)¬|¬5o¶·‰Ü«úù¨åÙy²Š³Þ=}¢äÿU­"PÄ}Eœ¨R‚:éÄñÓ +ïm= dï”Øâä©y[ÎJj0­8hÁ)iy6þß«´e„ÚʘƫwœÄzžÍzŸœ¬TmÀ˜?L 0¾æV4Yd€í +χ=MŽ«Èmü[Ï +A“$†VhåLo®S >íx_[‚†Ô”Xý‘l”Ýs“†´Ùx8±Êvò ++óíñBžUÐjï_êÔ­‚µçoòÂ\•ªìàÑÀYÓËCe{ ¾´ ¬Åh²®.Ak0™¥,ÍM‹ø‘>Û’6%† )âþ= rÏÿƒé®¯Šð~ó ˆ’qUMÑ©ô÷MûòV4©¦],‡uvþ¢ô]õe‰ý'Hwî£%6ÂOI¸1)‰ïÌ"= ¶ê²>OòêLl+:@Î+âüžR:¾]"ÙÕO%Ш}Qâ­ï±18«S º¿=MfàÀù“‹Eô©®ý¿ Mœåðä„PЉaB<,ÈÈHçَÔþ†dtãÎ5LÃG0…–{[–êc¼9è +ÇmP:¼ÂKޟ_Q…W¥žeŸ7ê¤ Õ–‡YéIµWë­1pÑþU6G¯KAPvkÒ,¸8Çٖ¥xB\ŽØ!Z:†Üßÿ^Qc@@liÃ0ÿ,¯®\×= ‚ÏxÙm\|(}íÒ_ *‚›TdjßYÜ C_?ŒJ¼¦dÙñ¶ýxØSë€üwùñÉÃnis~õ_™Oþ[êhüË®œa¥NË= G ÝCô« ú4(FMŒÒÔìŒxí€ì©êí»€"Eiې,ï GËßý?0BmþÇÕÖ>ª97~?oŸ<±îw'è®"î;¹Üf9ˆ2ìÍ ®7k9RøCPù‰k<–,øš·h74@ò W¤÷~jȯóuišƒÖFþð1^~8?‰Œ“doÓAi^ãéO°ö²ÂÚz´9=}ͤAÌՔ¤NºfÔ<»5ÚâÓg{Þˆò¥*»û”)$oo +•JW­y~(ÅÓ;j«Yâ?úEa¿8¨!å)‰!â²fO¬< +ì±-»÷ñ§0mŒ­uSá[ÿ6àH<$8ðÞb}\ †ˆG½ÉH=M £¤…1!Ì = ø4Ögˆ8p‚',[Ýp8”ƒ›­½ª¿+ŸªÉ˜…;—bÂÌ<ÐÃ^ìuOy¼ÖÔ}#´+ýPÈÉÛ»yܓx˃щBýÜݚç¯ô.¼e…hF¢Áb˜æ8ÕiÙ²N§®¹o}õQ××»Õëöy‡8ÆBÕól=MΠcـ'd7ۜ¦¯ÌŸ/ušt‡¾>„1›keu­˜ê¢ˆÃthMuÞ觚³k^ƒ —Î9p§l(Ó¼I1b¡à9†Sùêþ—Tº¿xëð×_§ç*%žæ½ûà•ûþðÝי¤]½©—C›‡ž9K{öþ߆ãó¾fA©ò×¾9‹YBØ^m™ ‡ppúo. Çþ dÉWbzóñ×TéL×k‰Àùö(Ž™«¸òîøËˆ¶N+à4áw…êcA¼Çx‘&NÞ rµ-&Ê:ñê\'¿„ËÝX&½&ŒLg?lŽ67Ió@K›°u©;xàØn–¯|¬V6d‘·‚š@ó aì¸GN³ý\?&=  ®7ÍÝ»l†ö$ä»ô{ݽ8€bõ¤ÿC˜V¡™èX2Ô ¥ž›‘Íe˜ÖÉV¾‹òÂÎ|‡t= ­æ"vÚméze:•¿m @£,z¤·úü‘¬‡fz70+¹³‹HQL© +'ûÚ© ‰~“Ht‘üԔˆ‰~ðsp~yu2©ž.½ÿ”χQ¥R¥¿“é‡\X¼Ñ^<'cl ‡4ÛÆù•Ë֍¢ôÄYV¼VÑ̓”סԪ”1‘†¼(ä;~”_“|”_”Ü3ª”DõUIeÁ4½µÞ!™–ÕºÛª¦ ôÝӊ¾uÅ6_^<¥+;”´ ˆ•º\¯Ù–MO”k½pÑÄãkFpKpKpKÊïA‚.Vꌳô¼€©Î&ø= ™Ü.òW•’ÖW…¡¥ÎdØ2hçnïŒYè´3QWou–’E¤zƒoœÞÌÏÖ£OÖÜQŸÅðQ‚”NÚÆ\ˆEÆjÙ©9Æî…l7Ü.äÿXífå—üøOÿ ›-¢3Å«X>úMà¡4+—z›h\«!É=M \ÇñœÃ"(ö®4oæ…ðãkXÛ™…ÕõÖ´åYÏeª=M}—S\Ë ÚC/‰&±_þÐF¤­EŠÝؕY‘#£Du›Ñêµ/¯Ú¹—.€¢O„–¥— «5¯*T0ÕljXõáÿ› +ÙÖN°mγÉþ—‰VLúWÿ…˜£ð[$©fÃÅù¤"«NƒÕ$hMá½zo‚U|ù~UÛ°…¦î; àtHÞ´X^”£µàó.ÛóV‰³€ûtL=MoxÍÄì-ºAµmrEXö X:ÞXÓƒ£‚°¯®Òù +Ýr%'u"VþV4[›ÞKœ»›3¤Ý Mƒ4vf„ÔTrSƒSȇQð4‹Œ¯~Žm¸iøQØÌôL“ªã¿‡o‡ŽG\ç>I>ù>…%<ŽÁÇ+¾µ&9ÀR+Ÿ•Ê9€5ˆ<}ÀKÁðM ng¨B¬Â«O~KæÅKÊl¬}g = àjº¾º +ÚB c«¬cÂezŒc0lɄ’qÎbÓûŒdKýìÿxJ¤ÊØÊ3/0fýŠäjãêêQ_7=}q>1AAaç[À*çºB™zNe½º¾iÜcV$£A3Ó7 Û.ê¨X’q™e@sÒO ÿ”+*ˆ<"²gP€[Jè@¹\ۍP= ºÁ¢¬=MpTˆ/T­c4ü–ý•×IOº›é5ñîžëàá²&Í-OQbR×Aȗ_¾x‘y­òÐ]ö,ká‡5½7Íhëèb{»VÞ™~YÛØ .Y{0uxºÙ÷Œ³õüÂÆ¥‰¦ŠþÔìb”ñ™q¨±Ö.AÎFÈØNÇ·+ÊÞ6AóÒØAµ„\Æ×†U”!ë¥ïçmSŽTVšüºÔeÊì½øb¨£Tž»×R® ð ¡ÒÇhþ&YKŒd:ªÅ´ÆzÔY=}ëÛÄ2cëÌh;G[°/&çk/qrÛΰκ-mMƒ·š#^ÊT°(<Çí®vþ%ÝЕ…Ô0떬 +‡Zœ+‚Ä ¬+Û×í0&ǽÞ:¦FÖE˜E~G(Éã>˜Pl™£¨æ‰Y('Muå÷„Äg”Ì= “Ô„ÈÐûót0n.¶A5[×ZË2®Qˆi"ìT"‚Tp2Ž G€Wbþ|ƒQŽ2kªB!Î vî(3.7b¾üéJÅï˖¦†NÌ?z/ÎÄoE=}w¸ô\ÆÝ©9}v¸6íáÛzÄ/›{æPã+éɰã2PV‘Px€ãˆíOŠåÖþR3xq*æÜŸ=  +\oÁs ½T7ÞhYozK¸ºª´Ê¡H¬Ðñ‰Mæ= ]_ +ŽK8uØüAJ„kyhÌ3Ê#†"m +7MQëA"…Ka Äð‰âñ+D"= N‰KÚØdŠ;Àdâ;a‡ðOÎ|ÿjo›KÀ :4T“{ÌX†‹ 8wãôk;þÅj¾ùXVˆú´ŠBX'ãôDŽ¤˜= Î@ úñgeÎ ‡Á4:pÃRÆEžr-/òT¬²Xþˆ§ã«qB‘1*ˆQ…=}q@d¤9ÝË;70µnËó)›N‚™8BY½²ºHšÕ8GUVA%-Ú÷³= ò¹c¥Êµ,= ÃNÚ8™ñ­aûÅç_´{ÜöÔÊYðo2[ó3_É$—N§B=Mz‡Œ¼ç «Ä£‰1Î~­¢SÏâ¾8€~$ü1]_ÍT + e!ºó7à%àJ³j;UØåëPŸ™»—¨½Vƒþ»ðZ³ìY»Ã¶ˆ™ð¤ùƒÀãQ |Øï÷ô&ùôMíy{ Bã·¾ƒŽ„ódRŠ~öÎ ¹þð|ù;»ksNè1ìð'HD|Ð'wQ¤â +~„¤rDÀÎ%/èñ)såTÄD“ZI7PR'ðºŠ' &½î¹Ë¨NªêËkªªû<yŠ+9‚à= >@tÌÇÑiZö¾×X +;8ኡº®hðƒÞ÷ûEx ¥¶øÓtí9.bºÀ¢*Eá\Zzxéªuì=}ø=}Û3 ž¿ÜÛ~í_ò¶ø_ +Ò°ÆcpYбIŒ=MäMaˆr3ëhx¾í„‚u­“ ùùõ< 20=}ù™^àÔ÷¶ª=}-)y*—×[Ù¯«·šÀË×°Þ9É×ë¡×–å:å2Ν²??ÒskÒÙIÊvmNújâr!ƒ¹ÐŒu갌³ÑêDG6 üzÁTº¦çÈ ºêÂççèµ ‡ôSd謴éÊ ?þ!H3vLÅü‡^QÐêbç'ÒÐZ[M<ÄÓ=MneÖý#H †ôJþäãfŠgÌ +ÉlYc^Ä éW†×Þúœ$ÁEŒôQË¿ÊPyƒ= ãÿa)~«CÞÎ.8wÃV7Þ\ST „îbÄèÌ/{Kµp3‰¨|D÷ј= îÓ ·7ÖuŸm(ÊâHQ|Dñ´² u‡(OãcÏ”fÆ5 +)¶ +-û}ñߟé#ž$Xüñç՚ù•Œ§Ì4ß6è}½%Æ9¢õR.É%€=}aO1Ç7ÀÖØ°M宎%”%´Úú±uöý…Á•u~õ,V†z8ûXpä+&Nuªpb<¿”úœ‘ Í*i½Óœ?,)—sË%><€~? SáÚÿ1y EÞ{žx•ÝIýÀ»4Wó͕¢>âùÂtëØêrÜÂ,²ÝJÖ±À~Ry¢§Üœˆµ¾9RµwdŸeT†¦Q©…ù™Æ×ÑÅ/ ÍÙ¦xrâÐåìÍìͬý¸%†WöџÁøpíóÙµ¾Éc祉\«®¯³º¢ç.Àz¯ÅŒ®ÂMÙ_ÖìÝXÑ5 ­¡¶÷¢Ø«BarYA9˜>¥û¼D)⥘a×s­óª<³Oã#D²ûÛÀµ•ï]°ê= ñ5“ø¶'ÕM?Åö©È^¯ó¢d¡CH©]1ªX7îeßF…ç•‹:¥ÄSsµ85‚œ ­G½ZšyIj»VÌM_7+Œ…½óä¦åÓ%©÷СoíÈìۀUÿ§dlڈ®]Ì]øÎ! 8˜—㟎Xn,„n<£åÊ%?âÿÀ~â¡ó:Õ¸UÝÆv±¼=M¬¶Øs1—GY]E–àõ fQë;!‘¬‡®Úù@vUD¶W.¿ã›Xü¡ödúÅ%XñMW= õ”„ùSxvÎ÷Uuræu©ý£MKˆVøÔ뙄yf±—ÖéŬàιÛìãÈJ!–Ö3pf‘êÙµ‰Æƒße²’;W!±þ• ÄL1W“¤hŠÕ®á¡ü¨Ñ_'uÞ;Öï'ÂÄ4tNŽ ë߉+â€lBóJqt/e_F&SÏ^׸˜4ŒQwëöVŽ!‰ò€X¬ŽŽ=MMÐ-0}ýç¿_ßÁ ŽnÀlÃó_P>wk(gð#CÈRKÁßbÍ=M‹õ£NÒ.¿êé¥vì-A] ÑÂN¶n¼ P³¾­ì„Ð…½è'¨S7¹QÕÁ«¼¿µ½Ñ³Ÿí|¬¾n¬‹òŠP˜Ãû‚¡¨µîæE¯"rç ÛÜND  §CßãÒ +|ëkSy7´±o³þÑj™@¾S’Q<¹â‡?)…ð7pº^·¤Ì¤ìì(™|—F0•~ùýâáñ¨tŠ3s¬®®¿«j„s&«ª+MκâÞe¹„’Ø'!‚ö:»8“@lbN,q »âQÓп‰P}„%·§ñráìyڗ9ÍKŒ'íÉäææ©®¨Â=MAu‚eØ= t£bxÜ.Ì£ßÇ;|Ü ®È…\2?G¦~rޱ®hF6ÇÅtN¿wX‘¢ OB•„ñ:¦ñ¦,]-/õ²D£a1‰®€‰³op×Pí‹#u¶†§ÿ£×jÑ=}¯EÕ/îv?åÁiÿ=}MÉÉxw­Ðà¨ÒI*uÆi/È ç,$šj8e”Šè:<‡=MhFۛø‰Š +IJÉv=}[x­/·n¿så5ÃÄàóё@¾PʌŒ-#²ÒĜéSÝìÅ\m +×3âtôRôÓR†‘nxàÖȁ‚ÂIE®êy!¿ä74ÁmRӝ־fÛ}§i­©€!“Íâ7tf,[g0¥R©®Xüjáù!à×å¾aìú BæâÞ]©iñË ÈÝáy>G•jåäPð¼¨÷kDŸ†/Î +7FÃ4= å”’KêzhGpڜX|á°ÎLÊ=M[©ø²¥ùX­à¦ +tpÏø¡„túP4Çto%d%9„"¹ÌËÁmjû‡íÒâÊÙX4ôzڎï}ˆÝjæ.&6SƒÜNf¶£xINÍ¿­®}‚?Q3NjñP›/ñQf”ÿͬƇ­ +/"Ø])o4¤Dºc.YÖâú<‹ÜÍáå\”äú½Y7Œ(f|íâi\ ×ts\ãëÚtï˜ *fÕ\(ÒKÁitŠh.µèCÇ@Áòõ8@›PÓÝ#DŠ1=}CSIÑÂòyõYEePp\k0þ(•Bôo‰™û;$z>u„ëäpp0ÜldJe:*¹tO¯À‡÷,aÉê¼íG]“‹ðŠ"8€A‰Î=M&vT€–ò±ÂÙ²¯}@Y ÙÊ(ê値6ßý5RÑsæØžŠ QÓPoç¢cÜþ‚BUÞ©“kKÔÔ¨Îà˜üAD’ê6K¿…’òJ $DŠ-5#†¡þ›}¢®ºqo‹›Li:5 }π= d\à +±\,6°ƒ”HJ&¢“kñªŸ1J&6@IÚ4wIflÐs¹”Î'ǯ‘xw²«¯'Ó H|¿763oQúxލÂ~lkœ3?UŠKê3îøD"֐ñÒ¾Y'D!þÁÔHbø±BÐtÓêXsLg·ùRþX¨&HšÑ*f€= Äd–P2–8Ñ7Š?V47Ú¿ö :KóÒ0#owŠõúýiŒÓŸÛ¬f’î.õdæŠÿÁñûß½Uë8ÁsÚ°X­}“~qÿC§ûTÉrâ-È+÷^§‰ h± ù=M“‘“wÑÈS?±òèZ."ƒ—ƒ½O¶]z*i: ©IÔmrí0dù +n» þJ8M_#S>.B „7…}©íFW…inSÖàìÔ6i +!™{¸«C6= W@Î/[(BòÜ+DpMº0"Uz÷Éï©©©©îÁkÔ¿÷fñÀ€.ã:ÔS’ÇÍ7µ¬ø^ðÚ=}=Máêsì'd>]\*zÇñŽÑ¶,:z)ñ¸,ü=  +®N³¾Hv‡[vC„ȧ©T}?&ÀK¶ _¦ÎÄÙ:ß#½K¨ÉÜà>$¨×êHÅ NfjëÙ ÚD 2YüxȀ~i¦‰¤¯[T‚ÜXýÝæsG5"Ê՝cÛɍ ÖðeÀ›˜}®pËDpKòpKpKpKPr$æCJD1yàχ°x +¦“JÒ϶q,¥×睴f„ € =M %wϞ“È%ƒŸèóñ86…T”’è"ö±¨-Y©;Æîaö³ íY§+FêA¶½÷¸¹çݸÁ]8·ß½8¿ÿ=}8»ïý8Ã}ø¶Ûí,Šn%"n)bn'Bn+‚n&2n*rn(Rn,’Î%Î)XÎ'8Î+xÎ&(Î*hÎ(HÎ,ˆN% N)= N'@N+€N&0N*pN(PN,%)\'<+|&,*l(L,ŒŽ%$Ž)dŽ'DŽ+„Ž&4Ž*tŽ(æ8a4áõ§è-[¡ûÆÁõ¦àí[ŸëF7¶#™'ݶ"¡G]¶$—½6!Ÿ?=}6#›/ý6"£O}6$–-«iÙ¡š EÊÜÒg(n1ˆ0ê8‚¡,ˆÈÊøa)„þ®a°üä-3†û×Z™È8ù7.fÅ^ám=MGáÎ3ÿÞèèoÇj-:k±Ê|?Òä¤WÑSåäÚÀfø>ó= Šñ6Œ¿®é= 7ë» +Gþ%æ8A/úk¿PË® ]Gè;rG‚*4AœùÓ¹¦Èí\ÁkÈ(¢aû¸ÈoYAËÊHbbàÈÉY#b Hrf! ˀò‚~ƒ 䋋ŠSKTœO|õŒ³.XÒ9 í€$ŠcxR¹ü1wAh1HdçXNÿÎôâ¬?‚÷Êó;kÀi*oDjXŠNÞäL}‚ËÒs>lPŠ4s´%VXG=Mé»ä]{¾®ÒÀ@|ú‹'ó*v€Gà;„_‹®¹€äú“NTGV¡Ž%2܎s£+mR˜\\3’ÎVŒ(ò4?ÓPH|±Œp"žàò̌Ðbé€ûM3„’· œ_fݝ{2׍åÓÔ= vd×­:Dž” Ú$¸mdÐH øä'"ÊêyT.ïF2ñÌâJ.ïlñÿ›Š«o~iK‚BސCìƒLZˆaQKZò› 䖆ÀõI¸=}É ï2Õµ©¥á³ßU’Öõïk4µÅÓ¸=}ö5VÕaõ5±V@øe=M–=œU=}úænÚxÝ«gÏ«(Huúe %Ö§úŃ'vxœ®á&%v½L= V&Ølç–/—[ ŸéÀ6°=}µ¾Æn֚CLŸ»ÞšV2WÍwxªŸA¯wçÊ)Ç¢M¯ÉójIH0š¿0ï7FV§'@ª»dH—üsa~|î0ŠËÅËmuøèð-Kp E‡;²MV‹eþ±µC FB¸¿4Ï×삙¶[®(´Ï0x—èð£Wì£[BCÍY¢ ”NØNä Oœ¯ˆÒÖ/VÝQæX°"pµ.ÐwyFuØ4®•>êµ!f֍­aXiInè½Ø/ þ£ò€«¥Hö®߯©Ooŝ"v{¸UîÚV©´¢6¡!À÷††ã±‰3ž¥P¹×N“uÓemæ•þýžx|ë=}Á[[K%o0ˆt–±‚™êüÝô'ö±($ÏáÈÈÕÑH5q1¢Ãº¬O,¥Ÿõ!ÀÇ|T­O::¥_bztšqjCÅj Å5¼!Âx8ðô}ԊՕ˜˜åε>•VŸ5ãV %ï•Ù•Ñ£˜Yš{”•Õԕ™–®âµ2¶ÅÅ5Š¡™ä«gôÁý;¡›J±WêÎYҟ…P*5,êµ+ÿ%ïe€«uïÖ¡LuÿœØ¨œe³ã%cNÕ}|›/u–Ø'Ì9Ƶ”›ôt© ÅYðv¥à÷ՎمÞÂÖëLé+¡XUá-Ž¥= µ²ƒ–/ӕ+ª¥‰¥ä–T;›IýÆ]I*µæ ršszY1 ¥„WŃºýzüÝÿʦ§Íׂ=M÷¸+ÝPЧGM…¶˜ª\¦kÑ·‹ïÜ=}Û·‘}ÀÚ9Ÿ[l(6œø®6» &'œÇiÉÆAò= Î\­æŽ)v¤ç+cÿ_'©+é£ûþ‚+FªÒ÷š À.Ö²¸8†òùg;ÿ.Åÿ$!Ã=M¶Èmýzgˆ©Îž®¼|´*è€,04×íA5ଽ¼¬ªy)2ç_]å¯?ûÀþ^ú:…cêºJÃìf÷_e‡RߍÔ¿c.Ö00)#ýC»¬@i9núÊ2gñXT.9¦sl‡¹ X“Ï Oíß¼PîŸâãïgh¦ë +ë'og/®b´úâ(Pm2DpüÇ~2ÈK‘©Ð‰!2I¾hs% r3C~wµ±=M‚µB°€ÝmÍ®iÀ2Ξc7‚ªü[å¬ÿz]&‚±Þ +:Ói"Œp»+ ¬ïXoHïû/)rsè}ÕR}{èóNs§ìƒØ/‡Ì®Ô¯bäátyԐšíP˜KvÕöÃõΨ=}MšY»¨ áÅ ¨˜äLë# +ì©MZå½Ìþ0ªWâ =M [®Vjï+vîÈm°Ž¢Þ]$㛐䶷ðŠÃÕ¤ös± OE³øQ$·…møÑ +7y×öÅ.Ç49›áº~r‹%Zë´Ý‡‰½ûðfáz38a‡ç ‹g4ªþ…¢KQäÐnˆ Púo7™PëzIè6÷¬¤URÃ?ւ0fÇ뒇’èŠ*à Ê „ÑFýŠ#F¯‹EJ“{Õ°^öp~æö‹Ë }v2YÄûñHq6²j‹ÿ)ŒÒùŽ‘“t¹T¤ÉÐ+y´‰±paf +õòˆ>ö¢‘];JfFZ“ü¨» .,iÖÎ8Š;ø°p €&‘lÚ~”ÁÀb®ŠO=}EŽWÎúc érDmnü‘¢K¤¿¯\ÿÖ^Õb§u•ÖÌ ›äÀíÁñ»Üyúª/¤IYR‹…Óu#,pÅ롇p¼”i˜“”âÚw&bݤ=M0&ð|Ü(„(ø®na#;¤­Ü®G¿à»¢g^«.èˆ|½|c5ÉK÷PO؃¯ Ž†PëZ‹Rؘ(²c1‚Q¼5Rp_XÑ¢‘CÂ~\”t#!uÜ´ÜËAâÁ¬o=}Ààn‡¿Ô$0îi:,m–$[ô¯o˜%ʙjíQBêÕ|–¾f¦jð†‹àÁá~µ4;XmΡÿ¡¸OÚp H´ø;)²ÉHëû¯*=}µðÈêjàS=}t+|î~ëdE(‘¤Îì …=MÀϙ-ÓD£»àwwBˆ~ÅýgSrI’9Qœ¨*¡Ês©Kb¶Q‡%o£ÊB?õv«Ýp¶a¿õ^ K»Lñ=}óB§©<ÂÌÃK@Fóyßñ(c_/oq Kàl,ž@ÿÙt ¼±?¢ˆ#Ó퍄ê‰.ïäÁ¤øÑÞ/q/+Œ¢FSI= ³?Փ€-æð!oŸ‚iT0´pÅ©;Ó{݁Q§»f~?à,ŸÄ}EB̰vÊA’M-ÆNNú"ÆwjÕZžº+äTh䳺ih.ò·Þ«»@ms¨MìÊ2Y¦âX„fANh}šþºµp—‰Í™IN—ÝdӎAoš!•u€\7²5ߌ’¨ŸE•é~ã˜UÀ¡Ežœ'a*‡¤Dg’>ʞ·:º™œ¦c|ä&yYE㱜#»ƒ§r#<¯.æ®=MLdw2![K΁]ú]ы2^¼}–€°ŸŽ€÷¦$ëª(0èW-Æ/ã_.¨>U…&”Ø)Î=}9 PǪ@K>͌ëZ}g•Vs§V4”ã©‰Ñ»­=MâÅx&ucèVkÌ_$xô™ìTži+ÝSî‡t¹Ä¢ƒàÄFAñX‘ùÏ=}Úàt÷=}‰ÆÉ9¤góG§˜OêâPx~ šà菉WyiLë‡>tšTª=}+{=MÄb†ï5ñ0:‘Ø>‘Kéî% ïå Ý5KèEü2um÷Vµþb@š€uþK5µãu{Ä]I¬qj¬‘ÐñE=}@uˆÌªïêiñêUj¿»”>Wä=}Í/97iàZ„Û¿ó¡é† ðª +âßÃ2ç'MãxD߇‡)QŸåGpÝ^*sßÁsÀ=MîV"ŸñªÑÞì= îV&Ÿë,è òc{¢ Àz"&h\éc!ÃÁ°ßsîWWo§Ñ÷:IöžT@,58-ÖÀ žÂ¾;Z:-x Žií¦3h ]н7J¦°´ªûâ¶û7¥Â"×ÇdªÅd—;¬åÒA• [Aì{ˆìÛÓKOŽmQcxŒâ-|zö%Ün0ñ»aúIÁ,ÞN‰b6|g±wøe¯-Ø@n™jú8{1b$´Ç—K^ý0¾8xÝ®FÌG}ê0ZåpÁûR= z.ûGôÐ(gr®ÁÞdõeU¡ó1áüÕÏ Û<Ȃç¯PÙ¯s¨gES/ߊIé‰[<÷=M¾ty¾=M!ݶ“ùÇlú^Á„2a*òÞÙܼvqÓOú}P»÷‰ÉTÿ§/1Õvn=MsúYî¼3ƒ@wmu±¼R”íOczW5ís$6eÄ…üfÁÃ"[ÝÉöp7Íz®ZqÝC]¸& + ¹þ™¬b·ŒÍÜøWž°äþ­&HÑ)λ6áÓ³W-5¥_¯†_¢Û=}šh<»S’-FÍ%U¾‘K¸•ÈP•qãat4JL’ý¨„\SëôïùSŒÓa%…Ì|ûhôr0 ªók‹7S[(tÐ7î|VHc³miOh6(Ð{êÑexhìûK +Ú=}ñ=M“^.c!vYHù¼[Ò yû=}¸ÿ,±djjœfÈFü_ïЫ#E8{ðó}m/¢’tïh%ýbm°euŸ˜ ð7ª´“Lù##ºz«L/(´#kæÈ›~0Fû3¾PIâ¿ÉV|6ÆÌr"IjI6Ûyꭇ‚*–6›‘ Õ4ŽøŠfBhX‹réCãzjÓòÿÜGCŸ–ä¼Èrǰ¼&g§øŸ»' —[»¨=}ÑÞ´\Ïý±ÐL#ÚDlí‰\E„‘ýCã¯âz½+Ê#¹[ ¹À{!Վ͌ŠÅÑÏ£¸Áÿ½´L¢öél¶Ñá¯!ƒSÖ ‚Ò~~˜. +•’£UÃ8ÔÿœvhP´Û>nE<¤œO‹¶¾CÌ K‰7ÕR¬¤‡=MÚ¤Y|ƒ‚cIPfig*û(Ca¿€WCÀ2>²f.„Gãý‚jö(+£4˜JNLå M|/)/ÿwÇàjüjîl첔m#‘L?˜ÈtK< úÁüL8= .K)‹= EËYÁéx¼Ö…•­Û¥Ú–EKp1qJpKr[KpN"pK‚®*)¶mq±YI/S„ØyÕ¤«=  ¸ù¬‘ +ÏÅÐBïöQNšŠCâå¨ÿ?ôÒ?NÏ}œjUÊ{ê7ýš»,Ö¾«¸ä¦S™«Õæ·´xæ¾%Ãî½o-³ ‘ñ/ìo¾4+¡²~Ù9£Yx|Í}j[õ’2ý]^f]HÅi„Ú9ÚÅ«c ×:¡Xk3•Iъ\CñôI±ŠV ÔÄÉCŒY  I# mð¬!¾ W:ø#mƒ¤j* ;ý;ãÀÉrFY$í}-ö$6žS"Æ[¬Åàh/M³{.~õ\=M)QeY[é +á«æ!Ÿ!)‹S·a¸z6Kæ£À©ú;æŸ@cÝ×\‚´ñü’ÚözÄmnÜmxÄmS“¸¯Às÷!zÀQT±qG¼M,•jï & {««âS#…= =}‘–ddwûxޙ"X]2 +•Ïf%(…ü,Í1Àèô•ß¼ õtB~S¦³Aoqj=MªN±4+µ r×Qa1 ò>îMÃͲ“:(v8ϖ¬Å«/ +¸fQø[^ gpg»§¥D6hÿ:)é*Üó)Ö8ʧ¾M¿ïº×]åZ4WßÒ}%¨®]íhZ±¨Çn9ÜTS@èü1—âó·ÚÇîÔ}e¥1h Xn™Ù÷x“ýy|[5€±éOxö‹ µd3ºûÞéÝFô¿» 98;˜ dú6>Á°W¤¦s&i$šÍÑæ +ZåúÂZ¦rÇQ¨=}UQº?= C›G‡ +̓ñ$wZ"Ý Ug«UJªÎ pFhN˜¡‡òíÿl¢¬ó‘Ë€Ĕ•K}¡×ú­\”"µ ©Ó„[ænÇ0 ;EG¯¼#d·Ñû£ÐZ®½KïI®¾yŨÂ0û7Ò6aɋ/CÁû:À*AÉÿ]ÛÐ$H(¡ÐN™åãg({7ïëz»h²ÑémZØ‹QI‰·´‚ùw<#åo‘á¹Y™^Çâ%S f(zW©å¿á[ÿ·YPÚ)y°Ú¾XÕ!͋èÍ2É,5(¹>;Ü'íA9½vEɆŒßh¡ÒνàÄÐúþcí#+‚+9ÓáúâׯF¼h½Gä|“ ã¡c54ÑdÇÍ0-lFáéÁGÝó%ZLÔ^;y”nmP{%L¿¤Ré& pŽñsÏzfüþ³8|&/0d1¨Ží­«õÍ6•Zx—1Ÿïô]>ƒÚÁ^Âwt2Úù>¬¨nña'ˆZ™í+¥ÓF² à=} ÂúçÑÀz/ ®¬)Ƅڮç4o|*(I1ýŒŸëâDµ‘ãšfË*´êÞv”¿þiCwãúª@p?ý€è"ê÷Êôìê8“G/ŽãÊ©$?ökN¯›¤@zÿÂXìlG…ÞÂJ+ +A?@X}õ²òiö|êd6æŠaÐód^ƒ¢›É5ÌC-¨¢1„o]»„Ûc= ¯Îqƒ<ۗ/}½÷gO»Ëqÿ×Þ>.&bQ®láðÈØtÊ8þi¹BšH®Â-´Ö“6šòXë)'ï^-e_’Bñm_Ev§+Ý=}D)EF™:觃Q&…êêßCT©JŸ¾ˆ9Ýo9}q,7qÜÛ:!LFÇÃÉ_}Ní[Ë®Lùi”+Üàc§ÄÎÈz,_îNíc1˜Ã1,(Œï# ôš:Ÿƒ½m-=}uÝ:—ІŸcÂêLü©AN6õ‘°šPžÛŸ¯žP/י[¾—÷Öê85|íVh.—júްšn™¢‚À<™x”¸­î‹+µˆ6UˆN5"uVœV{ÄK„—jôT¡a˜ò–©ÕV9KpäpKŠ"ìpKpKpËÐoØ“DGŒ\ôS©+셑5e}.׈0Ÿv¼ª‹èñ=}eû9׀Ñ•RòªW¿¾Iû;jóÈæ¨b±àÇ(sr!’ô¨S\áá®ø7Û(jáÏüy~ݸsãÛsÌ*|曱= ®MRÞd+½Ò|Š ï9x!8#¢ŒIqg†?;:Y確ÿ“4ÿ³š>ÎÜZ,ùª?¬ÚÍúª&‘G%°>ÁQôUE—3c<Áމí(\ca°)= 8,Î!…= öwÁ@úμ ?\Èno¹(x?¡q$:hLãÏÈ(h]ò,_#¾­º’(^Ò%Y· ¹·§®DßUêÑøƒZ¦óÂ#ʋF!d6‰ý_$÷s(Ýc+"ùïPOˆÐ¾!N{å³ä£vqõ§ œÆ” µ8›e>Ô? }Sgx,¤À”=MŒ$¬,Ç0œzÓM¨…q"yÐCM3 v|ü›Bw†ÁÜ/¡ +U¿l3³V 鄠lžXme/Ê0Ëïf ôk»Š6.ê»ëÚ^PÒÅC~c ØÌ›M :jñMkžúãÜ×§Ïuk7Ò¦$oÇÖ°ó[I»TÀïóýÖnvbþÃbåR×"N¥eÜ«¢Fòò46ŠMýæ,O= ÿH^P«ÀsluŒY}æ"è¬@u,ô®*íII—ÛP‡1Sï+®nú=Mî/r(ßçLêž¾n^'› ”ßc#ô‡xa ΄ ѐæ‘R(:sZ,DÈò’Fý?Ô%6êƒß ‡L¹A +þŒ’GÁ +ÖãtÓÀä2'‚\ú„W¸‡üyÖ>èEäÿ« ÓÐyó‚BGˆÛ.2áóNï«¡ñ¶üX,߀ã ùZf24Â|b%PnèútÞÇD=M> ÿI)1Í×ÇXßÝ֏ãÔnÖ?Â'2kÎ?=M !,…š¦‡LÛ%¤‹+Y$Ãw§¼ª”¢É<ÌK€ý‹°pKnm“;>pK0ù‹?oûÿ¼ +C´rzÔF¯Bí(ZýKõ.ž= hú(7in"OVA=Míô»+ ørçõ¸7Ï‘³ô™XO|×ßoÂV<R5ççͦPÜ-·TóíI +65ÇÚ}p)º)À›Ÿ .:E:ýeF@ e¹òJ¢fóú°thr‹‘‹ï°6Ñ9Ûj::K„xYÏTîp€ +³lÊÜ@ßo›ÏgGDáÿÒ«ÃRÄÄ)C0ÃÎ Ã&¯·ËÞh#‚,×àgUI»þJ ìÏp*)³t¿º„”yæ:YhÉ…KH’<96qáÖ(„n=}d˜šÈsƒ­¸päèuØëM†ÇØôSyÙŤ#wC»….6~‘c8±ÓÆK-,ÓBy֐?оqç›d$4b +ëbxcl¬‡= ßàŽö€ª‘{ÿóM ²¬Ãy K…—2²‘?ŒƒóI$Ôí +%,‡¸mHxQ{´Êq™|Š+¬áâõd1Sí…6´P¢Õ†+]¥ã¡ÖWs6…„DZHÜÕ +P¯Ã“é\º¼â=}Ãi×zØ}}}7³O±eZÌ˜á®øK—øaÔý—ùŸúÉ\íuo'&ýÿ­À8†UýÁs"XD¾xfúTº)„ïÖSh¯Â¯KMµõê—„ÙWPj ÝÐÿ$ ,±…l Ü¡v™º¬1€„¥âÌ±È \ˆf³MúzN¬é^°ãaœ"ÄS#ŠØ.Ô_ —uÆÍ¥â?֍ŠÐìóšXïä÷‘OÅA>£\ÅÝ©"’D½O§"º0øeTÍ«ïÃ=}~¬$äw¶äUcAٙ"áAʞœ(v3*ì>’ͪ¨¬:6W¾²T:xž}ƒ.èB®=MGѨÞkî…%¢ºn0)òÎMäNm¸¬L¨‡¥ŽÊ¤kªŽ](ÄÕ|!—Ñ~ëÓŸFáJ®èÅT8ŽZ%ó«x×垛4¾V¶(çÁóçoÔ:Å¿X’oÇñ³ñ#Úz½¿Nš ‡¿¥ ?—"rÀæ±çmJ*-‰Q§dI?å§ôšlJê…RèrúåE(vÕC¡ k^EHæ´ÂÇ©¢§öýÂ7 x§¦: Ú‘s¶÷,¹÷“ê¦ð4‰óÙ_ ¦o”J”«†“´·RÂgKl·.ËéNÖ9:.·*.ùgÁ8úibéÛ O—Ñ?]B6'¡¿~l;Ÿ'“æã¨’= ^Ú®>r¿ºêZçüñ×ÃÃZI¼97Ÿ^ÇIòˆ>4þ³¢ì^: +ŒGþ€OÁG ]¹îL¡_Í)ãØhýnˆ©ÿâ介Ë&ûܺe ):´àGýÇM­ÞèÛå5ð­â6n€ÝØŸ5w€IŽ&¤ÞäQ}vò¤luÛ*ò]3¨n¼Îe*& ‰†<ÕÜ£p%Ø/ª‚‚Ô34±ØhÜ&v¯å÷ß^²ÛÉKqËj: Fo ?ó‹­=}fcÞX}k‚Vy6Ê%!gfw‚Rþ= H³éfD°în>åü·R.sáò“V·ˆ“Ez TÆ2‚4-ˆ6Ìa‹…{J€¶@î0ÊëñúÑ9&ÒÊÍ= ð€m†X\í{–ëBK ÂÿmaŸÇkGázz‘/šAs=M«ÐùÇ@d¾ü/®îAäé±È ¡ù2¯Ãáx<ºhÐaKaåø;Cí}Ôyî w¼v! Ø=Meˆ…¹ BeS'åL\ìݳÓéínJfkýP"‚~gÐÊ;ã +6 0€Q?Š:ºR7:–³È::ÂçëÂ,IgƒàÓrSàb“_qÚ“€C»top1HLzÞ¨qNŒA¥Ø·Œp0= 2Œì†³èŽOnQz·s xŸ3·T3¸ûÀT ýÌä”\ց t0]Nff‘vH»Sê“Ï ³,¬.Ó{Â80FZÐ߀ 4pŠœô’;"t!':2ѳ#åW´¿=}„ô ·½A¤‘›Š\z@˜F<C.¡\e€ÿ]_kYš²±ôAWö=}ô!Z’båÐj—ôÛvMt홄¯=d{ À=MÍp·¯X=Mmö‹¬œ™£ÛfÍìхÜÛ¼\xÄ-LÔë" YvV]”©ˆÊöí¿ UÊ= ½¹Q zím.[¸Šé—kM¼vߨÇӝ>ýs&‚?¨5´,5Ò.å”1r±šy÷ ö³ç|yØñîÌ@ F ¦ù¨ FÁ6™LAßöCé +Šú­¹À\§XŸ‰ý?ÕºT@Yu‰¿E\êE&ö«®Ã’)V0íèÃwm¦ÀD ‘½wÝP”¥þÞHÝë Þܹ= ÞÇÑéÚ Q'!{ìèOêèß*ed†Ú –>=}{æ= Vø&Þ ;:(Žv©ŸÑ3Ýè«\&$ðÉÎtž¼«×[¨˜½B¤ZÁˆ5à®ÃÉÖb“¨]ãbù"6æaþ³¸Eã,=}åGáæ‘Z–iXLÌ:2ß®r©^ct†Bëkg/´E\DE®žü-d8a½ý¼ˆôT +ó.dGóâ÷ì¯ÈíWûvÌ3rË1J™ öžþìßNêãP0ÅàB‚L'%o¹6:C‹é7)¯RòÄÒ'ypê=M=}b\Q¦«hÅC|(dÿâxȪ¬ˆÆìlˆp*}ƒ¼ŽeˆÑG“ÏT‘ŽÆŒ‡#lôV¬1)ó.§ ÉPøƒÓÝf°/ví礼ó~VxÆ×ïµbíá KÛ|ú#Æ#=}ÀÚjÿäáî~ ŠÕi¤ÚwFñå ÚñÑ*¼vóÛ¿)\㏂၉aVªÝ=}¾ÕxV·¡)*u<ï®É,)¸šWp¿f‹SŸÒŒ= å}·7lÖ¦ìÓ \”Öƒő¤˜“ZŸNÿžXž¡#P«Èdõißêv§µ‘UUZ½b=}£,Öêæ¢)”•ëXZý]ý\}V­U*Ö=}×ØÛ¥I˜W[=M[É[±Û-¡×š™öÑ¥«ÆëÅkÆËÃðîW]RÚ2Ý5VÄøD97;C>:B8@RöïۈGCKÁ@Ž/k3KÊ<ހý´-*^Q5?0B_û;H,‚aúGÏn¬[7œÖyN-V°™OXÕ ¥= ³›.–Ùæ]š«÷–´eU˜Ëu•±õ)>­VòßNWçAu ï=MW«1µ*¸Q»»…)ßE§°£óE˜¬]y™4¯$Ó&dÎ{ø£„å\Òkö¤†ø-^­WâNY÷Áv¨/=MY»±¶.ØQþÏ[…%CF±Ë3F¢üÝ|­´/"¾Q9/ðA[«6 ìWªGÎZ,[;¼Vy ;N.^0i <ÊnÞ¯÷›§âö¬ï-ÝØ¤o%¹ 5f;û¹Œ½‚ßàH'Äþ~Þð'i#‰3..]È¿Pf_yÜÏf#ñ¹H¨û§ã|/4NQàòŒï,RYLDv»ü½L½ƒç H)äþæ0)y£Š+~®= ¸_Pk‡AzðokK1ºRøù ;‡JÏðŽ*CóJÀì^ƒéôŽlØøïdt0= ©eØ5ñ楚Um^Lp-ÎFp³€¶Kp‹‚#pK‚BââŒ!;7)ƒ‡³p¾ªä㜠9ú¿p˪€¢¿qs¤ªtÇéYCôµñô¾Ý€Gð‘/65púF¼Üöðfy!!ªÀg«Ç T:§ï¼œèLϧӊÝ5®p±ÅÐ,]r’?f’ 1q-ßEöe„£)”_Ïúf%µÀ{ÍމQ?šDœ¤¶à¼ÈItÜæ;^™èd)Á®æw gî¢Eüb8WҊmÓžADX3<@=}ó×é]qݶ75I2aõ= ãÖœŒ\0˜‰¹8¬²–#H° T…K‡lUÏͪ|X~ÓJ‘´$½ê˜„9ÕÊ6¿W’׍qŽ_=}O¾!TAAq&Ñnﳤ#ÜHeÿ¦æÐ#\”aX؞±O¾œšH(×PÁ î)_´ù‘úӝ•›÷­›Ç 6 ïCPÔÑÉ SüÁH»üä‹#ÛIÛÈox¦D†!ŒùÝ+^)(Æî¼Gä .ã~˲h +Ï<Å ö§ËX½Aîm¢XEÑáX‹Å£€Xс4öϵ••™¥ëaTé®­hµÂ;Þoá8I/ûþÌ¢h/;‚Éã€þ3‹øµ«[Ù!&í®ÈFÈ÷þáZk­ JƬøÅ«\ù#fí²HFÐøá\‹­$ŠÆ´ˆÕó-¼‡[Ñ'ìcÈ|Ñ),cè€Ñ+lc„Ñ-¬d(ˆÉDÓd‹¾;¤WŸ5¥›֕ÛfÊr’x¢'@n£tøl”3òDülnJ³Pkülo4ò|ölŽm +ó 24òŒöl’m +ô r4òˆõlmÊÄ b$òˆùlnÊ b4òEÀ´ zZC‘m*¼P‡úìs_4òõìÔmj¬ Šò÷ìTmjÌ Š$òùìÔnjì Š,òûìTnj Š4ò³mzòómzò3mz òsmz$ò³nz(òónŠ‚$cQkQl¯kG;J‚öÄop®1Z.1b®2j.2r²qZ2qb²rj2rr¡aWáa[!a_aac¡bgábk!boabó›G%ù¹À.^ãG)>úÁ.= óœ Ö¼ ØÜ Úü Ü Þ< à\ â| äœ +æ¼ +èÜ +êü +ì +î< +ð\ +ò| +4–«-õ¦ë-ö¶+-÷ Æk-ø$Ö«.ù(æë.úŒîMøÀ•ö­›7¡ûr“ø&w,ѵK ì|¬óӄdŒˆ”1Ì|dQ.Ԑan̐¼ä‡º‡<$‰(SgôáT~В܍ìü \¦ôwäô†Â“’Ák–¢•µ¡YÕ1꙯†Aú^mô,a{óTN|q ƒÒŒ4Òù´nà‹(Òúônð‹,Òû4n‹0Òütn‹´¤wØQ´†¹SY0¨$wèQô†ÁSZP¬¤xøQ!4†ÉS[p°$xQ#t†ÑS\4³9¤~ڏ&39Ä~â(³:ä~ê*3:~ò,³;$~ú.3;D~|tLÔI³ÈA ôÿ"/a#¥b{Peˆ'ŠLs¢ã½Ës㌐ç òú~›ÐZÂsêGR³‹B­r lú°ƒ¡ åJ~£q$—Ê-xÑdÝe\¯lÏ?U­p±‹.å«M] ç¡EV"ã¿ÆVäß9£àñ{ª®ÍjBsèö³†2íÒA:Ìy­vá Ý"®c‡4™þÉlŽ3*=}DJCá6FKPl|WIï}÷ÄßÊeÛÌð> el7 ïD_ÉiþˆSß肌=}„Z~þ:@Q¾lÝ0rzøJûÏp(‹ ‹ƒnh®¥ +Y “œ”ŽvýÕé}AvÔp#šÙ‚¢áèü%îWzþÈ_·†_‰Ç^¿VGÉ®û3ýÚO!Há'¿’áHã Š›0Â×S 8ÂkâNs»ðÂÙKûXb¬8í xÂ,AïhB¬½Ø¶{é5X­à¹=}e\©_f÷š÷*}öªçi­õ¢ùÿÉiñæŸ +¯>Ù*/ÿBÅNï"Iå1çÒâIç?ï˜bIf»n¨sª¥þ“¤Ü²Ï†Ïð«”JcvtZVýþiž«Äà.»v'y>g»Õ! Z}»ðNû¦'z@kÃåA¡º¯ß%8× Ý¾Ÿi}¥¹ã;ç"W¹_ê>ê)éÿ +€XCµ«‰äWx¼Àx³õyŸW••dnKpˆ«ŠƒnK°PJpÛåpëíP"?ŽôrㇺT›FÜã8š’W´€Ñ˜6(Yó÷ÒF1-äáºùY×÷ËÆ2-‘¸4Þá‘Æ˜Žq´’O”ÆÄs%h=MÀd~ú‹H-!ÓáŏGÙîwÁæM^BfJV +]]/sÇbBpÉá ã™$›¤š"£¡¡ÚÊY{†gþ>Q ë»P½7_TbJX¾tØy4½ßäx<…çxÇMnQóJøHH’©Rþh¢+CçtCݎEËÊ(‚V"éž@µ;%,³,^b©:<ÝGín!'g]G]{Uãdžö0ü6ìéÛ ùN:ð"ì(R2,Ž%Z-a‹§rZ€JK>´åÒ?QÚOf‹/M©ç÷»À"0_^w¬_XÀ°˜°×א˜ñüp^AŸ±ƒƒ‚A¿$ïlûdãpË\ëPóP¦>f¡(N b{0DËo9PìDÂ>#† ?,%)-*åˆÍ÷º<Ó9ºEÐEºEÎùiqú&‚É@Ð6À׺©×—W.Õ©¯>9%6Ì£ö ævMàLzÉêJ ¢!áábÂÁBA‚‚²±²21 Hk ÜãX‰ˆÜ¸Íœ¾€ôZ•÷O[ÑßoK¿NÌ~§ÁÈ£õq3ۈèr^¾ zÉñôD;äF®–YåÈv}çì|D[Y¿®Œ*ë>exÿ¨ÜÅ­ÿˆ3÷þj½¢w´Mz31Û41&j¨k|sß¡µoKÜ { + WASMAudioDecoderCommon.setModule(EmscriptenWASM, data); + }; + + this.getModule = () => + WASMAudioDecoderCommon.getModule(EmscriptenWASM); + + this.instantiate = () => { + this.getModule().then((wasm) => WebAssembly.instantiate(wasm, imports)).then((instance) => { + var asm = instance.exports; + _opus_frame_decoder_create = asm["g"]; + _malloc = asm["h"]; + _opus_frame_decode_float_deinterleaved = asm["i"]; + _opus_frame_decoder_destroy = asm["j"]; + _free = asm["k"]; + asm["l"]; + wasmMemory = asm["e"]; + updateGlobalBufferAndViews(wasmMemory.buffer); + initRuntime(asm); + ready(); + }); + + this.ready = new Promise(resolve => { + ready = resolve; + }).then(() => { + this.HEAP = buffer; + this._malloc = _malloc; + this._free = _free; + this._opus_frame_decoder_create = _opus_frame_decoder_create; + this._opus_frame_decode_float_deinterleaved = _opus_frame_decode_float_deinterleaved; + this._opus_frame_decoder_destroy = _opus_frame_decoder_destroy; + }); + return this; + };} + + function OpusDecoder(options = {}) { + // static properties + if (!OpusDecoder.errors) { + // prettier-ignore + Object.defineProperties(OpusDecoder, { + errors: { + value: new Map([ + [-1, "OPUS_BAD_ARG: One or more invalid/out of range arguments"], + [-2, "OPUS_BUFFER_TOO_SMALL: Not enough bytes allocated in the buffer"], + [-3, "OPUS_INTERNAL_ERROR: An internal error was detected"], + [-4, "OPUS_INVALID_PACKET: The compressed data passed is corrupted"], + [-5, "OPUS_UNIMPLEMENTED: Invalid/unsupported request number"], + [-6, "OPUS_INVALID_STATE: An encoder or decoder structure is invalid or already freed"], + [-7, "OPUS_ALLOC_FAIL: Memory allocation has failed"], + ]), + }, + }); + } + + // injects dependencies when running as a web worker + // async + this._init = () => + new this._WASMAudioDecoderCommon(this).instantiate().then((common) => { + this._common = common; + + const mapping = this._common.allocateTypedArray( + this._channels, + Uint8Array + ); + + mapping.buf.set(this._channelMappingTable); + + this._decoder = this._common.wasm._opus_frame_decoder_create( + this._channels, + this._streamCount, + this._coupledStreamCount, + mapping.ptr, + this._preSkip, + this._forceStereo + ); + }); + + Object.defineProperty(this, "ready", { + enumerable: true, + get: () => this._ready, + }); + + // async + this.reset = () => { + this.free(); + return this._init(); + }; + + this.free = () => { + this._common.wasm._opus_frame_decoder_destroy(this._decoder); + + this._common.free(); + }; + + this._decode = (opusFrame) => { + if (!(opusFrame instanceof Uint8Array)) + throw Error( + "Data to decode must be Uint8Array. Instead got " + typeof opusFrame + ); + + this._input.buf.set(opusFrame); + + let samplesDecoded = + this._common.wasm._opus_frame_decode_float_deinterleaved( + this._decoder, + this._input.ptr, + opusFrame.length, + this._output.ptr + ); + + let error; + + if (samplesDecoded < 0) { + error = + "libopus " + + samplesDecoded + + " " + + (OpusDecoder.errors.get(samplesDecoded) || "Unknown Error"); + + console.error(error); + samplesDecoded = 0; + } + + return { + outputBuffer: this._common.getOutputChannels( + this._output.buf, + this._outputChannels, + samplesDecoded + ), + samplesDecoded: samplesDecoded, + error: error, + }; + }; + + this.decodeFrame = (opusFrame) => { + let errors = []; + + const decoded = this._decode(opusFrame); + + if (decoded.error) + this._common.addError(errors, decoded.error, opusFrame.length); + + this._frameNumber++; + this._inputBytes += opusFrame.length; + this._outputSamples += decoded.samplesDecoded; + + return this._WASMAudioDecoderCommon.getDecodedAudioMultiChannel( + errors, + [decoded.outputBuffer], + this._outputChannels, + decoded.samplesDecoded, + 48000 + ); + }; + + this.decodeFrames = (opusFrames) => { + let outputBuffers = [], + errors = [], + samplesDecoded = 0, + i = 0; + + while (i < opusFrames.length) { + const opusFrame = opusFrames[i++]; + const decoded = this._decode(opusFrame); + + outputBuffers.push(decoded.outputBuffer); + samplesDecoded += decoded.samplesDecoded; + + if (decoded.error) + this._common.addError(errors, decoded.error, opusFrame.length); + + this._frameNumber++; + this._inputBytes += opusFrame.length; + this._outputSamples += decoded.samplesDecoded; + } + + return this._WASMAudioDecoderCommon.getDecodedAudioMultiChannel( + errors, + outputBuffers, + this._outputChannels, + samplesDecoded, + 48000 + ); + }; + + // injects dependencies when running as a web worker + this._isWebWorker = OpusDecoder.isWebWorker; + this._WASMAudioDecoderCommon = + OpusDecoder.WASMAudioDecoderCommon || WASMAudioDecoderCommon; + this._EmscriptenWASM = OpusDecoder.EmscriptenWASM || EmscriptenWASM; + this._module = OpusDecoder.module; + + const MAX_FORCE_STEREO_CHANNELS = 8; + const isNumber = (param) => typeof param === "number"; + + const channels = options.channels; + const streamCount = options.streamCount; + const coupledStreamCount = options.coupledStreamCount; + const channelMappingTable = options.channelMappingTable; + const preSkip = options.preSkip; + const forceStereo = options.forceStereo ? 1 : 0; + + // channel mapping family >= 1 + if ( + channels > 2 && + (!isNumber(streamCount) || + !isNumber(coupledStreamCount) || + !Array.isArray(channelMappingTable)) + ) { + throw new Error("Invalid Opus Decoder Options for multichannel decoding."); + } + + // channel mapping family 0 + this._channels = isNumber(channels) ? channels : 2; + this._streamCount = isNumber(streamCount) ? streamCount : 1; + this._coupledStreamCount = isNumber(coupledStreamCount) + ? coupledStreamCount + : this._channels - 1; + this._channelMappingTable = + channelMappingTable || (this._channels === 2 ? [0, 1] : [0]); + this._preSkip = preSkip || 0; + + this._forceStereo = + channels <= MAX_FORCE_STEREO_CHANNELS && channels != 2 ? forceStereo : 0; + + this._inputSize = 32000 * 0.12 * this._channels; // 256kbs per channel + this._outputChannelSize = 120 * 48; + this._outputChannels = this._forceStereo ? 2 : this._channels; + + this._ready = this._init(); + + return this; + } + + class OpusDecoderWebWorker extends WASMAudioDecoderWorker { + constructor(options) { + super(options, "opus-decoder", OpusDecoder, EmscriptenWASM); + } + + async decodeFrame(data) { + return this._postToDecoder("decodeFrame", data); + } + + async decodeFrames(data) { + return this._postToDecoder("decodeFrames", data); + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + const getCrcTable = (crcTable, crcInitialValueFunction, crcFunction) => { + for (let byte = 0; byte < crcTable.length; byte++) { + let crc = crcInitialValueFunction(byte); + + for (let bit = 8; bit > 0; bit--) crc = crcFunction(crc); + + crcTable[byte] = crc; + } + return crcTable; + }; + + const crc8Table = getCrcTable( + new Uint8Array(256), + (b) => b, + (crc) => (crc & 0x80 ? 0x07 ^ (crc << 1) : crc << 1) + ); + + const flacCrc16Table = [ + getCrcTable( + new Uint16Array(256), + (b) => b << 8, + (crc) => (crc << 1) ^ (crc & (1 << 15) ? 0x8005 : 0) + ), + ]; + + const crc32Table = [ + getCrcTable( + new Uint32Array(256), + (b) => b, + (crc) => (crc >>> 1) ^ ((crc & 1) * 0xedb88320) + ), + ]; + + // build crc tables + for (let i = 0; i < 15; i++) { + flacCrc16Table.push(new Uint16Array(256)); + crc32Table.push(new Uint32Array(256)); + + for (let j = 0; j <= 0xff; j++) { + flacCrc16Table[i + 1][j] = + flacCrc16Table[0][flacCrc16Table[i][j] >>> 8] ^ + (flacCrc16Table[i][j] << 8); + + crc32Table[i + 1][j] = + (crc32Table[i][j] >>> 8) ^ crc32Table[0][crc32Table[i][j] & 0xff]; + } + } + + const crc8 = (data) => { + let crc = 0; + const dataLength = data.length; + + for (let i = 0; i !== dataLength; i++) crc = crc8Table[crc ^ data[i]]; + + return crc; + }; + + const flacCrc16 = (data) => { + const dataLength = data.length; + const crcChunkSize = dataLength - 16; + let crc = 0; + let i = 0; + + while (i <= crcChunkSize) { + crc ^= (data[i++] << 8) | data[i++]; + crc = + flacCrc16Table[15][crc >> 8] ^ + flacCrc16Table[14][crc & 0xff] ^ + flacCrc16Table[13][data[i++]] ^ + flacCrc16Table[12][data[i++]] ^ + flacCrc16Table[11][data[i++]] ^ + flacCrc16Table[10][data[i++]] ^ + flacCrc16Table[9][data[i++]] ^ + flacCrc16Table[8][data[i++]] ^ + flacCrc16Table[7][data[i++]] ^ + flacCrc16Table[6][data[i++]] ^ + flacCrc16Table[5][data[i++]] ^ + flacCrc16Table[4][data[i++]] ^ + flacCrc16Table[3][data[i++]] ^ + flacCrc16Table[2][data[i++]] ^ + flacCrc16Table[1][data[i++]] ^ + flacCrc16Table[0][data[i++]]; + } + + while (i !== dataLength) + crc = ((crc & 0xff) << 8) ^ flacCrc16Table[0][(crc >> 8) ^ data[i++]]; + + return crc; + }; + + const crc32 = (data) => { + const dataLength = data.length; + const crcChunkSize = dataLength - 16; + let crc = 0; + let i = 0; + + while (i <= crcChunkSize) + crc = + crc32Table[15][(data[i++] ^ crc) & 0xff] ^ + crc32Table[14][(data[i++] ^ (crc >>> 8)) & 0xff] ^ + crc32Table[13][(data[i++] ^ (crc >>> 16)) & 0xff] ^ + crc32Table[12][data[i++] ^ (crc >>> 24)] ^ + crc32Table[11][data[i++]] ^ + crc32Table[10][data[i++]] ^ + crc32Table[9][data[i++]] ^ + crc32Table[8][data[i++]] ^ + crc32Table[7][data[i++]] ^ + crc32Table[6][data[i++]] ^ + crc32Table[5][data[i++]] ^ + crc32Table[4][data[i++]] ^ + crc32Table[3][data[i++]] ^ + crc32Table[2][data[i++]] ^ + crc32Table[1][data[i++]] ^ + crc32Table[0][data[i++]]; + + while (i !== dataLength) + crc = crc32Table[0][(crc ^ data[i++]) & 0xff] ^ (crc >>> 8); + + return crc ^ -1; + }; + + const concatBuffers = (...buffers) => { + const buffer = new Uint8Array( + buffers.reduce((acc, buf) => acc + buf.length, 0) + ); + + buffers.reduce((offset, buf) => { + buffer.set(buf, offset); + return offset + buf.length; + }, 0); + + return buffer; + }; + + const bytesToString = (bytes) => String.fromCharCode(...bytes); + + // prettier-ignore + const reverseTable = [0x0,0x8,0x4,0xc,0x2,0xa,0x6,0xe,0x1,0x9,0x5,0xd,0x3,0xb,0x7,0xf]; + const reverse = (val) => + (reverseTable[val & 0b1111] << 4) | reverseTable[val >> 4]; + + class BitReader { + constructor(data) { + this._data = data; + this._pos = data.length * 8; + } + + set position(position) { + this._pos = position; + } + + get position() { + return this._pos; + } + + read(bits) { + const byte = Math.floor(this._pos / 8); + const bit = this._pos % 8; + this._pos -= bits; + + const window = + (reverse(this._data[byte - 1]) << 8) + reverse(this._data[byte]); + + return (window >> (7 - bit)) & 0xff; + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class HeaderCache { + constructor(onCodecUpdate) { + this._onCodecUpdate = onCodecUpdate; + this.reset(); + } + + enable() { + this._isEnabled = true; + } + + reset() { + this._headerCache = new Map(); + this._codecUpdateData = new WeakMap(); + this._codecShouldUpdate = false; + this._bitrate = null; + this._isEnabled = false; + } + + checkCodecUpdate(bitrate, totalDuration) { + if (this._onCodecUpdate) { + if (this._bitrate !== bitrate) { + this._bitrate = bitrate; + this._codecShouldUpdate = true; + } + + // only update if codec data is available + const codecData = this._codecUpdateData.get( + this._headerCache.get(this._currentHeader) + ); + + if (this._codecShouldUpdate && codecData) { + this._onCodecUpdate( + { + bitrate, + ...codecData, + }, + totalDuration + ); + } + + this._codecShouldUpdate = false; + } + } + + updateCurrentHeader(key) { + if (this._onCodecUpdate && key !== this._currentHeader) { + this._codecShouldUpdate = true; + this._currentHeader = key; + } + } + + getHeader(key) { + const header = this._headerCache.get(key); + + if (header) { + this.updateCurrentHeader(key); + } + + return header; + } + + setHeader(key, header, codecUpdateFields) { + if (this._isEnabled) { + this.updateCurrentHeader(key); + + this._headerCache.set(key, header); + this._codecUpdateData.set(header, codecUpdateFields); + } + } + } + + const headerStore = new WeakMap(); + const frameStore = new WeakMap(); + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + /** + * @abstract + * @description Abstract class containing methods for parsing codec frames + */ + class Parser { + constructor(codecParser, headerCache) { + this._codecParser = codecParser; + this._headerCache = headerCache; + } + + *syncFrame() { + let frame; + + do { + frame = yield* this.Frame.getFrame( + this._codecParser, + this._headerCache, + 0 + ); + if (frame) return frame; + this._codecParser.incrementRawData(1); // increment to continue syncing + } while (true); + } + + /** + * @description Searches for Frames within bytes containing a sequence of known codec frames. + * @param {boolean} ignoreNextFrame Set to true to return frames even if the next frame may not exist at the expected location + * @returns {Frame} + */ + *fixedLengthFrameSync(ignoreNextFrame) { + let frame = yield* this.syncFrame(); + const frameLength = frameStore.get(frame).length; + + if ( + ignoreNextFrame || + this._codecParser._flushing || + // check if there is a frame right after this one + (yield* this.Header.getHeader( + this._codecParser, + this._headerCache, + frameLength + )) + ) { + this._headerCache.enable(); // start caching when synced + + this._codecParser.incrementRawData(frameLength); // increment to the next frame + this._codecParser.mapFrameStats(frame); + return frame; + } + + this._codecParser.logWarning( + `Missing frame frame at ${frameLength} bytes from current position.`, + "Dropping current frame and trying again." + ); + this._headerCache.reset(); // frame is invalid and must re-sync and clear cache + this._codecParser.incrementRawData(1); // increment to invalidate the current frame + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + /** + * @abstract + */ + class Frame { + constructor(header, data) { + frameStore.set(this, { header }); + + this.data = data; + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class CodecFrame extends Frame { + static *getFrame(Header, Frame, codecParser, headerCache, readOffset) { + const header = yield* Header.getHeader( + codecParser, + headerCache, + readOffset + ); + + if (header) { + const frameLength = headerStore.get(header).frameLength; + const samples = headerStore.get(header).samples; + + const frame = (yield* codecParser.readRawData( + frameLength, + readOffset + )).subarray(0, frameLength); + + return new Frame(header, frame, samples); + } else { + return null; + } + } + + constructor(header, data, samples) { + super(header, data); + + this.header = header; + this.samples = samples; + this.duration = (samples / header.sampleRate) * 1000; + this.frameNumber = null; + this.totalBytesOut = null; + this.totalSamples = null; + this.totalDuration = null; + + frameStore.get(this).length = data.length; + } + } + + const reserved = "reserved"; + const bad = "bad"; + const free = "free"; + const none = "none"; + const sixteenBitCRC = "16bit CRC"; + + // channel mappings + const mappingJoin = ", "; + + const front = "front"; + const side = "side"; + const rear = "rear"; + const left = "left"; + const center = "center"; + const right = "right"; + + // prettier-ignore + /* + [ + [ + "left, right", + "left, right, center", + "left, center, right", + "center, left, right", + "center" + ], + [ + "front left, front right", + "front left, front right, front center", + "front left, front center, front right", + "front center, front left, front right", + "front center" + ], + [ + "side left, side right", + "side left, side right, side center", + "side left, side center, side right", + "side center, side left, side right", + "side center" + ], + [ + "rear left, rear right", + "rear left, rear right, rear center", + "rear left, rear center, rear right", + "rear center, rear left, rear right", + "rear center" + ] + ] + */ + const channelMappings = + [ + "", + front + " ", + side + " ", + rear + " " + ].map((x) => + [ + [left, right], + [left, right, center], + [left, center, right], + [center, left, right], + [center], + ].flatMap((y) => y.map((z) => x + z).join(mappingJoin)) + ); + + const lfe = "LFE"; + const monophonic = "monophonic (mono)"; + const stereo = "stereo"; + const surround = "surround"; + + const channels = [ + monophonic, + stereo, + `linear ${surround}`, + "quadraphonic", + `5.0 ${surround}`, + `5.1 ${surround}`, + `6.1 ${surround}`, + `7.1 ${surround}`, + ]; + + const getChannelMapping = (channelCount, ...mappings) => + `${channels[channelCount - 1]} (${mappings.join(mappingJoin)})`; + + // prettier-ignore + const vorbisOpusChannelMapping = [ + monophonic, + getChannelMapping(2,channelMappings[0][0]), + getChannelMapping(3,channelMappings[0][2]), + getChannelMapping(4,channelMappings[1][0],channelMappings[3][0]), + getChannelMapping(5,channelMappings[1][2],channelMappings[3][0]), + getChannelMapping(6,channelMappings[1][2],channelMappings[3][0],lfe), + getChannelMapping(7,channelMappings[1][2],channelMappings[2][0],channelMappings[3][4],lfe), + getChannelMapping(8,channelMappings[1][2],channelMappings[2][0],channelMappings[3][0],lfe), + ]; + + // sampleRates + const rate192000 = 192000; + const rate176400 = 176400; + const rate96000 = 96000; + const rate88200 = 88200; + const rate64000 = 64000; + const rate48000 = 48000; + const rate44100 = 44100; + const rate32000 = 32000; + const rate24000 = 24000; + const rate22050 = 22050; + const rate16000 = 16000; + const rate12000 = 12000; + const rate11025 = 11025; + const rate8000 = 8000; + const rate7350 = 7350; + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + // https://id3.org/Developer%20Information + + class ID3v2 { + static *getID3v2Header(codecParser, headerCache, readOffset) { + const header = { headerLength: 10 }; + + let data = yield* codecParser.readRawData(3, readOffset); + // Byte (0-2 of 9) + // ID3 + if (data[0] !== 0x49 || data[1] !== 0x44 || data[2] !== 0x33) return null; + + data = yield* codecParser.readRawData(header.headerLength, readOffset); + + // Byte (3-4 of 9) + // * `BBBBBBBB|........`: Major version + // * `........|BBBBBBBB`: Minor version + header.version = `id3v2.${data[3]}.${data[4]}`; + + // Byte (5 of 9) + // * `....0000.: Zeros (flags not implemented yet) + if (data[5] & 0b00001111) return null; + + // Byte (5 of 9) + // * `CDEF0000`: Flags + // * `C.......`: Unsynchronisation (indicates whether or not unsynchronisation is used) + // * `.D......`: Extended header (indicates whether or not the header is followed by an extended header) + // * `..E.....`: Experimental indicator (indicates whether or not the tag is in an experimental stage) + // * `...F....`: Footer present (indicates that a footer is present at the very end of the tag) + header.unsynchronizationFlag = Boolean(data[5] & 0b10000000); + header.extendedHeaderFlag = Boolean(data[5] & 0b01000000); + header.experimentalFlag = Boolean(data[5] & 0b00100000); + header.footerPresent = Boolean(data[5] & 0b00010000); + + // Byte (6-9 of 9) + // * `0.......|0.......|0.......|0.......`: Zeros + if ( + data[6] & 0b10000000 || + data[7] & 0b10000000 || + data[8] & 0b10000000 || + data[9] & 0b10000000 + ) + return null; + + // Byte (6-9 of 9) + // * `.FFFFFFF|.FFFFFFF|.FFFFFFF|.FFFFFFF`: Tag Length + // The ID3v2 tag size is encoded with four bytes where the most significant bit (bit 7) + // is set to zero in every byte, making a total of 28 bits. The zeroed bits are ignored, + // so a 257 bytes long tag is represented as $00 00 02 01. + header.dataLength = + (data[6] << 21) | (data[7] << 14) | (data[8] << 7) | data[9]; + + header.length = header.headerLength + header.dataLength; + + return new ID3v2(header); + } + + constructor(header) { + this.version = header.version; + this.unsynchronizationFlag = header.unsynchronizationFlag; + this.extendedHeaderFlag = header.extendedHeaderFlag; + this.experimentalFlag = header.experimentalFlag; + this.footerPresent = header.footerPresent; + this.length = header.length; + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class CodecHeader { + /** + * @private + */ + constructor(header) { + headerStore.set(this, header); + + this.bitDepth = header.bitDepth; + this.bitrate = null; // set during frame mapping + this.channels = header.channels; + this.channelMode = header.channelMode; + this.sampleRate = header.sampleRate; + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + // http://www.mp3-tech.org/programmer/frame_header.html + + const bitrateMatrix = { + // bits | V1,L1 | V1,L2 | V1,L3 | V2,L1 | V2,L2 & L3 + 0b00000000: [free, free, free, free, free], + 0b00010000: [32, 32, 32, 32, 8], + // 0b00100000: [64, 48, 40, 48, 16,], + // 0b00110000: [96, 56, 48, 56, 24,], + // 0b01000000: [128, 64, 56, 64, 32,], + // 0b01010000: [160, 80, 64, 80, 40,], + // 0b01100000: [192, 96, 80, 96, 48,], + // 0b01110000: [224, 112, 96, 112, 56,], + // 0b10000000: [256, 128, 112, 128, 64,], + // 0b10010000: [288, 160, 128, 144, 80,], + // 0b10100000: [320, 192, 160, 160, 96,], + // 0b10110000: [352, 224, 192, 176, 112,], + // 0b11000000: [384, 256, 224, 192, 128,], + // 0b11010000: [416, 320, 256, 224, 144,], + // 0b11100000: [448, 384, 320, 256, 160,], + 0b11110000: [bad, bad, bad, bad, bad], + }; + + const calcBitrate = (idx, interval, intervalOffset) => + 8 * + (((idx + intervalOffset) % interval) + interval) * + (1 << ((idx + intervalOffset) / interval)) - + 8 * interval * ((interval / 8) | 0); + + // generate bitrate matrix + for (let i = 2; i < 15; i++) + bitrateMatrix[i << 4] = [ + i * 32, // V1,L1 + calcBitrate(i, 4, 0), // V1,L2 + calcBitrate(i, 4, -1), // V1,L3 + calcBitrate(i, 8, 4), // V2,L1 + calcBitrate(i, 8, 0), // V2,L2 & L3 + ]; + + const v1Layer1 = 0; + const v1Layer2 = 1; + const v1Layer3 = 2; + const v2Layer1 = 3; + const v2Layer23 = 4; + + const bands = "bands "; + const to31 = " to 31"; + const layer12ModeExtensions = { + 0b00000000: bands + 4 + to31, + 0b00010000: bands + 8 + to31, + 0b00100000: bands + 12 + to31, + 0b00110000: bands + 16 + to31, + }; + + const intensityStereo = "Intensity stereo "; + const msStereo = ", MS stereo "; + const on = "on"; + const off = "off"; + const layer3ModeExtensions = { + 0b00000000: intensityStereo + off + msStereo + off, + 0b00010000: intensityStereo + on + msStereo + off, + 0b00100000: intensityStereo + off + msStereo + on, + 0b00110000: intensityStereo + on + msStereo + on, + }; + const layers = { + 0b00000000: { description: reserved }, + 0b00000010: { + description: "Layer III", + framePadding: 1, + modeExtensions: layer3ModeExtensions, + v1: { + bitrateIndex: v1Layer3, + samples: 1152, + }, + v2: { + bitrateIndex: v2Layer23, + samples: 576, + }, + }, + 0b00000100: { + description: "Layer II", + framePadding: 1, + modeExtensions: layer12ModeExtensions, + samples: 1152, + v1: { + bitrateIndex: v1Layer2, + }, + v2: { + bitrateIndex: v2Layer23, + }, + }, + 0b00000110: { + description: "Layer I", + framePadding: 4, + modeExtensions: layer12ModeExtensions, + samples: 384, + v1: { + bitrateIndex: v1Layer1, + }, + v2: { + bitrateIndex: v2Layer1, + }, + }, + }; + + const mpegVersion$1 = "MPEG Version "; + const isoIec = "ISO/IEC "; + const v2 = "v2"; + const v1 = "v1"; + const mpegVersions = { + 0b00000000: { + description: `${mpegVersion$1}2.5 (later extension of MPEG 2)`, + layers: v2, + sampleRates: { + 0b00000000: rate11025, + 0b00000100: rate12000, + 0b00001000: rate8000, + 0b00001100: reserved, + }, + }, + 0b00001000: { description: reserved }, + 0b00010000: { + description: `${mpegVersion$1}2 (${isoIec}13818-3)`, + layers: v2, + sampleRates: { + 0b00000000: rate22050, + 0b00000100: rate24000, + 0b00001000: rate16000, + 0b00001100: reserved, + }, + }, + 0b00011000: { + description: `${mpegVersion$1}1 (${isoIec}11172-3)`, + layers: v1, + sampleRates: { + 0b00000000: rate44100, + 0b00000100: rate48000, + 0b00001000: rate32000, + 0b00001100: reserved, + }, + }, + }; + + const protection$1 = { + 0b00000000: sixteenBitCRC, + 0b00000001: none, + }; + + const emphasis = { + 0b00000000: none, + 0b00000001: "50/15 ms", + 0b00000010: reserved, + 0b00000011: "CCIT J.17", + }; + + const channelModes = { + 0b00000000: { channels: 2, description: stereo }, + 0b01000000: { channels: 2, description: "joint " + stereo }, + 0b10000000: { channels: 2, description: "dual channel" }, + 0b11000000: { channels: 1, description: monophonic }, + }; + + class MPEGHeader extends CodecHeader { + static *getHeader(codecParser, headerCache, readOffset) { + const header = {}; + + // check for id3 header + const id3v2Header = yield* ID3v2.getID3v2Header( + codecParser, + headerCache, + readOffset + ); + + if (id3v2Header) { + // throw away the data. id3 parsing is not implemented yet. + yield* codecParser.readRawData(id3v2Header.length, readOffset); + codecParser.incrementRawData(id3v2Header.length); + } + + // Must be at least four bytes. + const data = yield* codecParser.readRawData(4, readOffset); + + // Check header cache + const key = bytesToString(data.subarray(0, 4)); + const cachedHeader = headerCache.getHeader(key); + if (cachedHeader) return new MPEGHeader(cachedHeader); + + // Frame sync (all bits must be set): `11111111|111`: + if (data[0] !== 0xff || data[1] < 0xe0) return null; + + // Byte (2 of 4) + // * `111BBCCD` + // * `...BB...`: MPEG Audio version ID + // * `.....CC.`: Layer description + // * `.......D`: Protection bit (0 - Protected by CRC (16bit CRC follows header), 1 = Not protected) + + // Mpeg version (1, 2, 2.5) + const mpegVersion = mpegVersions[data[1] & 0b00011000]; + if (mpegVersion.description === reserved) return null; + + // Layer (I, II, III) + const layerBits = data[1] & 0b00000110; + if (layers[layerBits].description === reserved) return null; + const layer = { + ...layers[layerBits], + ...layers[layerBits][mpegVersion.layers], + }; + + header.mpegVersion = mpegVersion.description; + header.layer = layer.description; + header.samples = layer.samples; + header.protection = protection$1[data[1] & 0b00000001]; + + header.length = 4; + + // Byte (3 of 4) + // * `EEEEFFGH` + // * `EEEE....`: Bitrate index. 1111 is invalid, everything else is accepted + // * `....FF..`: Sample rate + // * `......G.`: Padding bit, 0=frame not padded, 1=frame padded + // * `.......H`: Private bit. + header.bitrate = bitrateMatrix[data[2] & 0b11110000][layer.bitrateIndex]; + if (header.bitrate === bad) return null; + + header.sampleRate = mpegVersion.sampleRates[data[2] & 0b00001100]; + if (header.sampleRate === reserved) return null; + + header.framePadding = data[2] & 0b00000010 && layer.framePadding; + header.isPrivate = Boolean(data[2] & 0b00000001); + + header.frameLength = Math.floor( + (125 * header.bitrate * header.samples) / header.sampleRate + + header.framePadding + ); + if (!header.frameLength) return null; + + // Byte (4 of 4) + // * `IIJJKLMM` + // * `II......`: Channel mode + // * `..JJ....`: Mode extension (only if joint stereo) + // * `....K...`: Copyright + // * `.....L..`: Original + // * `......MM`: Emphasis + const channelModeBits = data[3] & 0b11000000; + header.channelMode = channelModes[channelModeBits].description; + header.channels = channelModes[channelModeBits].channels; + + header.modeExtension = layer.modeExtensions[data[3] & 0b00110000]; + header.isCopyrighted = Boolean(data[3] & 0b00001000); + header.isOriginal = Boolean(data[3] & 0b00000100); + + header.emphasis = emphasis[data[3] & 0b00000011]; + if (header.emphasis === reserved) return null; + + header.bitDepth = 16; + + // set header cache + const { length, frameLength, samples, ...codecUpdateFields } = header; + + headerCache.setHeader(key, header, codecUpdateFields); + return new MPEGHeader(header); + } + + /** + * @private + * Call MPEGHeader.getHeader(Array) to get instance + */ + constructor(header) { + super(header); + + this.bitrate = header.bitrate; + this.emphasis = header.emphasis; + this.framePadding = header.framePadding; + this.isCopyrighted = header.isCopyrighted; + this.isOriginal = header.isOriginal; + this.isPrivate = header.isPrivate; + this.layer = header.layer; + this.modeExtension = header.modeExtension; + this.mpegVersion = header.mpegVersion; + this.protection = header.protection; + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class MPEGFrame extends CodecFrame { + static *getFrame(codecParser, headerCache, readOffset) { + return yield* super.getFrame( + MPEGHeader, + MPEGFrame, + codecParser, + headerCache, + readOffset + ); + } + + constructor(header, frame, samples) { + super(header, frame, samples); + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class MPEGParser extends Parser { + constructor(codecParser, headerCache, onCodec) { + super(codecParser, headerCache); + this.Frame = MPEGFrame; + this.Header = MPEGHeader; + + onCodec(this.codec); + } + + get codec() { + return "mpeg"; + } + + *parseFrame() { + return yield* this.fixedLengthFrameSync(); + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + const mpegVersion = { + 0b00000000: "MPEG-4", + 0b00001000: "MPEG-2", + }; + + const layer = { + 0b00000000: "valid", + 0b00000010: bad, + 0b00000100: bad, + 0b00000110: bad, + }; + + const protection = { + 0b00000000: sixteenBitCRC, + 0b00000001: none, + }; + + const profile = { + 0b00000000: "AAC Main", + 0b01000000: "AAC LC (Low Complexity)", + 0b10000000: "AAC SSR (Scalable Sample Rate)", + 0b11000000: "AAC LTP (Long Term Prediction)", + }; + + const sampleRates = { + 0b00000000: rate96000, + 0b00000100: rate88200, + 0b00001000: rate64000, + 0b00001100: rate48000, + 0b00010000: rate44100, + 0b00010100: rate32000, + 0b00011000: rate24000, + 0b00011100: rate22050, + 0b00100000: rate16000, + 0b00100100: rate12000, + 0b00101000: rate11025, + 0b00101100: rate8000, + 0b00110000: rate7350, + 0b00110100: reserved, + 0b00111000: reserved, + 0b00111100: "frequency is written explicitly", + }; + + // prettier-ignore + const channelMode = { + 0b000000000: { channels: 0, description: "Defined in AOT Specific Config" }, + /* + 'monophonic (mono)' + 'stereo (left, right)' + 'linear surround (front center, front left, front right)' + 'quadraphonic (front center, front left, front right, rear center)' + '5.0 surround (front center, front left, front right, rear left, rear right)' + '5.1 surround (front center, front left, front right, rear left, rear right, LFE)' + '7.1 surround (front center, front left, front right, side left, side right, rear left, rear right, LFE)' + */ + 0b001000000: { channels: 1, description: monophonic }, + 0b010000000: { channels: 2, description: getChannelMapping(2,channelMappings[0][0]) }, + 0b011000000: { channels: 3, description: getChannelMapping(3,channelMappings[1][3]), }, + 0b100000000: { channels: 4, description: getChannelMapping(4,channelMappings[1][3],channelMappings[3][4]), }, + 0b101000000: { channels: 5, description: getChannelMapping(5,channelMappings[1][3],channelMappings[3][0]), }, + 0b110000000: { channels: 6, description: getChannelMapping(6,channelMappings[1][3],channelMappings[3][0],lfe), }, + 0b111000000: { channels: 8, description: getChannelMapping(8,channelMappings[1][3],channelMappings[2][0],channelMappings[3][0],lfe), }, + }; + + class AACHeader extends CodecHeader { + static *getHeader(codecParser, headerCache, readOffset) { + const header = {}; + + // Must be at least seven bytes. Out of data + const data = yield* codecParser.readRawData(7, readOffset); + + // Check header cache + const key = bytesToString([ + data[0], + data[1], + data[2], + (data[3] & 0b11111100) | (data[6] & 0b00000011), // frame length, buffer fullness varies so don't cache it + ]); + const cachedHeader = headerCache.getHeader(key); + + if (!cachedHeader) { + // Frame sync (all bits must be set): `11111111|1111`: + if (data[0] !== 0xff || data[1] < 0xf0) return null; + + // Byte (2 of 7) + // * `1111BCCD` + // * `....B...`: MPEG Version: 0 for MPEG-4, 1 for MPEG-2 + // * `.....CC.`: Layer: always 0 + // * `.......D`: protection absent, Warning, set to 1 if there is no CRC and 0 if there is CRC + header.mpegVersion = mpegVersion[data[1] & 0b00001000]; + + header.layer = layer[data[1] & 0b00000110]; + if (header.layer === bad) return null; + + const protectionBit = data[1] & 0b00000001; + header.protection = protection[protectionBit]; + header.length = protectionBit ? 7 : 9; + + // Byte (3 of 7) + // * `EEFFFFGH` + // * `EE......`: profile, the MPEG-4 Audio Object Type minus 1 + // * `..FFFF..`: MPEG-4 Sampling Frequency Index (15 is forbidden) + // * `......G.`: private bit, guaranteed never to be used by MPEG, set to 0 when encoding, ignore when decoding + header.profileBits = data[2] & 0b11000000; + header.sampleRateBits = data[2] & 0b00111100; + const privateBit = data[2] & 0b00000010; + + header.profile = profile[header.profileBits]; + + header.sampleRate = sampleRates[header.sampleRateBits]; + if (header.sampleRate === reserved) return null; + + header.isPrivate = Boolean(privateBit); + + // Byte (3,4 of 7) + // * `.......H|HH......`: MPEG-4 Channel Configuration (in the case of 0, the channel configuration is sent via an inband PCE) + header.channelModeBits = ((data[2] << 8) | data[3]) & 0b111000000; + header.channelMode = channelMode[header.channelModeBits].description; + header.channels = channelMode[header.channelModeBits].channels; + + // Byte (4 of 7) + // * `HHIJKLMM` + // * `..I.....`: originality, set to 0 when encoding, ignore when decoding + // * `...J....`: home, set to 0 when encoding, ignore when decoding + // * `....K...`: copyrighted id bit, the next bit of a centrally registered copyright identifier, set to 0 when encoding, ignore when decoding + // * `.....L..`: copyright id start, signals that this frame's copyright id bit is the first bit of the copyright id, set to 0 when encoding, ignore when decoding + header.isOriginal = Boolean(data[3] & 0b00100000); + header.isHome = Boolean(data[3] & 0b00001000); + header.copyrightId = Boolean(data[3] & 0b00001000); + header.copyrightIdStart = Boolean(data[3] & 0b00000100); + header.bitDepth = 16; + header.samples = 1024; + + // Byte (7 of 7) + // * `......PP` Number of AAC frames (RDBs) in ADTS frame minus 1, for maximum compatibility always use 1 AAC frame per ADTS frame + header.numberAACFrames = data[6] & 0b00000011; + + const { + length, + channelModeBits, + profileBits, + sampleRateBits, + frameLength, + samples, + numberAACFrames, + ...codecUpdateFields + } = header; + headerCache.setHeader(key, header, codecUpdateFields); + } else { + Object.assign(header, cachedHeader); + } + + // Byte (4,5,6 of 7) + // * `.......MM|MMMMMMMM|MMM.....`: frame length, this value must include 7 or 9 bytes of header length: FrameLength = (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame) + header.frameLength = + ((data[3] << 11) | (data[4] << 3) | (data[5] >> 5)) & 0x1fff; + if (!header.frameLength) return null; + + // Byte (6,7 of 7) + // * `...OOOOO|OOOOOO..`: Buffer fullness + const bufferFullnessBits = ((data[5] << 6) | (data[6] >> 2)) & 0x7ff; + header.bufferFullness = + bufferFullnessBits === 0x7ff ? "VBR" : bufferFullnessBits; + + return new AACHeader(header); + } + + /** + * @private + * Call AACHeader.getHeader(Array) to get instance + */ + constructor(header) { + super(header); + + this.copyrightId = header.copyrightId; + this.copyrightIdStart = header.copyrightIdStart; + this.bufferFullness = header.bufferFullness; + this.isHome = header.isHome; + this.isOriginal = header.isOriginal; + this.isPrivate = header.isPrivate; + this.layer = header.layer; + this.length = header.length; + this.mpegVersion = header.mpegVersion; + this.numberAACFrames = header.numberAACFrames; + this.profile = header.profile; + this.protection = header.protection; + } + + get audioSpecificConfig() { + // Audio Specific Configuration + // * `000EEFFF|F0HHH000`: + // * `000EE...|........`: Object Type (profileBit + 1) + // * `.....FFF|F.......`: Sample Rate + // * `........|.0HHH...`: Channel Configuration + // * `........|.....0..`: Frame Length (1024) + // * `........|......0.`: does not depend on core coder + // * `........|.......0`: Not Extension + const header = headerStore.get(this); + + const audioSpecificConfig = + ((header.profileBits + 0x40) << 5) | + (header.sampleRateBits << 5) | + (header.channelModeBits >> 3); + + const bytes = new Uint8Array(2); + new DataView(bytes.buffer).setUint16(0, audioSpecificConfig, false); + return bytes; + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class AACFrame extends CodecFrame { + static *getFrame(codecParser, headerCache, readOffset) { + return yield* super.getFrame( + AACHeader, + AACFrame, + codecParser, + headerCache, + readOffset + ); + } + + constructor(header, frame, samples) { + super(header, frame, samples); + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class AACParser extends Parser { + constructor(codecParser, headerCache, onCodec) { + super(codecParser, headerCache); + this.Frame = AACFrame; + this.Header = AACHeader; + + onCodec(this.codec); + } + + get codec() { + return "aac"; + } + + *parseFrame() { + return yield* this.fixedLengthFrameSync(); + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class FLACFrame extends CodecFrame { + static getFrameFooterCrc16(data) { + return (data[data.length - 2] << 8) + data[data.length - 1]; + } + + // check frame footer crc + // https://xiph.org/flac/format.html#frame_footer + static checkFrameFooterCrc16(data) { + const expectedCrc16 = FLACFrame.getFrameFooterCrc16(data); + const actualCrc16 = flacCrc16(data.subarray(0, -2)); + + return expectedCrc16 === actualCrc16; + } + + constructor(data, header, streamInfo) { + header.streamInfo = streamInfo; + header.crc16 = FLACFrame.getFrameFooterCrc16(data); + + super(header, data, headerStore.get(header).samples); + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + const getFromStreamInfo = "get from STREAMINFO metadata block"; + + const blockingStrategy = { + 0b00000000: "Fixed", + 0b00000001: "Variable", + }; + + const blockSize = { + 0b00000000: reserved, + 0b00010000: 192, + // 0b00100000: 576, + // 0b00110000: 1152, + // 0b01000000: 2304, + // 0b01010000: 4608, + // 0b01100000: "8-bit (blocksize-1) from end of header", + // 0b01110000: "16-bit (blocksize-1) from end of header", + // 0b10000000: 256, + // 0b10010000: 512, + // 0b10100000: 1024, + // 0b10110000: 2048, + // 0b11000000: 4096, + // 0b11010000: 8192, + // 0b11100000: 16384, + // 0b11110000: 32768, + }; + for (let i = 2; i < 16; i++) + blockSize[i << 4] = i < 6 ? 576 * 2 ** (i - 2) : 2 ** i; + + const sampleRate = { + 0b00000000: getFromStreamInfo, + 0b00000001: rate88200, + 0b00000010: rate176400, + 0b00000011: rate192000, + 0b00000100: rate8000, + 0b00000101: rate16000, + 0b00000110: rate22050, + 0b00000111: rate24000, + 0b00001000: rate32000, + 0b00001001: rate44100, + 0b00001010: rate48000, + 0b00001011: rate96000, + // 0b00001100: "8-bit sample rate (in kHz) from end of header", + // 0b00001101: "16-bit sample rate (in Hz) from end of header", + // 0b00001110: "16-bit sample rate (in tens of Hz) from end of header", + 0b00001111: bad, + }; + + /* prettier-ignore */ + const channelAssignments = { + /*' + 'monophonic (mono)' + 'stereo (left, right)' + 'linear surround (left, right, center)' + 'quadraphonic (front left, front right, rear left, rear right)' + '5.0 surround (front left, front right, front center, rear left, rear right)' + '5.1 surround (front left, front right, front center, LFE, rear left, rear right)' + '6.1 surround (front left, front right, front center, LFE, rear center, side left, side right)' + '7.1 surround (front left, front right, front center, LFE, rear left, rear right, side left, side right)' + */ + 0b00000000: {channels: 1, description: monophonic}, + 0b00010000: {channels: 2, description: getChannelMapping(2,channelMappings[0][0])}, + 0b00100000: {channels: 3, description: getChannelMapping(3,channelMappings[0][1])}, + 0b00110000: {channels: 4, description: getChannelMapping(4,channelMappings[1][0],channelMappings[3][0])}, + 0b01000000: {channels: 5, description: getChannelMapping(5,channelMappings[1][1],channelMappings[3][0])}, + 0b01010000: {channels: 6, description: getChannelMapping(6,channelMappings[1][1],lfe,channelMappings[3][0])}, + 0b01100000: {channels: 7, description: getChannelMapping(7,channelMappings[1][1],lfe,channelMappings[3][4],channelMappings[2][0])}, + 0b01110000: {channels: 8, description: getChannelMapping(8,channelMappings[1][1],lfe,channelMappings[3][0],channelMappings[2][0])}, + 0b10000000: {channels: 2, description: `${stereo} (left, diff)`}, + 0b10010000: {channels: 2, description: `${stereo} (diff, right)`}, + 0b10100000: {channels: 2, description: `${stereo} (avg, diff)`}, + 0b10110000: reserved, + 0b11000000: reserved, + 0b11010000: reserved, + 0b11100000: reserved, + 0b11110000: reserved, + }; + + const bitDepth = { + 0b00000000: getFromStreamInfo, + 0b00000010: 8, + 0b00000100: 12, + 0b00000110: reserved, + 0b00001000: 16, + 0b00001010: 20, + 0b00001100: 24, + 0b00001110: reserved, + }; + + class FLACHeader extends CodecHeader { + // https://datatracker.ietf.org/doc/html/rfc3629#section-3 + // Char. number range | UTF-8 octet sequence + // (hexadecimal) | (binary) + // --------------------+--------------------------------------------- + // 0000 0000-0000 007F | 0xxxxxxx + // 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + // 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + // 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + static decodeUTF8Int(data) { + if (data[0] > 0xfe) { + return null; // length byte must have at least one zero as the lsb + } + + if (data[0] < 0x80) return { value: data[0], length: 1 }; + + // get length by counting the number of msb that are set to 1 + let length = 1; + for (let zeroMask = 0x40; zeroMask & data[0]; zeroMask >>= 1) length++; + + let idx = length - 1, + value = 0, + shift = 0; + + // sum together the encoded bits in bytes 2 to length + // 1110xxxx 10[cccccc] 10[bbbbbb] 10[aaaaaa] + // + // value = [cccccc] | [bbbbbb] | [aaaaaa] + for (; idx > 0; shift += 6, idx--) { + if ((data[idx] & 0xc0) !== 0x80) { + return null; // each byte should have leading 10xxxxxx + } + value |= (data[idx] & 0x3f) << shift; // add the encoded bits + } + + // read the final encoded bits in byte 1 + // 1110[dddd] 10[cccccc] 10[bbbbbb] 10[aaaaaa] + // + // value = [dddd] | [cccccc] | [bbbbbb] | [aaaaaa] + value |= (data[idx] & (0x7f >> length)) << shift; + + return { value, length }; + } + + static getHeaderFromUint8Array(data, headerCache) { + const codecParserStub = { + readRawData: function* () { + return data; + }, + }; + + return FLACHeader.getHeader(codecParserStub, headerCache, 0).next().value; + } + + static *getHeader(codecParser, headerCache, readOffset) { + // Must be at least 6 bytes. + let data = yield* codecParser.readRawData(6, readOffset); + + // Bytes (1-2 of 6) + // * `11111111|111110..`: Frame sync + // * `........|......0.`: Reserved 0 - mandatory, 1 - reserved + if (data[0] !== 0xff || !(data[1] === 0xf8 || data[1] === 0xf9)) { + return null; + } + + const header = {}; + + // Check header cache + const key = bytesToString(data.subarray(0, 4)); + const cachedHeader = headerCache.getHeader(key); + + if (!cachedHeader) { + // Byte (2 of 6) + // * `.......C`: Blocking strategy, 0 - fixed, 1 - variable + header.blockingStrategyBits = data[1] & 0b00000001; + header.blockingStrategy = blockingStrategy[header.blockingStrategyBits]; + + // Byte (3 of 6) + // * `DDDD....`: Block size in inter-channel samples + // * `....EEEE`: Sample rate + header.blockSizeBits = data[2] & 0b11110000; + header.sampleRateBits = data[2] & 0b00001111; + + header.blockSize = blockSize[header.blockSizeBits]; + if (header.blockSize === reserved) { + return null; + } + + header.sampleRate = sampleRate[header.sampleRateBits]; + if (header.sampleRate === bad) { + return null; + } + + // Byte (4 of 6) + // * `FFFF....`: Channel assignment + // * `....GGG.`: Sample size in bits + // * `.......H`: Reserved 0 - mandatory, 1 - reserved + if (data[3] & 0b00000001) { + return null; + } + + const channelAssignment = channelAssignments[data[3] & 0b11110000]; + if (channelAssignment === reserved) { + return null; + } + + header.channels = channelAssignment.channels; + header.channelMode = channelAssignment.description; + + header.bitDepth = bitDepth[data[3] & 0b00001110]; + if (header.bitDepth === reserved) { + return null; + } + } else { + Object.assign(header, cachedHeader); + } + + // Byte (5...) + // * `IIIIIIII|...`: VBR block size ? sample number : frame number + header.length = 5; + + // check if there is enough data to parse UTF8 + data = yield* codecParser.readRawData(header.length + 8, readOffset); + + const decodedUtf8 = FLACHeader.decodeUTF8Int(data.subarray(4)); + if (!decodedUtf8) { + return null; + } + + if (header.blockingStrategyBits) { + header.sampleNumber = decodedUtf8.value; + } else { + header.frameNumber = decodedUtf8.value; + } + + header.length += decodedUtf8.length; + + // Byte (...) + // * `JJJJJJJJ|(JJJJJJJJ)`: Blocksize (8/16bit custom value) + if (header.blockSizeBits === 0b01100000) { + // 8 bit + if (data.length < header.length) + data = yield* codecParser.readRawData(header.length, readOffset); + + header.blockSize = data[header.length - 1] + 1; + header.length += 1; + } else if (header.blockSizeBits === 0b01110000) { + // 16 bit + if (data.length < header.length) + data = yield* codecParser.readRawData(header.length, readOffset); + + header.blockSize = + (data[header.length - 1] << 8) + data[header.length] + 1; + header.length += 2; + } + + header.samples = header.blockSize; + + // Byte (...) + // * `KKKKKKKK|(KKKKKKKK)`: Sample rate (8/16bit custom value) + if (header.sampleRateBits === 0b00001100) { + // 8 bit + if (data.length < header.length) + data = yield* codecParser.readRawData(header.length, readOffset); + + header.sampleRate = data[header.length - 1] * 1000; + header.length += 1; + } else if (header.sampleRateBits === 0b00001101) { + // 16 bit + if (data.length < header.length) + data = yield* codecParser.readRawData(header.length, readOffset); + + header.sampleRate = (data[header.length - 1] << 8) + data[header.length]; + header.length += 2; + } else if (header.sampleRateBits === 0b00001110) { + // 16 bit + if (data.length < header.length) + data = yield* codecParser.readRawData(header.length, readOffset); + + header.sampleRate = + ((data[header.length - 1] << 8) + data[header.length]) * 10; + header.length += 2; + } + + // Byte (...) + // * `LLLLLLLL`: CRC-8 + if (data.length < header.length) + data = yield* codecParser.readRawData(header.length, readOffset); + + header.crc = data[header.length - 1]; + if (header.crc !== crc8(data.subarray(0, header.length - 1))) { + return null; + } + + if (!cachedHeader) { + const { + blockingStrategyBits, + frameNumber, + sampleNumber, + samples, + sampleRateBits, + blockSizeBits, + crc, + length, + ...codecUpdateFields + } = header; + headerCache.setHeader(key, header, codecUpdateFields); + } + return new FLACHeader(header); + } + + /** + * @private + * Call FLACHeader.getHeader(Array) to get instance + */ + constructor(header) { + super(header); + + this.crc16 = null; // set in FLACFrame + this.blockingStrategy = header.blockingStrategy; + this.blockSize = header.blockSize; + this.frameNumber = header.frameNumber; + this.sampleNumber = header.sampleNumber; + this.streamInfo = null; // set during ogg parsing + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + const MIN_FLAC_FRAME_SIZE = 2; + const MAX_FLAC_FRAME_SIZE = 512 * 1024; + + class FLACParser extends Parser { + constructor(codecParser, onCodecUpdate) { + super(codecParser, onCodecUpdate); + this.Frame = FLACFrame; + this.Header = FLACHeader; + } + + get codec() { + return "flac"; + } + + *_getNextFrameSyncOffset(offset) { + const data = yield* this._codecParser.readRawData(2, 0); + const dataLength = data.length - 2; + + while (offset < dataLength) { + // * `11111111|111110..`: Frame sync + // * `........|......0.`: Reserved 0 - mandatory, 1 - reserved + const firstByte = data[offset]; + if (firstByte === 0xff) { + const secondByte = data[offset + 1]; + if (secondByte === 0xf8 || secondByte === 0xf9) break; + if (secondByte !== 0xff) offset++; // might as well check for the next sync byte + } + offset++; + } + + return offset; + } + + *parseFrame() { + // find the first valid frame header + do { + const header = yield* FLACHeader.getHeader( + this._codecParser, + this._headerCache, + 0 + ); + + if (header) { + // found a valid frame header + // find the next valid frame header + let nextHeaderOffset = + headerStore.get(header).length + MIN_FLAC_FRAME_SIZE; + + while (nextHeaderOffset <= MAX_FLAC_FRAME_SIZE) { + if ( + this._codecParser._flushing || + (yield* FLACHeader.getHeader( + this._codecParser, + this._headerCache, + nextHeaderOffset + )) + ) { + // found a valid next frame header + let frameData = yield* this._codecParser.readRawData( + nextHeaderOffset + ); + + if (!this._codecParser._flushing) + frameData = frameData.subarray(0, nextHeaderOffset); + + // check that this is actually the next header by validating the frame footer crc16 + if (FLACFrame.checkFrameFooterCrc16(frameData)) { + // both frame headers, and frame footer crc16 are valid, we are synced (odds are pretty low of a false positive) + const frame = new FLACFrame(frameData, header); + + this._headerCache.enable(); // start caching when synced + this._codecParser.incrementRawData(nextHeaderOffset); // increment to the next frame + this._codecParser.mapFrameStats(frame); + + return frame; + } + } + + nextHeaderOffset = yield* this._getNextFrameSyncOffset( + nextHeaderOffset + 1 + ); + } + + this._codecParser.logWarning( + `Unable to sync FLAC frame after searching ${nextHeaderOffset} bytes.` + ); + this._codecParser.incrementRawData(nextHeaderOffset); + } else { + // not synced, increment data to continue syncing + this._codecParser.incrementRawData( + yield* this._getNextFrameSyncOffset(1) + ); + } + } while (true); + } + + parseOggPage(oggPage) { + if (oggPage.pageSequenceNumber === 0) { + // Identification header + + this._headerCache.enable(); + this._streamInfo = oggPage.data.subarray(13); + } else if (oggPage.pageSequenceNumber === 1) ; else { + oggPage.codecFrames = frameStore + .get(oggPage) + .segments.map((segment) => { + const header = FLACHeader.getHeaderFromUint8Array( + segment, + this._headerCache + ); + + if (header) { + return new FLACFrame(segment, header, this._streamInfo); + } else { + this._codecParser.logWarning( + "Failed to parse Ogg FLAC frame", + "Skipping invalid FLAC frame" + ); + } + }) + .filter((frame) => Boolean(frame)); + } + + return oggPage; + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class OggPageHeader { + static *getHeader(codecParser, headerCache, readOffset) { + const header = {}; + + // Must be at least 28 bytes. + let data = yield* codecParser.readRawData(28, readOffset); + + // Bytes (1-4 of 28) + // Frame sync (must equal OggS): `AAAAAAAA|AAAAAAAA|AAAAAAAA|AAAAAAAA`: + if ( + data[0] !== 0x4f || // O + data[1] !== 0x67 || // g + data[2] !== 0x67 || // g + data[3] !== 0x53 // S + ) { + return null; + } + + // Byte (5 of 28) + // * `BBBBBBBB`: stream_structure_version + header.streamStructureVersion = data[4]; + + // Byte (6 of 28) + // * `00000CDE` + // * `00000...`: All zeros + // * `.....C..`: (0 no, 1 yes) last page of logical bitstream (eos) + // * `......D.`: (0 no, 1 yes) first page of logical bitstream (bos) + // * `.......E`: (0 no, 1 yes) continued packet + const zeros = data[5] & 0b11111000; + if (zeros) return null; + + header.isLastPage = Boolean(data[5] & 0b00000100); + header.isFirstPage = Boolean(data[5] & 0b00000010); + header.isContinuedPacket = Boolean(data[5] & 0b00000001); + + const view = new DataView(Uint8Array.from(data.subarray(0, 28)).buffer); + + // Byte (7-14 of 28) + // * `FFFFFFFF|FFFFFFFF|FFFFFFFF|FFFFFFFF|FFFFFFFF|FFFFFFFF|FFFFFFFF|FFFFFFFF` + // * Absolute Granule Position + + /** + * @todo Safari does not support getBigInt64, but it also doesn't support Ogg + */ + try { + header.absoluteGranulePosition = view.getBigInt64(6, true); + } catch {} + + // Byte (15-18 of 28) + // * `GGGGGGGG|GGGGGGGG|GGGGGGGG|GGGGGGGG` + // * Stream Serial Number + header.streamSerialNumber = view.getInt32(14, true); + + // Byte (19-22 of 28) + // * `HHHHHHHH|HHHHHHHH|HHHHHHHH|HHHHHHHH` + // * Page Sequence Number + header.pageSequenceNumber = view.getInt32(18, true); + + // Byte (23-26 of 28) + // * `IIIIIIII|IIIIIIII|IIIIIIII|IIIIIIII` + // * Page Checksum + header.pageChecksum = view.getInt32(22, true); + + // Byte (27 of 28) + // * `JJJJJJJJ`: Number of page segments in the segment table + const pageSegmentTableLength = data[26]; + header.length = pageSegmentTableLength + 27; + + data = yield* codecParser.readRawData(header.length, readOffset); // read in the page segment table + + header.frameLength = 0; + header.pageSegmentTable = []; + header.pageSegmentBytes = Uint8Array.from(data.subarray(27, header.length)); + + for (let i = 0, segmentLength = 0; i < pageSegmentTableLength; i++) { + const segmentByte = header.pageSegmentBytes[i]; + + header.frameLength += segmentByte; + segmentLength += segmentByte; + + if (segmentByte !== 0xff || i === pageSegmentTableLength - 1) { + header.pageSegmentTable.push(segmentLength); + segmentLength = 0; + } + } + + return new OggPageHeader(header); + } + + /** + * @private + * Call OggPageHeader.getHeader(Array) to get instance + */ + constructor(header) { + headerStore.set(this, header); + + this.absoluteGranulePosition = header.absoluteGranulePosition; + this.isContinuedPacket = header.isContinuedPacket; + this.isFirstPage = header.isFirstPage; + this.isLastPage = header.isLastPage; + this.pageSegmentTable = header.pageSegmentTable; + this.pageSequenceNumber = header.pageSequenceNumber; + this.pageChecksum = header.pageChecksum; + this.streamSerialNumber = header.streamSerialNumber; + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class OggPage extends Frame { + static *getFrame(codecParser, headerCache, readOffset) { + const header = yield* OggPageHeader.getHeader( + codecParser, + headerCache, + readOffset + ); + + if (header) { + const frameLength = headerStore.get(header).frameLength; + const headerLength = headerStore.get(header).length; + const totalLength = headerLength + frameLength; + + const rawData = (yield* codecParser.readRawData(totalLength, 0)).subarray( + 0, + totalLength + ); + + const frame = rawData.subarray(headerLength, totalLength); + + return new OggPage(header, frame, rawData); + } else { + return null; + } + } + + constructor(header, frame, rawData) { + super(header, frame); + + frameStore.get(this).length = rawData.length; + + this.codecFrames = []; + this.rawData = rawData; + this.absoluteGranulePosition = header.absoluteGranulePosition; + this.crc32 = header.pageChecksum; + this.duration = 0; + this.isContinuedPacket = header.isContinuedPacket; + this.isFirstPage = header.isFirstPage; + this.isLastPage = header.isLastPage; + this.pageSequenceNumber = header.pageSequenceNumber; + this.samples = 0; + this.streamSerialNumber = header.streamSerialNumber; + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class OpusFrame extends CodecFrame { + constructor(data, header) { + super( + header, + data, + ((header.frameSize * header.frameCount) / 1000) * header.sampleRate + ); + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + /* prettier-ignore */ + const channelMappingFamilies = { + 0b00000000: vorbisOpusChannelMapping.slice(0,2), + /* + 0: "monophonic (mono)" + 1: "stereo (left, right)" + */ + 0b00000001: vorbisOpusChannelMapping + /* + 0: "monophonic (mono)" + 1: "stereo (left, right)" + 2: "linear surround (left, center, right)" + 3: "quadraphonic (front left, front right, rear left, rear right)" + 4: "5.0 surround (front left, front center, front right, rear left, rear right)" + 5: "5.1 surround (front left, front center, front right, rear left, rear right, LFE)" + 6: "6.1 surround (front left, front center, front right, side left, side right, rear center, LFE)" + 7: "7.1 surround (front left, front center, front right, side left, side right, rear left, rear right, LFE)" + */ + // additional channel mappings are user defined + }; + + const silkOnly = "SILK-only"; + const celtOnly = "CELT-only"; + const hybrid = "Hybrid"; + + const narrowBand = "narrowband"; + const mediumBand = "medium-band"; + const wideBand = "wideband"; + const superWideBand = "super-wideband"; + const fullBand = "fullband"; + + // 0 1 2 3 4 5 6 7 + // +-+-+-+-+-+-+-+-+ + // | config |s| c | + // +-+-+-+-+-+-+-+-+ + const configTable = { + 0b00000000: { mode: silkOnly, bandwidth: narrowBand, frameSize: 10 }, + 0b00001000: { mode: silkOnly, bandwidth: narrowBand, frameSize: 20 }, + 0b00010000: { mode: silkOnly, bandwidth: narrowBand, frameSize: 40 }, + 0b00011000: { mode: silkOnly, bandwidth: narrowBand, frameSize: 60 }, + 0b00100000: { mode: silkOnly, bandwidth: mediumBand, frameSize: 10 }, + 0b00101000: { mode: silkOnly, bandwidth: mediumBand, frameSize: 20 }, + 0b00110000: { mode: silkOnly, bandwidth: mediumBand, frameSize: 40 }, + 0b00111000: { mode: silkOnly, bandwidth: mediumBand, frameSize: 60 }, + 0b01000000: { mode: silkOnly, bandwidth: wideBand, frameSize: 10 }, + 0b01001000: { mode: silkOnly, bandwidth: wideBand, frameSize: 20 }, + 0b01010000: { mode: silkOnly, bandwidth: wideBand, frameSize: 40 }, + 0b01011000: { mode: silkOnly, bandwidth: wideBand, frameSize: 60 }, + 0b01100000: { mode: hybrid, bandwidth: superWideBand, frameSize: 10 }, + 0b01101000: { mode: hybrid, bandwidth: superWideBand, frameSize: 20 }, + 0b01110000: { mode: hybrid, bandwidth: fullBand, frameSize: 10 }, + 0b01111000: { mode: hybrid, bandwidth: fullBand, frameSize: 20 }, + 0b10000000: { mode: celtOnly, bandwidth: narrowBand, frameSize: 2.5 }, + 0b10001000: { mode: celtOnly, bandwidth: narrowBand, frameSize: 5 }, + 0b10010000: { mode: celtOnly, bandwidth: narrowBand, frameSize: 10 }, + 0b10011000: { mode: celtOnly, bandwidth: narrowBand, frameSize: 20 }, + 0b10100000: { mode: celtOnly, bandwidth: wideBand, frameSize: 2.5 }, + 0b10101000: { mode: celtOnly, bandwidth: wideBand, frameSize: 5 }, + 0b10110000: { mode: celtOnly, bandwidth: wideBand, frameSize: 10 }, + 0b10111000: { mode: celtOnly, bandwidth: wideBand, frameSize: 20 }, + 0b11000000: { mode: celtOnly, bandwidth: superWideBand, frameSize: 2.5 }, + 0b11001000: { mode: celtOnly, bandwidth: superWideBand, frameSize: 5 }, + 0b11010000: { mode: celtOnly, bandwidth: superWideBand, frameSize: 10 }, + 0b11011000: { mode: celtOnly, bandwidth: superWideBand, frameSize: 20 }, + 0b11100000: { mode: celtOnly, bandwidth: fullBand, frameSize: 2.5 }, + 0b11101000: { mode: celtOnly, bandwidth: fullBand, frameSize: 5 }, + 0b11110000: { mode: celtOnly, bandwidth: fullBand, frameSize: 10 }, + 0b11111000: { mode: celtOnly, bandwidth: fullBand, frameSize: 20 }, + }; + + class OpusHeader extends CodecHeader { + static getHeaderFromUint8Array(data, packetData, headerCache) { + const header = {}; + + // get length of header + // Byte (10 of 19) + // * `CCCCCCCC`: Channel Count + header.channels = data[9]; + // Byte (19 of 19) + // * `GGGGGGGG`: Channel Mapping Family + header.channelMappingFamily = data[18]; + + header.length = + header.channelMappingFamily !== 0 ? 21 + header.channels : 19; + + if (data.length < header.length) + throw new Error("Out of data while inside an Ogg Page"); + + // Page Segment Bytes (1-2) + // * `AAAAA...`: Packet config + // * `.....B..`: + // * `......CC`: Packet code + const packetMode = packetData[0] & 0b00000011; + const packetLength = packetMode === 3 ? 2 : 1; + + // Check header cache + const key = + bytesToString(data.subarray(0, header.length)) + + bytesToString(packetData.subarray(0, packetLength)); + const cachedHeader = headerCache.getHeader(key); + + if (cachedHeader) return new OpusHeader(cachedHeader); + + // Bytes (1-8 of 19): OpusHead - Magic Signature + if (key.substr(0, 8) !== "OpusHead") { + return null; + } + + // Byte (9 of 19) + // * `00000001`: Version number + if (data[8] !== 1) return null; + + header.data = Uint8Array.from(data.subarray(0, header.length)); + + const view = new DataView(header.data.buffer); + + header.bitDepth = 16; + + // Byte (10 of 19) + // * `CCCCCCCC`: Channel Count + // set earlier to determine length + + // Byte (11-12 of 19) + // * `DDDDDDDD|DDDDDDDD`: Pre skip + header.preSkip = view.getUint16(10, true); + + // Byte (13-16 of 19) + // * `EEEEEEEE|EEEEEEEE|EEEEEEEE|EEEEEEEE`: Sample Rate + header.inputSampleRate = view.getUint32(12, true); + // Opus is always decoded at 48kHz + header.sampleRate = rate48000; + + // Byte (17-18 of 19) + // * `FFFFFFFF|FFFFFFFF`: Output Gain + header.outputGain = view.getInt16(16, true); + + // Byte (19 of 19) + // * `GGGGGGGG`: Channel Mapping Family + // set earlier to determine length + if (header.channelMappingFamily in channelMappingFamilies) { + header.channelMode = + channelMappingFamilies[header.channelMappingFamily][ + header.channels - 1 + ]; + if (!header.channelMode) return null; + } + + if (header.channelMappingFamily !== 0) { + // * `HHHHHHHH`: Stream count + header.streamCount = data[19]; + + // * `IIIIIIII`: Coupled Stream count + header.coupledStreamCount = data[20]; + + // * `JJJJJJJJ|...` Channel Mapping table + header.channelMappingTable = [...data.subarray(21, header.channels + 21)]; + } + + const packetConfig = configTable[0b11111000 & packetData[0]]; + header.mode = packetConfig.mode; + header.bandwidth = packetConfig.bandwidth; + header.frameSize = packetConfig.frameSize; + + // https://tools.ietf.org/html/rfc6716#appendix-B + switch (packetMode) { + case 0: + // 0: 1 frame in the packet + header.frameCount = 1; + break; + case 1: + // 1: 2 frames in the packet, each with equal compressed size + case 2: + // 2: 2 frames in the packet, with different compressed sizes + header.frameCount = 2; + break; + case 3: + // 3: an arbitrary number of frames in the packet + header.isVbr = Boolean(0b10000000 & packetData[1]); + header.hasOpusPadding = Boolean(0b01000000 & packetData[1]); + header.frameCount = 0b00111111 & packetData[1]; + break; + default: + return null; + } + + // set header cache + const { + length, + data: headerData, + channelMappingFamily, + ...codecUpdateFields + } = header; + + headerCache.setHeader(key, header, codecUpdateFields); + + return new OpusHeader(header); + } + + /** + * @private + * Call OpusHeader.getHeader(Array) to get instance + */ + constructor(header) { + super(header); + + this.data = header.data; + this.bandwidth = header.bandwidth; + this.channelMappingFamily = header.channelMappingFamily; + this.channelMappingTable = header.channelMappingTable; + this.coupledStreamCount = header.coupledStreamCount; + this.frameCount = header.frameCount; + this.frameSize = header.frameSize; + this.hasOpusPadding = header.hasOpusPadding; + this.inputSampleRate = header.inputSampleRate; + this.isVbr = header.isVbr; + this.mode = header.mode; + this.outputGain = header.outputGain; + this.preSkip = header.preSkip; + this.streamCount = header.streamCount; + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class OpusParser extends Parser { + constructor(codecParser, headerCache) { + super(codecParser, headerCache); + this.Frame = OpusFrame; + this.Header = OpusHeader; + + this._identificationHeader = null; + } + + get codec() { + return "opus"; + } + + /** + * @todo implement continued page support + */ + parseOggPage(oggPage) { + if (oggPage.pageSequenceNumber === 0) { + // Identification header + + this._headerCache.enable(); + this._identificationHeader = oggPage.data; + } else if (oggPage.pageSequenceNumber === 1) ; else { + oggPage.codecFrames = frameStore.get(oggPage).segments.map((segment) => { + const header = OpusHeader.getHeaderFromUint8Array( + this._identificationHeader, + segment, + this._headerCache + ); + + if (header) return new OpusFrame(segment, header); + + this._codecParser.logError( + "Failed to parse Ogg Opus Header", + "Not a valid Ogg Opus file" + ); + }); + } + + return oggPage; + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class VorbisFrame extends CodecFrame { + constructor(data, header, samples) { + super(header, data, samples); + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + const blockSizes = { + // 0b0110: 64, + // 0b0111: 128, + // 0b1000: 256, + // 0b1001: 512, + // 0b1010: 1024, + // 0b1011: 2048, + // 0b1100: 4096, + // 0b1101: 8192 + }; + for (let i = 0; i < 8; i++) blockSizes[i + 6] = 2 ** (6 + i); + + class VorbisHeader extends CodecHeader { + static getHeaderFromUint8Array(data, headerCache) { + // Must be at least 30 bytes. + if (data.length < 30) + throw new Error("Out of data while inside an Ogg Page"); + + // Check header cache + const key = bytesToString(data.subarray(0, 30)); + const cachedHeader = headerCache.getHeader(key); + if (cachedHeader) return new VorbisHeader(cachedHeader); + + const header = { length: 30 }; + + // Bytes (1-7 of 30): /01vorbis - Magic Signature + if (key.substr(0, 7) !== "\x01vorbis") { + return null; + } + + header.data = Uint8Array.from(data.subarray(0, 30)); + const view = new DataView(header.data.buffer); + + // Byte (8-11 of 30) + // * `CCCCCCCC|CCCCCCCC|CCCCCCCC|CCCCCCCC`: Version number + header.version = view.getUint32(7, true); + if (header.version !== 0) return null; + + // Byte (12 of 30) + // * `DDDDDDDD`: Channel Count + header.channels = data[11]; + header.channelMode = + vorbisOpusChannelMapping[header.channels - 1] || "application defined"; + + // Byte (13-16 of 30) + // * `EEEEEEEE|EEEEEEEE|EEEEEEEE|EEEEEEEE`: Sample Rate + header.sampleRate = view.getUint32(12, true); + + // Byte (17-20 of 30) + // * `FFFFFFFF|FFFFFFFF|FFFFFFFF|FFFFFFFF`: Bitrate Maximum + header.bitrateMaximum = view.getInt32(16, true); + + // Byte (21-24 of 30) + // * `GGGGGGGG|GGGGGGGG|GGGGGGGG|GGGGGGGG`: Bitrate Nominal + header.bitrateNominal = view.getInt32(20, true); + + // Byte (25-28 of 30) + // * `HHHHHHHH|HHHHHHHH|HHHHHHHH|HHHHHHHH`: Bitrate Minimum + header.bitrateMinimum = view.getInt32(24, true); + + // Byte (29 of 30) + // * `IIII....` Blocksize 1 + // * `....JJJJ` Blocksize 0 + header.blocksize1 = blockSizes[(data[28] & 0b11110000) >> 4]; + header.blocksize0 = blockSizes[data[28] & 0b00001111]; + if (header.blocksize0 > header.blocksize1) return null; + + // Byte (29 of 30) + // * `00000001` Framing bit + if (data[29] !== 0x01) return null; + + header.bitDepth = 32; + + { + // set header cache + const { length, data, version, ...codecUpdateFields } = header; + headerCache.setHeader(key, header, codecUpdateFields); + } + + return new VorbisHeader(header); + } + + /** + * @private + * Call VorbisHeader.getHeader(Array) to get instance + */ + constructor(header) { + super(header); + + this.bitrateMaximum = header.bitrateMaximum; + this.bitrateMinimum = header.bitrateMinimum; + this.bitrateNominal = header.bitrateNominal; + this.blocksize0 = header.blocksize0; + this.blocksize1 = header.blocksize1; + this.data = header.data; + this.vorbisComments = null; // set during ogg parsing + this.vorbisSetup = null; // set during ogg parsing + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class VorbisParser extends Parser { + constructor(codecParser, headerCache) { + super(codecParser, headerCache); + this.Frame = VorbisFrame; + + this._identificationHeader = null; + + this._mode = { + count: 0, + }; + this._prevBlockSize = 0; + this._currBlockSize = 0; + } + + get codec() { + return "vorbis"; + } + + parseOggPage(oggPage) { + const oggPageSegments = frameStore.get(oggPage).segments; + + if (oggPage.pageSequenceNumber === 0) { + // Identification header + + this._headerCache.enable(); + this._identificationHeader = oggPage.data; + } else if (oggPage.pageSequenceNumber === 1) { + // gather WEBM CodecPrivate data + if (oggPageSegments[1]) { + this._vorbisComments = oggPageSegments[0]; + this._vorbisSetup = oggPageSegments[1]; + + this._mode = this._parseSetupHeader(oggPageSegments[1]); + } + } else { + oggPage.codecFrames = oggPageSegments.map((segment) => { + const header = VorbisHeader.getHeaderFromUint8Array( + this._identificationHeader, + this._headerCache + ); + + if (header) { + header.vorbisComments = this._vorbisComments; + header.vorbisSetup = this._vorbisSetup; + + return new VorbisFrame( + segment, + header, + this._getSamples(segment, header) + ); + } + + this._codecParser.logError( + "Failed to parse Ogg Vorbis Header", + "Not a valid Ogg Vorbis file" + ); + }); + } + + return oggPage; + } + + _getSamples(segment, header) { + const byte = segment[0] >> 1; + + const blockFlag = this._mode[byte & this._mode.mask]; + + // is this a large window + if (blockFlag) { + this._prevBlockSize = + byte & this._mode.prevMask ? header.blocksize1 : header.blocksize0; + } + + this._currBlockSize = blockFlag ? header.blocksize1 : header.blocksize0; + + const samples = (this._prevBlockSize + this._currBlockSize) >> 2; + this._prevBlockSize = this._currBlockSize; + + return samples; + } + + // https://gitlab.xiph.org/xiph/liboggz/-/blob/master/src/liboggz/oggz_auto.c + // https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/vorbis_parser.c + /* + * This is the format of the mode data at the end of the packet for all + * Vorbis Version 1 : + * + * [ 6:number_of_modes ] + * [ 1:size | 16:window_type(0) | 16:transform_type(0) | 8:mapping ] + * [ 1:size | 16:window_type(0) | 16:transform_type(0) | 8:mapping ] + * [ 1:size | 16:window_type(0) | 16:transform_type(0) | 8:mapping ] + * [ 1:framing(1) ] + * + * e.g.: + * + * MsB LsB + * <- + * 0 0 0 0 0 1 0 0 + * 0 0 1 0 0 0 0 0 + * 0 0 1 0 0 0 0 0 + * 0 0 1|0 0 0 0 0 + * 0 0 0 0|0|0 0 0 + * 0 0 0 0 0 0 0 0 + * 0 0 0 0|0 0 0 0 + * 0 0 0 0 0 0 0 0 + * 0 0 0 0|0 0 0 0 + * 0 0 0|1|0 0 0 0 | + * 0 0 0 0 0 0 0 0 V + * 0 0 0|0 0 0 0 0 + * 0 0 0 0 0 0 0 0 + * 0 0 1|0 0 0 0 0 + * + * The simplest way to approach this is to start at the end + * and read backwards to determine the mode configuration. + * + * liboggz and ffmpeg both use this method. + */ + _parseSetupHeader(setup) { + const bitReader = new BitReader(setup); + const failedToParseVorbisStream = "Failed to read Vorbis stream"; + const failedToParseVorbisModes = ", failed to parse vorbis modes"; + + let mode = { + count: 0, + }; + + // sync with the framing bit + while ((bitReader.read(1) & 0x01) !== 1) {} + + let modeBits; + // search in reverse to parse out the mode entries + // limit mode count to 63 so previous block flag will be in first packet byte + while (mode.count < 64 && bitReader.position > 0) { + const mapping = reverse(bitReader.read(8)); + if ( + mapping in mode && + !(mode.count === 1 && mapping === 0) // allows for the possibility of only one mode + ) { + this._codecParser.logError( + "received duplicate mode mapping" + failedToParseVorbisModes + ); + throw new Error(failedToParseVorbisStream); + } + + // 16 bits transform type, 16 bits window type, all values must be zero + let i = 0; + while (bitReader.read(8) === 0x00 && i++ < 3) {} // a non-zero value may indicate the end of the mode entries, or invalid data + + if (i === 4) { + // transform type and window type were all zeros + modeBits = bitReader.read(7); // modeBits may need to be used in the next iteration if this is the last mode entry + mode[mapping] = modeBits & 0x01; // read and store mode -> block flag mapping + bitReader.position += 6; // go back 6 bits so next iteration starts right after the block flag + mode.count++; + } else { + // transform type and window type were not all zeros + // check for mode count using previous iteration modeBits + if (((reverse(modeBits) & 0b01111110) >> 1) + 1 !== mode.count) { + this._codecParser.logError( + "mode count did not match actual modes" + failedToParseVorbisModes + ); + throw new Error(failedToParseVorbisStream); + } + + break; + } + } + + // mode mask to read the mode from the first byte in the vorbis frame + mode.mask = (1 << Math.log2(mode.count)) - 1; + // previous window flag is the next bit after the mode mask + mode.prevMask = (mode.mask | 0x1) + 1; + + return mode; + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + class OggParser extends Parser { + constructor(codecParser, headerCache, onCodec) { + super(codecParser, headerCache); + + this._onCodec = onCodec; + this.Frame = OggPage; + this.Header = OggPageHeader; + this._codec = null; + this._continuedPacket = new Uint8Array(); + + this._pageSequenceNumber = 0; + } + + get codec() { + return this._codec || ""; + } + + _updateCodec(codec, Parser) { + if (this._codec !== codec) { + this._parser = new Parser(this._codecParser, this._headerCache); + this._codec = codec; + this._onCodec(codec); + } + } + + _checkForIdentifier({ data }) { + const idString = bytesToString(data.subarray(0, 8)); + + switch (idString) { + case "fishead\0": + case "fisbone\0": + case "index\0\0\0": + return false; // ignore ogg skeleton packets + case "OpusHead": + this._updateCodec("opus", OpusParser); + return true; + case /^\x7fFLAC/.test(idString) && idString: + this._updateCodec("flac", FLACParser); + return true; + case /^\x01vorbis/.test(idString) && idString: + this._updateCodec("vorbis", VorbisParser); + return true; + } + } + + _checkPageSequenceNumber(oggPage) { + if ( + oggPage.pageSequenceNumber !== this._pageSequenceNumber + 1 && + this._pageSequenceNumber > 1 && + oggPage.pageSequenceNumber > 1 + ) { + this._codecParser.logWarning( + "Unexpected gap in Ogg Page Sequence Number.", + `Expected: ${this._pageSequenceNumber + 1}, Got: ${ + oggPage.pageSequenceNumber + }` + ); + } + + this._pageSequenceNumber = oggPage.pageSequenceNumber; + } + + *parseFrame() { + const oggPage = yield* this.fixedLengthFrameSync(true); + + this._checkPageSequenceNumber(oggPage); + + const oggPageStore = frameStore.get(oggPage); + const { pageSegmentBytes, pageSegmentTable } = headerStore.get( + oggPageStore.header + ); + + let offset = 0; + + oggPageStore.segments = pageSegmentTable.map((segmentLength) => + oggPage.data.subarray(offset, (offset += segmentLength)) + ); + + if (pageSegmentBytes[pageSegmentBytes.length - 1] === 0xff) { + // continued packet + this._continuedPacket = concatBuffers( + this._continuedPacket, + oggPageStore.segments.pop() + ); + } else if (this._continuedPacket.length) { + oggPageStore.segments[0] = concatBuffers( + this._continuedPacket, + oggPageStore.segments[0] + ); + + this._continuedPacket = new Uint8Array(); + } + + if (this._codec || this._checkForIdentifier(oggPage)) { + const frame = this._parser.parseOggPage(oggPage); + this._codecParser.mapFrameStats(frame); + return frame; + } + } + } + + /* Copyright 2020-2022 Ethan Halsall + + This file is part of codec-parser. + + codec-parser is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + codec-parser is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see + */ + + const noOp = () => {}; + + class CodecParser { + constructor( + mimeType, + { + onCodecUpdate, + onCodec, + enableLogging = false, + enableFrameCRC32 = true, + } = {} + ) { + this._inputMimeType = mimeType; + this._onCodec = onCodec || noOp; + this._onCodecUpdate = onCodecUpdate; + this._enableLogging = enableLogging; + this._crc32 = enableFrameCRC32 ? crc32 : noOp; + + this._generator = this._getGenerator(); + this._generator.next(); + } + + /** + * @public + * @returns The detected codec + */ + get codec() { + return this._parser.codec; + } + + /** + * @public + * @description Generator function that yields any buffered CodecFrames and resets the CodecParser + * @returns {Iterable} Iterator that operates over the codec data. + * @yields {CodecFrame|OggPage} Parsed codec or ogg page data + */ + *flush() { + this._flushing = true; + + for (let i = this._generator.next(); i.value; i = this._generator.next()) { + yield i.value; + } + + this._flushing = false; + + this._generator = this._getGenerator(); + this._generator.next(); + } + + /** + * @public + * @description Generator function takes in a Uint8Array of data and returns a CodecFrame from the data for each iteration + * @param {Uint8Array} chunk Next chunk of codec data to read + * @returns {Iterable} Iterator that operates over the codec data. + * @yields {CodecFrame|OggPage} Parsed codec or ogg page data + */ + *parseChunk(chunk) { + for ( + let i = this._generator.next(chunk); + i.value; + i = this._generator.next() + ) { + yield i.value; + } + } + + /** + * @public + * @description Parses an entire file and returns all of the contained frames. + * @param {Uint8Array} fileData Coded data to read + * @returns {Array} CodecFrames + */ + parseAll(fileData) { + return [...this.parseChunk(fileData), ...this.flush()]; + } + + /** + * @private + */ + *_getGenerator() { + this._headerCache = new HeaderCache(this._onCodecUpdate); + + if (this._inputMimeType.match(/aac/)) { + this._parser = new AACParser(this, this._headerCache, this._onCodec); + } else if (this._inputMimeType.match(/mpeg/)) { + this._parser = new MPEGParser(this, this._headerCache, this._onCodec); + } else if (this._inputMimeType.match(/flac/)) { + this._parser = new FLACParser(this, this._headerCache, this._onCodec); + } else if (this._inputMimeType.match(/ogg/)) { + this._parser = new OggParser(this, this._headerCache, this._onCodec); + } else { + throw new Error(`Unsupported Codec ${mimeType}`); + } + + this._frameNumber = 0; + this._currentReadPosition = 0; + this._totalBytesIn = 0; + this._totalBytesOut = 0; + this._totalSamples = 0; + this._sampleRate = undefined; + + this._rawData = new Uint8Array(0); + + // start parsing out frames + while (true) { + const frame = yield* this._parser.parseFrame(); + if (frame) yield frame; + } + } + + /** + * @protected + * @param {number} minSize Minimum bytes to have present in buffer + * @returns {Uint8Array} rawData + */ + *readRawData(minSize = 0, readOffset = 0) { + let rawData; + + while (this._rawData.length <= minSize + readOffset) { + rawData = yield; + + if (this._flushing) return this._rawData.subarray(readOffset); + + if (rawData) { + this._totalBytesIn += rawData.length; + this._rawData = concatBuffers(this._rawData, rawData); + } + } + + return this._rawData.subarray(readOffset); + } + + /** + * @protected + * @param {number} increment Bytes to increment codec data + */ + incrementRawData(increment) { + this._currentReadPosition += increment; + this._rawData = this._rawData.subarray(increment); + } + + /** + * @protected + */ + mapCodecFrameStats(frame) { + this._sampleRate = frame.header.sampleRate; + + frame.header.bitrate = Math.round(frame.data.length / frame.duration) * 8; + frame.frameNumber = this._frameNumber++; + frame.totalBytesOut = this._totalBytesOut; + frame.totalSamples = this._totalSamples; + frame.totalDuration = (this._totalSamples / this._sampleRate) * 1000; + frame.crc32 = this._crc32(frame.data); + + this._headerCache.checkCodecUpdate( + frame.header.bitrate, + frame.totalDuration + ); + + this._totalBytesOut += frame.data.length; + this._totalSamples += frame.samples; + } + + /** + * @protected + */ + mapFrameStats(frame) { + if (frame.codecFrames) { + // Ogg container + frame.codecFrames.forEach((codecFrame) => { + frame.duration += codecFrame.duration; + frame.samples += codecFrame.samples; + this.mapCodecFrameStats(codecFrame); + }); + + frame.totalSamples = this._totalSamples; + frame.totalDuration = (this._totalSamples / this._sampleRate) * 1000 || 0; + frame.totalBytesOut = this._totalBytesOut; + } else { + this.mapCodecFrameStats(frame); + } + } + + /** + * @private + */ + _log(logger, messages) { + if (this._enableLogging) { + const stats = [ + `codec: ${this.codec}`, + `inputMimeType: ${this._inputMimeType}`, + `readPosition: ${this._currentReadPosition}`, + `totalBytesIn: ${this._totalBytesIn}`, + `totalBytesOut: ${this._totalBytesOut}`, + ]; + + const width = Math.max(...stats.map((s) => s.length)); + + messages.push( + `--stats--${"-".repeat(width - 9)}`, + ...stats, + "-".repeat(width) + ); + + logger( + "codec-parser", + messages.reduce((acc, message) => acc + "\n " + message, "") + ); + } + } + + /** + * @protected + */ + logWarning(...messages) { + this._log(console.warn, messages); + } + + /** + * @protected + */ + logError(...messages) { + this._log(console.error, messages); + } + } + + class DecoderState { + constructor(instance) { + this._instance = instance; + + this._decoderOperations = []; + this._errors = []; + this._decoded = []; + this._channelsDecoded = 0; + this._totalSamples = 0; + } + + get decoded() { + return this._instance.ready + .then(() => Promise.all(this._decoderOperations)) + .then(() => [ + this._errors, + this._decoded, + this._channelsDecoded, + this._totalSamples, + 48000, + ]); + } + + async _instantiateDecoder(header) { + this._instance._decoder = new this._instance._decoderClass({ + ...header, + forceStereo: this._instance._forceStereo, + }); + this._instance._ready = this._instance._decoder.ready; + } + + async _sendToDecoder(frames) { + const { channelData, samplesDecoded, errors } = + await this._instance._decoder.decodeFrames(frames); + + this._decoded.push(channelData); + this._errors = this._errors.concat(errors); + this._totalSamples += samplesDecoded; + this._channelsDecoded = channelData.length; + } + + async _decode(codecFrames) { + if (codecFrames.length) { + if (!this._instance._decoder && codecFrames[0].header) + this._instantiateDecoder(codecFrames[0].header); + + await this._instance.ready; + + this._decoderOperations.push( + this._sendToDecoder(codecFrames.map((f) => f.data)) + ); + } + } + } + + class OggOpusDecoder { + constructor(options = {}) { + this._forceStereo = + options.forceStereo !== undefined ? options.forceStereo : false; + + this._onCodec = (codec) => { + if (codec !== "opus") + throw new Error( + "ogg-opus-decoder does not support this codec " + codec + ); + }; + + // instantiate to create static properties + new WASMAudioDecoderCommon(); + this._decoderClass = OpusDecoder; + + this._init(); + } + + _init() { + if (this._decoder) this._decoder.free(); + this._decoder = null; + this._ready = Promise.resolve(); + + this._codecParser = new CodecParser("application/ogg", { + onCodec: this._onCodec, + enableFrameCRC32: false, + }); + } + + get ready() { + return this._ready; + } + + async reset() { + this._init(); + } + + free() { + this._init(); + } + + async _flush(decoderState) { + for (const { codecFrames } of this._codecParser.flush()) { + decoderState._decode(codecFrames); + } + + const decoded = await decoderState.decoded; + this._init(); + + return decoded; + } + + async _decode(oggOpusData, decoderState) { + for (const { codecFrames } of this._codecParser.parseChunk(oggOpusData)) { + decoderState._decode(codecFrames); + } + + return decoderState.decoded; + } + + async decode(oggOpusData) { + return WASMAudioDecoderCommon.getDecodedAudioMultiChannel( + ...(await this._decode(oggOpusData, new DecoderState(this))) + ); + } + + async decodeFile(oggOpusData) { + const decoderState = new DecoderState(this); + + return WASMAudioDecoderCommon.getDecodedAudioMultiChannel( + ...(await this._decode(oggOpusData, decoderState).then(() => + this._flush(decoderState) + )) + ); + } + + async flush() { + return WASMAudioDecoderCommon.getDecodedAudioMultiChannel( + ...(await this._flush(new DecoderState(this))) + ); + } + } + + class OggOpusDecoderWebWorker extends OggOpusDecoder { + constructor(options) { + super(options); + + this._decoderClass = OpusDecoderWebWorker; + } + + async free() { + super.free(); + } + } + + exports.OggOpusDecoder = OggOpusDecoder; + exports.OggOpusDecoderWebWorker = OggOpusDecoderWebWorker; + +})); diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/audio/ogg-opus-decoder/ogg-opus-decoder.min.js b/ui/src/phaser3-rex-plugins/plugins/utils/audio/ogg-opus-decoder/ogg-opus-decoder.min.js new file mode 100644 index 0000000000000000000000000000000000000000..23e4b2b7b7e5f8c10491bbc435fbf46358a4a016 GIT binary patch literal 107469 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 0; i--) { + this.soundEffects[i].destroy(); + } + } + this.soundEffects.length = 0; + + if (this.soundEffects2.length) { + for (var i = this.soundEffects2.length - 1; i >= 0; i--) { + this.soundEffects2[i].destroy(); + } + } + this.soundEffects2.length = 0; + + this.sound = undefined; + + return this; + } + + get backgroundMusicVolume() { + return this._backgroundMusicVolume; + } + + set backgroundMusicVolume(value) { + this._backgroundMusicVolume = value; + if (this.backgroundMusic) { + this.backgroundMusic.setVolume(value); + } + } + + get backgroundMusic2Volume() { + return this._backgroundMusic2Volume; + } + + set backgroundMusic2Volume(value) { + this._backgroundMusic2Volume = value; + if (this.backgroundMusic2) { + this.backgroundMusic2.setVolume(value); + } + } + + get soundEffectsVolume() { + return this._soundEffectsVolume; + } + + set soundEffectsVolume(value) { + this._soundEffectsVolume = value; + var soundEffects = this.soundEffects; + for (var i = 0, cnt = soundEffects.length; i < cnt; i++) { + soundEffects[i].setVolume(value); + } + } + +} + +Object.assign( + SoundManager.prototype, + Methods +) + +export default SoundManager; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/BackgroundMusic2Methods.js b/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/BackgroundMusic2Methods.js new file mode 100644 index 000000000..238461cc5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/BackgroundMusic2Methods.js @@ -0,0 +1,124 @@ +import FadeIn from '../../../../audio/fade/FadeIn.js'; +import FadeOut from '../../../../audio/fade/FadeOut.js'; + +export default { + setBackgroundMusic2LoopValue(value) { + this.backgroundMusic2LoopValue = value; + return this; + }, + + setBackgroundMusic2FadeTime(time) { + this.backgroundMusic2FadeTime = time; + return this; + }, + + getBackgroundMusic2() { + return this.backgroundMusic2; + }, + + // Internal method + setCurrentBackgroundMusic2(music) { + this.backgroundMusic2 = music; + + if (music) { + music.setLoop(this.backgroundMusic2LoopValue); + music + .once('complete', function () { + if (this.backgroundMusic2 === music) { + this.backgroundMusic2.destroy(); + this.backgroundMusic2 = undefined; + } + }, this) + .once('destroy', function () { + if (this.backgroundMusic2 === music) { + this.backgroundMusic2 = undefined; + } + }, this) + + if (!music.isPlaying) { + music.play(); + } + } + return this; + }, + + playBackgroundMusic2(key) { + // Don't re-play the same background music + if (this.backgroundMusic2 && (this.backgroundMusic2.key === key)) { + return this; + } + + this.stopBackgroundMusic2(); // Stop previous background music + + this.setCurrentBackgroundMusic2(this.sound.add(key)); + + if (this.backgroundMusic2FadeTime > 0) { + this.fadeInBackgroundMusic2(this.backgroundMusic2FadeTime); + } + return this; + }, + + pauseBackgroundMusic2() { + if (this.backgroundMusic2) { + this.backgroundMusic2.pause(); + } + return this; + }, + + resumeBackgroundMusic2() { + if (this.backgroundMusic2) { + this.backgroundMusic2.resume(); + } + return this; + }, + + stopBackgroundMusic2() { + if (this.backgroundMusic2) { + if (this.backgroundMusic2FadeTime > 0) { + this.fadeOutBackgroundMusic2(this.backgroundMusic2FadeTime, true); + + } else { + this.backgroundMusic2.stop(); + this.backgroundMusic2.destroy(); + this.backgroundMusic2 = undefined; + } + } + return this; + }, + + fadeInBackgroundMusic2(time) { + if (this.backgroundMusic2) { + FadeIn(this.backgroundMusic2, time, this.backgroundMusic2Volume, 0); + } + + return this; + }, + + fadeOutBackgroundMusic2(time, isStopped) { + if (this.backgroundMusic2) { + FadeOut(this.backgroundMusic2, time, isStopped); + } + + return this; + }, + + crossFadeBackgroundMusic2(key, time) { + var backgroundMusic2FadeTimeSave = this.backgroundMusic2FadeTime; + this.backgroundMusic2FadeTime = 0; + + this + .fadeOutBackgroundMusic2(time, true) + .playBackgroundMusic2(key) + .fadeInBackgroundMusic2(time); + + this.backgroundMusic2FadeTime = backgroundMusic2FadeTimeSave; + + return this; + }, + + setBackgroundMusic2Volume(volume) { + this.backgroundMusic2Volume = volume; + return this; + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/BackgroundMusicMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/BackgroundMusicMethods.js new file mode 100644 index 000000000..f1d4f974b --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/BackgroundMusicMethods.js @@ -0,0 +1,124 @@ +import FadeIn from '../../../../audio/fade/FadeIn.js'; +import FadeOut from '../../../../audio/fade/FadeOut.js'; + +export default { + setBackgroundMusicLoopValue(value) { + this.backgroundMusicLoopValue = value; + return this; + }, + + setBackgroundMusicFadeTime(time) { + this.backgroundMusicFadeTime = time; + return this; + }, + + getBackgroundMusic() { + return this.backgroundMusic; + }, + + // Internal method + setCurrentBackgroundMusic(music) { + this.backgroundMusic = music; + + if (music) { + music.setLoop(this.backgroundMusicLoopValue); + music + .once('complete', function () { + if (this.backgroundMusic === music) { + this.backgroundMusic.destroy(); + this.backgroundMusic = undefined; + } + }, this) + .once('destroy', function () { + if (this.backgroundMusic === music) { + this.backgroundMusic = undefined; + } + }, this) + + if (!music.isPlaying) { + music.play(); + } + } + return this; + }, + + playBackgroundMusic(key) { + // Don't re-play the same background music + if (this.backgroundMusic && (this.backgroundMusic.key === key)) { + return this; + } + + this.stopBackgroundMusic(); // Stop previous background music + + this.setCurrentBackgroundMusic(this.sound.add(key)); + + if (this.backgroundMusicFadeTime > 0) { + this.fadeInBackgroundMusic(this.backgroundMusicFadeTime); + } + return this; + }, + + pauseBackgroundMusic() { + if (this.backgroundMusic) { + this.backgroundMusic.pause(); + } + return this; + }, + + resumeBackgroundMusic() { + if (this.backgroundMusic) { + this.backgroundMusic.resume(); + } + return this; + }, + + stopBackgroundMusic() { + if (this.backgroundMusic) { + if (this.backgroundMusicFadeTime > 0) { + this.fadeOutBackgroundMusic(this.backgroundMusicFadeTime, true); + + } else { + this.backgroundMusic.stop(); + this.backgroundMusic.destroy(); + this.backgroundMusic = undefined; + } + } + return this; + }, + + fadeInBackgroundMusic(time) { + if (this.backgroundMusic) { + FadeIn(this.backgroundMusic, time, this.backgroundMusicVolume, 0); + } + + return this; + }, + + fadeOutBackgroundMusic(time, isStopped) { + if (this.backgroundMusic) { + FadeOut(this.backgroundMusic, time, isStopped); + } + + return this; + }, + + crossFadeBackgroundMusic(key, time) { + var backgroundMusicFadeTimeSave = this.backgroundMusicFadeTime; + this.backgroundMusicFadeTime = 0; + + this + .fadeOutBackgroundMusic(time, true) + .playBackgroundMusic(key) + .fadeInBackgroundMusic(time); + + this.backgroundMusicFadeTime = backgroundMusicFadeTimeSave; + + return this; + }, + + setBackgroundMusicVolume(volume) { + this.backgroundMusicVolume = volume; + return this; + } + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/Methods.js b/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/Methods.js new file mode 100644 index 000000000..1100cce1d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/Methods.js @@ -0,0 +1,14 @@ +import BackgroundMusicMethods from './BackgroundMusicMethods.js'; +import BackgroundMusic2Methods from './BackgroundMusic2Methods.js'; +import SoundEffectsMethods from './SoundEffectsMethods.js'; +import SoundEffects2Methods from './SoundEffects2Methods.js'; + +var Methods = {}; + +Object.assign( + Methods, + BackgroundMusicMethods, BackgroundMusic2Methods, + SoundEffectsMethods, SoundEffects2Methods, +) + +export default Methods; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/SoundEffects2Methods.js b/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/SoundEffects2Methods.js new file mode 100644 index 000000000..3ab38780a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/SoundEffects2Methods.js @@ -0,0 +1,90 @@ +import FadeIn from '../../../../audio/fade/FadeIn.js'; +import FadeOut from '../../../../audio/fade/FadeOut.js'; + +const RemoveItem = Phaser.Utils.Array.Remove; + +export default { + + getSoundEffects2() { + return this.soundEffects2; + }, + + getLastSoundEffect2() { + return this.soundEffects2[this.soundEffects2.length - 1]; + }, + + playSoundEffect2(key) { + var soundEffect = this.sound.add(key); + soundEffect.setVolume(this.soundEffects2Volume); + + this.soundEffects2.push(soundEffect); + + soundEffect + .once('complete', function () { + soundEffect.destroy(); + + // SoundManager has been destroyed + if (!this.sound) { + return; + } + RemoveItem(this.soundEffects2, soundEffect); + }, this) + .once('destroy', function () { + // SoundManager has been destroyed + if (!this.sound) { + return; + } + RemoveItem(this.soundEffects2, soundEffect); + }, this) + .play(); + + return this; + }, + + fadeInSoundEffect2(time) { + var soundEffect = this.getLastSoundEffect2(); + if (soundEffect) { + FadeIn(soundEffect, time, this.soundEffects2Volume, 0); + } + + return this; + }, + + fadeOutSoundEffect2(time, isStopped) { + var soundEffect = this.getLastSoundEffect2(); + if (soundEffect) { + FadeOut(soundEffect, time, isStopped); + } + + return this; + }, + + fadeOutAllSoundEffects2(time, isStopped) { + for (var i = this.soundEffects2.length - 1; i >= 0; i--) { + FadeOut(this.soundEffects2[i], time, isStopped); + } + + return this; + }, + + setSoundEffect2Volume(volume, lastSoundEffect) { + if (lastSoundEffect === undefined) { + lastSoundEffect = false; + } + + if (lastSoundEffect) { + // Set volume of last sound effect + var soundEffect = this.getLastSoundEffect2(); + if (soundEffect) { + soundEffect.setVolume(volume); + } + + } else { + // Set volume of all sound effects + this.soundEffects2Volume = volume; + } + + return this; + }, + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/SoundEffectsMethods.js b/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/SoundEffectsMethods.js new file mode 100644 index 000000000..b24ad895d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/audio/soundmanager/methods/SoundEffectsMethods.js @@ -0,0 +1,90 @@ +import FadeIn from '../../../../audio/fade/FadeIn.js'; +import FadeOut from '../../../../audio/fade/FadeOut.js'; + +const RemoveItem = Phaser.Utils.Array.Remove; + +export default { + + getSoundEffects() { + return this.soundEffects; + }, + + getLastSoundEffect() { + return this.soundEffects[this.soundEffects.length - 1]; + }, + + playSoundEffect(key) { + var soundEffect = this.sound.add(key); + soundEffect.setVolume(this.soundEffectsVolume); + + this.soundEffects.push(soundEffect); + + soundEffect + .once('complete', function () { + soundEffect.destroy(); + + // SoundManager has been destroyed + if (!this.sound) { + return; + } + RemoveItem(this.soundEffects, soundEffect); + }, this) + .once('destroy', function () { + // SoundManager has been destroyed + if (!this.sound) { + return; + } + RemoveItem(this.soundEffects, soundEffect); + }, this) + .play(); + + return this; + }, + + fadeInSoundEffect(time) { + var soundEffect = this.getLastSoundEffect(); + if (soundEffect) { + FadeIn(soundEffect, time, this.soundEffectsVolume, 0); + } + + return this; + }, + + fadeOutSoundEffect(time, isStopped) { + var soundEffect = this.getLastSoundEffect(); + if (soundEffect) { + FadeOut(soundEffect, time, isStopped); + } + + return this; + }, + + fadeOutAllSoundEffects(time, isStopped) { + for (var i = this.soundEffects.length - 1; i >= 0; i--) { + FadeOut(this.soundEffects[i], time, isStopped); + } + + return this; + }, + + setSoundEffectVolume(volume, lastSoundEffect) { + if (lastSoundEffect === undefined) { + lastSoundEffect = false; + } + + if (lastSoundEffect) { + // Set volume of last sound effect + var soundEffect = this.getLastSoundEffect(); + if (soundEffect) { + soundEffect.setVolume(volume); + } + + } else { + // Set volume of all sound effects + this.soundEffectsVolume = volume; + } + + return this; + }, + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/bitmaptext/IsBitmapTextGameObject.js b/ui/src/phaser3-rex-plugins/plugins/utils/bitmaptext/IsBitmapTextGameObject.js new file mode 100644 index 000000000..608746c4c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/bitmaptext/IsBitmapTextGameObject.js @@ -0,0 +1,7 @@ +const BitmapTextKlass = Phaser.GameObjects.BitmapText; + +var IsBitmapTextGameObject = function (gameObject) { + return (gameObject instanceof BitmapTextKlass); +} + +export default IsBitmapTextGameObject; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/bitmaptext/WrapText.js b/ui/src/phaser3-rex-plugins/plugins/utils/bitmaptext/WrapText.js new file mode 100644 index 000000000..867097f63 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/bitmaptext/WrapText.js @@ -0,0 +1,22 @@ +var WrapText = function (content, lineWidth, characterWidth) { + var characterCount = Math.floor(lineWidth / characterWidth); + var segments = content.split('\n'); + var lines = []; + for (var i = 0, cnt = segments.length; i < cnt; i++) { + SplitTextByLength(segments[i], characterCount, lines); + } + + return lines.join('\n'); +} + +var SplitTextByLength = function (text, len, out) { + if (out === undefined) { + out = []; + } + for (var i = 0, cnt = text.length; i < cnt; i += len) { + out.push(text.substring(i, i + len)); + } + return out; +} + +export default WrapText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/blob-util/blob-util.js b/ui/src/phaser3-rex-plugins/plugins/utils/blob-util/blob-util.js new file mode 100644 index 000000000..b73e75c6d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/blob-util/blob-util.js @@ -0,0 +1,460 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (factory((global.blobUtil = {}))); +}(this, (function (exports) { 'use strict'; + + // TODO: including these in blob-util.ts causes typedoc to generate docs for them, + // even with --excludePrivate ¯\_(ツ)_/¯ + /** @private */ + function loadImage(src, crossOrigin) { + return new Promise(function (resolve, reject) { + var img = new Image(); + if (crossOrigin) { + img.crossOrigin = crossOrigin; + } + img.onload = function () { + resolve(img); + }; + img.onerror = reject; + img.src = src; + }); + } + /** @private */ + function imgToCanvas(img) { + var canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + // copy the image contents to the canvas + var context = canvas.getContext('2d', { willReadFrequently: true }); + context.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height); + return canvas; + } + + /* global Promise, Image, Blob, FileReader, atob, btoa, + BlobBuilder, MSBlobBuilder, MozBlobBuilder, WebKitBlobBuilder, webkitURL */ + /** + * Shim for + * {@link https://developer.mozilla.org/en-US/docs/Web/API/Blob.Blob | new Blob()} + * to support + * {@link http://caniuse.com/blob | older browsers that use the deprecated BlobBuilder API}. + * + * Example: + * + * ```js + * var myBlob = blobUtil.createBlob(['hello world'], {type: 'text/plain'}); + * ``` + * + * @param parts - content of the Blob + * @param properties - usually {type: myContentType}, + * you can also pass a string for the content type + * @returns Blob + */ + function createBlob(parts, properties) { + parts = parts || []; + properties = properties || {}; + if (typeof properties === 'string') { + properties = { type: properties }; // infer content type + } + try { + return new Blob(parts, properties); + } + catch (e) { + if (e.name !== 'TypeError') { + throw e; + } + var Builder = typeof BlobBuilder !== 'undefined' + ? BlobBuilder : typeof MSBlobBuilder !== 'undefined' + ? MSBlobBuilder : typeof MozBlobBuilder !== 'undefined' + ? MozBlobBuilder : WebKitBlobBuilder; + var builder = new Builder(); + for (var i = 0; i < parts.length; i += 1) { + builder.append(parts[i]); + } + return builder.getBlob(properties.type); + } + } + /** + * Shim for + * {@link https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL | URL.createObjectURL()} + * to support browsers that only have the prefixed + * webkitURL (e.g. Android <4.4). + * + * Example: + * + * ```js + * var myUrl = blobUtil.createObjectURL(blob); + * ``` + * + * @param blob + * @returns url + */ + function createObjectURL(blob) { + return (typeof URL !== 'undefined' ? URL : webkitURL).createObjectURL(blob); + } + /** + * Shim for + * {@link https://developer.mozilla.org/en-US/docs/Web/API/URL.revokeObjectURL | URL.revokeObjectURL()} + * to support browsers that only have the prefixed + * webkitURL (e.g. Android <4.4). + * + * Example: + * + * ```js + * blobUtil.revokeObjectURL(myUrl); + * ``` + * + * @param url + */ + function revokeObjectURL(url) { + return (typeof URL !== 'undefined' ? URL : webkitURL).revokeObjectURL(url); + } + /** + * Convert a Blob to a binary string. + * + * Example: + * + * ```js + * blobUtil.blobToBinaryString(blob).then(function (binaryString) { + * // success + * }).catch(function (err) { + * // error + * }); + * ``` + * + * @param blob + * @returns Promise that resolves with the binary string + */ + function blobToBinaryString(blob) { + return new Promise(function (resolve, reject) { + var reader = new FileReader(); + var hasBinaryString = typeof reader.readAsBinaryString === 'function'; + reader.onloadend = function () { + var result = reader.result || ''; + if (hasBinaryString) { + return resolve(result); + } + resolve(arrayBufferToBinaryString(result)); + }; + reader.onerror = reject; + if (hasBinaryString) { + reader.readAsBinaryString(blob); + } + else { + reader.readAsArrayBuffer(blob); + } + }); + } + /** + * Convert a base64-encoded string to a Blob. + * + * Example: + * + * ```js + * var blob = blobUtil.base64StringToBlob(base64String); + * ``` + * @param base64 - base64-encoded string + * @param type - the content type (optional) + * @returns Blob + */ + function base64StringToBlob(base64, type) { + var parts = [binaryStringToArrayBuffer(atob(base64))]; + return type ? createBlob(parts, { type: type }) : createBlob(parts); + } + /** + * Convert a binary string to a Blob. + * + * Example: + * + * ```js + * var blob = blobUtil.binaryStringToBlob(binaryString); + * ``` + * + * @param binary - binary string + * @param type - the content type (optional) + * @returns Blob + */ + function binaryStringToBlob(binary, type) { + return base64StringToBlob(btoa(binary), type); + } + /** + * Convert a Blob to a binary string. + * + * Example: + * + * ```js + * blobUtil.blobToBase64String(blob).then(function (base64String) { + * // success + * }).catch(function (err) { + * // error + * }); + * ``` + * + * @param blob + * @returns Promise that resolves with the binary string + */ + function blobToBase64String(blob) { + return blobToBinaryString(blob).then(btoa); + } + /** + * Convert a data URL string + * (e.g. 'data:image/png;base64,iVBORw0KG...') + * to a Blob. + * + * Example: + * + * ```js + * var blob = blobUtil.dataURLToBlob(dataURL); + * ``` + * + * @param dataURL - dataURL-encoded string + * @returns Blob + */ + function dataURLToBlob(dataURL) { + var type = dataURL.match(/data:([^;]+)/)[1]; + var base64 = dataURL.replace(/^[^,]+,/, ''); + var buff = binaryStringToArrayBuffer(atob(base64)); + return createBlob([buff], { type: type }); + } + /** + * Convert a Blob to a data URL string + * (e.g. 'data:image/png;base64,iVBORw0KG...'). + * + * Example: + * + * ```js + * var dataURL = blobUtil.blobToDataURL(blob); + * ``` + * + * @param blob + * @returns Promise that resolves with the data URL string + */ + function blobToDataURL(blob) { + return blobToBase64String(blob).then(function (base64String) { + return 'data:' + blob.type + ';base64,' + base64String; + }); + } + /** + * Convert an image's src URL to a data URL by loading the image and painting + * it to a canvas. + * + * Note: this will coerce the image to the desired content type, and it + * will only paint the first frame of an animated GIF. + * + * Examples: + * + * ```js + * blobUtil.imgSrcToDataURL('http://mysite.com/img.png').then(function (dataURL) { + * // success + * }).catch(function (err) { + * // error + * }); + * ``` + * + * ```js + * blobUtil.imgSrcToDataURL('http://some-other-site.com/img.jpg', 'image/jpeg', + * 'Anonymous', 1.0).then(function (dataURL) { + * // success + * }).catch(function (err) { + * // error + * }); + * ``` + * + * @param src - image src + * @param type - the content type (optional, defaults to 'image/png') + * @param crossOrigin - for CORS-enabled images, set this to + * 'Anonymous' to avoid "tainted canvas" errors + * @param quality - a number between 0 and 1 indicating image quality + * if the requested type is 'image/jpeg' or 'image/webp' + * @returns Promise that resolves with the data URL string + */ + function imgSrcToDataURL(src, type, crossOrigin, quality) { + type = type || 'image/png'; + return loadImage(src, crossOrigin).then(imgToCanvas).then(function (canvas) { + return canvas.toDataURL(type, quality); + }); + } + /** + * Convert a canvas to a Blob. + * + * Examples: + * + * ```js + * blobUtil.canvasToBlob(canvas).then(function (blob) { + * // success + * }).catch(function (err) { + * // error + * }); + * ``` + * + * Most browsers support converting a canvas to both `'image/png'` and `'image/jpeg'`. You may + * also want to try `'image/webp'`, which will work in some browsers like Chrome (and in other browsers, will just fall back to `'image/png'`): + * + * ```js + * blobUtil.canvasToBlob(canvas, 'image/webp').then(function (blob) { + * // success + * }).catch(function (err) { + * // error + * }); + * ``` + * + * @param canvas - HTMLCanvasElement + * @param type - the content type (optional, defaults to 'image/png') + * @param quality - a number between 0 and 1 indicating image quality + * if the requested type is 'image/jpeg' or 'image/webp' + * @returns Promise that resolves with the Blob + */ + function canvasToBlob(canvas, type, quality) { + if (typeof canvas.toBlob === 'function') { + return new Promise(function (resolve) { + canvas.toBlob(resolve, type, quality); + }); + } + return Promise.resolve(dataURLToBlob(canvas.toDataURL(type, quality))); + } + /** + * Convert an image's src URL to a Blob by loading the image and painting + * it to a canvas. + * + * Note: this will coerce the image to the desired content type, and it + * will only paint the first frame of an animated GIF. + * + * Examples: + * + * ```js + * blobUtil.imgSrcToBlob('http://mysite.com/img.png').then(function (blob) { + * // success + * }).catch(function (err) { + * // error + * }); + * ``` + * + * ```js + * blobUtil.imgSrcToBlob('http://some-other-site.com/img.jpg', 'image/jpeg', + * 'Anonymous', 1.0).then(function (blob) { + * // success + * }).catch(function (err) { + * // error + * }); + * ``` + * + * @param src - image src + * @param type - the content type (optional, defaults to 'image/png') + * @param crossOrigin - for CORS-enabled images, set this to + * 'Anonymous' to avoid "tainted canvas" errors + * @param quality - a number between 0 and 1 indicating image quality + * if the requested type is 'image/jpeg' or 'image/webp' + * @returns Promise that resolves with the Blob + */ + function imgSrcToBlob(src, type, crossOrigin, quality) { + type = type || 'image/png'; + return loadImage(src, crossOrigin).then(imgToCanvas).then(function (canvas) { + return canvasToBlob(canvas, type, quality); + }); + } + /** + * Convert an ArrayBuffer to a Blob. + * + * Example: + * + * ```js + * var blob = blobUtil.arrayBufferToBlob(arrayBuff, 'audio/mpeg'); + * ``` + * + * @param buffer + * @param type - the content type (optional) + * @returns Blob + */ + function arrayBufferToBlob(buffer, type) { + return createBlob([buffer], type); + } + /** + * Convert a Blob to an ArrayBuffer. + * + * Example: + * + * ```js + * blobUtil.blobToArrayBuffer(blob).then(function (arrayBuff) { + * // success + * }).catch(function (err) { + * // error + * }); + * ``` + * + * @param blob + * @returns Promise that resolves with the ArrayBuffer + */ + function blobToArrayBuffer(blob) { + return new Promise(function (resolve, reject) { + var reader = new FileReader(); + reader.onloadend = function () { + var result = reader.result || new ArrayBuffer(0); + resolve(result); + }; + reader.onerror = reject; + reader.readAsArrayBuffer(blob); + }); + } + /** + * Convert an ArrayBuffer to a binary string. + * + * Example: + * + * ```js + * var myString = blobUtil.arrayBufferToBinaryString(arrayBuff) + * ``` + * + * @param buffer - array buffer + * @returns binary string + */ + function arrayBufferToBinaryString(buffer) { + var binary = ''; + var bytes = new Uint8Array(buffer); + var length = bytes.byteLength; + var i = -1; + while (++i < length) { + binary += String.fromCharCode(bytes[i]); + } + return binary; + } + /** + * Convert a binary string to an ArrayBuffer. + * + * ```js + * var myBuffer = blobUtil.binaryStringToArrayBuffer(binaryString) + * ``` + * + * @param binary - binary string + * @returns array buffer + */ + function binaryStringToArrayBuffer(binary) { + var length = binary.length; + var buf = new ArrayBuffer(length); + var arr = new Uint8Array(buf); + var i = -1; + while (++i < length) { + arr[i] = binary.charCodeAt(i); + } + return buf; + } + + exports.createBlob = createBlob; + exports.createObjectURL = createObjectURL; + exports.revokeObjectURL = revokeObjectURL; + exports.blobToBinaryString = blobToBinaryString; + exports.base64StringToBlob = base64StringToBlob; + exports.binaryStringToBlob = binaryStringToBlob; + exports.blobToBase64String = blobToBase64String; + exports.dataURLToBlob = dataURLToBlob; + exports.blobToDataURL = blobToDataURL; + exports.imgSrcToDataURL = imgSrcToDataURL; + exports.canvasToBlob = canvasToBlob; + exports.imgSrcToBlob = imgSrcToBlob; + exports.arrayBufferToBlob = arrayBufferToBlob; + exports.blobToArrayBuffer = blobToArrayBuffer; + exports.arrayBufferToBinaryString = arrayBufferToBinaryString; + exports.binaryStringToArrayBuffer = binaryStringToArrayBuffer; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/blob-util/blob-util.min.js b/ui/src/phaser3-rex-plugins/plugins/utils/blob-util/blob-util.min.js new file mode 100644 index 000000000..2d7b92527 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/blob-util/blob-util.min.js @@ -0,0 +1 @@ +!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n(e.blobUtil={})}(this,function(e){"use strict";function n(e,n){return new Promise(function(r,t){var o=new Image;n&&(o.crossOrigin=n),o.onload=function(){r(o)},o.onerror=t,o.src=e})}function r(e){var n=document.createElement("canvas");return n.width=e.width,n.height=e.height,n.getContext("2d", { willReadFrequently: true }).drawImage(e,0,0,e.width,e.height,0,0,e.width,e.height),n}function t(e,n){e=e||[],"string"==typeof(n=n||{})&&(n={type:n});try{return new Blob(e,n)}catch(i){if("TypeError"!==i.name)throw i;for(var r="undefined"!=typeof BlobBuilder?BlobBuilder:"undefined"!=typeof MSBlobBuilder?MSBlobBuilder:"undefined"!=typeof MozBlobBuilder?MozBlobBuilder:WebKitBlobBuilder,t=new r,o=0;o top-right + line = lines[0]; + GetTopLeft(gameObject, line.p0); + GetTopRight(gameObject, line.p1); + + // top-right -> bottom-right + CopyEndPoint(lines[1], line); + line = lines[1]; + GetBottomRight(gameObject, line.p1); + + // bottom-right -> bottom-left + CopyEndPoint(lines[2], line); + line = lines[2]; + GetBottomLeft(gameObject, line.p1); + + // bottom-left -> top-left + CopyEndPoint(lines[3], line); + CopyStartPoint(lines[3], lines[0]); + return out; +} + +var Create4Lines = function () { + var path = new Phaser.Curves.Path(); + for (var i = 0; i < 4; i++) { + path.add(new Phaser.Curves.Line({ x: 0, y: 0 }, { x: 0, y: 0 })); + } + return path; +} + +var CopyEndPoint = function (curLine, prevLine) { + curLine.p0.x = prevLine.p1.x; + curLine.p0.y = prevLine.p1.y; +} + +var CopyStartPoint = function (curLine, prevLine) { + curLine.p1.x = prevLine.p0.x; + curLine.p1.y = prevLine.p0.y; +} + +export default BoundsToPath; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/bounds/BoundsToPolygon.js b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/BoundsToPolygon.js new file mode 100644 index 000000000..b9ca04fd7 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/BoundsToPolygon.js @@ -0,0 +1,20 @@ +import { + GetTopLeft, GetTopRight, + GetBottomLeft, GetBottomRight +} from './GetBounds.js'; + +const Polygon = Phaser.Geom.Polygon; + +var BoundsToPolygon = function (gameObject, out) { + if (out === undefined) { + out = new Polygon(); + } + var p0 = GetTopLeft(gameObject), + p1 = GetTopRight(gameObject), + p2 = GetBottomRight(gameObject), + p3 = GetBottomLeft(gameObject); + out.setTo([p0, p1, p2, p3, p0]); + return out; +} + +export default BoundsToPolygon; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/bounds/DrawBounds.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/DrawBounds.d.ts new file mode 100644 index 000000000..9a07ad3b1 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/DrawBounds.d.ts @@ -0,0 +1,26 @@ +export default DrawBounds; + +declare namespace DrawBounds { + interface IConfig { + color?: number, + lineWidth?: number, + + fillColor?: number, + fillAlpha?: number, + + padding?: number, + + } +} + +declare function DrawBounds( + gameObjects: Phaser.GameObjects.GameObject[] | Phaser.GameObjects.GameObject, + graphics: Phaser.GameObjects.Graphics, + config?: number, +): void; + +declare function DrawBounds( + gameObjects: Phaser.GameObjects.GameObject[] | Phaser.GameObjects.GameObject, + graphics: Phaser.GameObjects.Graphics, + config?: DrawBounds.IConfig +): void; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/bounds/DrawBounds.js b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/DrawBounds.js new file mode 100644 index 000000000..b4e228cf5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/DrawBounds.js @@ -0,0 +1,75 @@ +import { + GetTopLeft, + GetTopRight, + GetBottomLeft, + GetBottomRight, +} from './GetBounds.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +var DrawBounds = function (gameObjects, graphics, config) { + var strokeColor, lineWidth, fillColor, fillAlpha, padding; + if (typeof (config) === 'number') { + strokeColor = config; + } else { + strokeColor = GetValue(config, 'color'); + lineWidth = GetValue(config, 'lineWidth'); + fillColor = GetValue(config, 'fillColor'); + fillAlpha = GetValue(config, 'fillAlpha', 1); + padding = GetValue(config, 'padding', 0); + } + + if (Array.isArray(gameObjects)) { + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + Draw(gameObjects[i], graphics, strokeColor, lineWidth, fillColor, fillAlpha, padding); + } + } else { + Draw(gameObjects, graphics, strokeColor, lineWidth, fillColor, fillAlpha, padding); + } +} + +var Draw = function (gameObject, graphics, strokeColor, lineWidth, fillColor, fillAlpha, padding) { + var canDrawBound = gameObject.getBounds || + ((gameObject.width !== undefined) && (gameObject.height !== undefined)); + if (!canDrawBound) { + return; + } + + if (strokeColor === undefined) { strokeColor = 0xffffff; } + if (lineWidth === undefined) { lineWidth = 1; } + if (fillColor === undefined) { fillColor = null }; + if (fillAlpha === undefined) { fillAlpha = 1 }; + if (padding === undefined) { padding = 0; } + + var p0 = GetTopLeft(gameObject, Points[0]); + p0.x -= padding; + p0.y -= padding; + + var p1 = GetTopRight(gameObject, Points[1]); + p1.x += padding; + p1.y -= padding; + + var p2 = GetBottomRight(gameObject, Points[2]); + p2.x += padding; + p2.y += padding; + + var p3 = GetBottomLeft(gameObject, Points[3]); + p3.x -= padding; + p3.y += padding; + + if (fillColor !== null) { + graphics + .fillStyle(fillColor, fillAlpha) + .fillPoints(Points, true, true); + } + if (strokeColor !== null) { + graphics + .lineStyle(lineWidth, strokeColor) + .strokePoints(Points, true, true); + } + +} + +var Points = [{ x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }]; + +export default DrawBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/bounds/GetBounds.js b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/GetBounds.js new file mode 100644 index 000000000..98c29d3d2 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/GetBounds.js @@ -0,0 +1,193 @@ +import { + GetDisplayWidth, + GetDisplayHeight +} from '../size/GetDisplaySize.js'; + +const Rectangle = Phaser.Geom.Rectangle; +const Vector2 = Phaser.Math.Vector2; +const RotateAround = Phaser.Math.RotateAround; + +var GetBounds = function (gameObject, output) { + if (output === undefined) { + output = new Rectangle(); + } else if (output === true) { + if (GlobRect === undefined) { + GlobRect = new Rectangle(); + } + output = GlobRect; + } + + if (gameObject.getBounds) { + return gameObject.getBounds(output); + } + + // We can use the output object to temporarily store the x/y coords in: + + var TLx, TLy, TRx, TRy, BLx, BLy, BRx, BRy; + + // Instead of doing a check if parent container is + // defined per corner we only do it once. + if (gameObject.parentContainer) { + var parentMatrix = gameObject.parentContainer.getBoundsTransformMatrix(); + + GetTopLeft(gameObject, output); + parentMatrix.transformPoint(output.x, output.y, output); + + TLx = output.x; + TLy = output.y; + + GetTopRight(gameObject, output); + parentMatrix.transformPoint(output.x, output.y, output); + + TRx = output.x; + TRy = output.y; + + GetBottomLeft(gameObject, output);; + parentMatrix.transformPoint(output.x, output.y, output); + + BLx = output.x; + BLy = output.y; + + GetBottomRight(gameObject, output); + parentMatrix.transformPoint(output.x, output.y, output); + + BRx = output.x; + BRy = output.y; + } + else { + GetTopLeft(gameObject, output); + + TLx = output.x; + TLy = output.y; + + GetTopRight(gameObject, output);; + + TRx = output.x; + TRy = output.y; + + GetBottomLeft(gameObject, output);; + + BLx = output.x; + BLy = output.y; + + GetBottomRight(gameObject, output); + + BRx = output.x; + BRy = output.y; + } + + output.x = Math.min(TLx, TRx, BLx, BRx); + output.y = Math.min(TLy, TRy, BLy, BRy); + output.width = Math.max(TLx, TRx, BLx, BRx) - output.x; + output.height = Math.max(TLy, TRy, BLy, BRy) - output.y; + + return output; +} + +var GlobRect = undefined; + +var GetTopLeft = function (gameObject, output, includeParent) { + if (output === undefined) { + output = new Vector2(); + } else if (output === true) { + if (GlobVector === undefined) { + GlobVector = new Vector2(); + } + output = GlobVector; + } + + if (gameObject.getTopLeft) { + return gameObject.getTopLeft(output); + } + + output.x = gameObject.x - (GetDisplayWidth(gameObject) * gameObject.originX); + output.y = gameObject.y - (GetDisplayHeight(gameObject) * gameObject.originY); + + return PrepareBoundsOutput(gameObject, output, includeParent); +}; + +var GetTopRight = function (gameObject, output, includeParent) { + if (output === undefined) { + output = new Vector2(); + } else if (output === true) { + if (GlobVector === undefined) { + GlobVector = new Vector2(); + } + output = GlobVector; + } + + if (gameObject.getTopRight) { + return gameObject.getTopRight(output); + } + + output.x = (gameObject.x - (GetDisplayWidth(gameObject) * gameObject.originX)) + GetDisplayWidth(gameObject); + output.y = gameObject.y - (GetDisplayHeight(gameObject) * gameObject.originY); + + return PrepareBoundsOutput(gameObject, output, includeParent); +}; + +var GetBottomLeft = function (gameObject, output, includeParent) { + if (output === undefined) { + output = new Vector2(); + } else if (output === true) { + if (GlobVector === undefined) { + GlobVector = new Vector2(); + } + output = GlobVector; + } + + if (gameObject.getBottomLeft) { + return gameObject.getBottomLeft(output); + } + + output.x = gameObject.x - (GetDisplayWidth(gameObject) * gameObject.originX); + output.y = (gameObject.y - (GetDisplayHeight(gameObject) * gameObject.originY)) + GetDisplayHeight(gameObject); + + return PrepareBoundsOutput(gameObject, output, includeParent); +}; + +var GetBottomRight = function (gameObject, output, includeParent) { + if (output === undefined) { + output = new Vector2(); + } else if (output === true) { + if (GlobVector === undefined) { + GlobVector = new Vector2(); + } + output = GlobVector; + } + + if (gameObject.getBottomRight) { + return gameObject.getBottomRight(output); + } + + output.x = (gameObject.x - (GetDisplayWidth(gameObject) * gameObject.originX)) + GetDisplayWidth(gameObject); + output.y = (gameObject.y - (GetDisplayHeight(gameObject) * gameObject.originY)) + GetDisplayHeight(gameObject); + + return PrepareBoundsOutput(gameObject, output, includeParent); +}; + +var GlobVector = undefined; + +var PrepareBoundsOutput = function (gameObject, output, includeParent) { + if (includeParent === undefined) { includeParent = false; } + + if (gameObject.rotation !== 0) { + RotateAround(output, gameObject.x, gameObject.y, gameObject.rotation); + } + + if (includeParent && gameObject.parentContainer) { + var parentMatrix = gameObject.parentContainer.getBoundsTransformMatrix(); + + parentMatrix.transformPoint(output.x, output.y, output); + } + + return output; +}; + +export { + GetBounds, + GetTopLeft, + GetTopRight, + GetBottomLeft, + GetBottomRight, +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/bounds/GetBoundsConfig.js b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/GetBoundsConfig.js new file mode 100644 index 000000000..bde81a424 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/GetBoundsConfig.js @@ -0,0 +1,21 @@ +const GetValue = Phaser.Utils.Objects.GetValue; + +var GetBoundsConfig = function (config, out) { + if (out === undefined) { + out = {}; + } + if (typeof (config) === 'number') { + out.left = config; + out.right = config; + out.top = config; + out.bottom = config; + } else { + out.left = GetValue(config, 'left', 0); + out.right = GetValue(config, 'right', 0); + out.top = GetValue(config, 'top', 0); + out.bottom = GetValue(config, 'bottom', 0); + } + return out; +} + +export default GetBoundsConfig; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/bounds/GetBoundsOfGameObjects.js b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/GetBoundsOfGameObjects.js new file mode 100644 index 000000000..d2293e2c0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/GetBoundsOfGameObjects.js @@ -0,0 +1,41 @@ +import { GetBounds } from './GetBounds.js'; + +const Rectangle = Phaser.Geom.Rectangle; +const Union = Phaser.Geom.Rectangle.Union; + +var GetBoundsOfGameObjects = function (gameObjects, out) { + if (out === undefined) { + out = new Rectangle(); + } else if (out === true) { + if (GlobRect === undefined) { + GlobRect = new Rectangle(); + } + out = GlobRect; + } + + out.setTo(0, 0, 0, 0); + + var gameObject; + var firstClone = true; + for (var i = 0, cnt = gameObjects.length; i < cnt; i++) { + gameObject = gameObjects[i]; + if (!gameObject.getBounds) { + continue; + } + + var boundsRect = GetBounds(gameObject, true); + + if (firstClone) { + out.setTo(boundsRect.x, boundsRect.y, boundsRect.width, boundsRect.height); + firstClone = false; + } else { + Union(boundsRect, out, out); + } + } + + return out; +} + +var GlobRect; + +export default GetBoundsOfGameObjects; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/bounds/IsPointInBounds.js b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/IsPointInBounds.js new file mode 100644 index 000000000..115cfa902 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/bounds/IsPointInBounds.js @@ -0,0 +1,25 @@ +import { GetBounds } from './GetBounds.js'; + +var IsPointInBounds = function (gameObject, x, y, preTest, postTest) { + // Can't get bounds + if (!gameObject) { + return false; + } + + if (preTest && !preTest(gameObject, x, y)) { + return false; + } + + var boundsRect = GetBounds(gameObject, true); + if (!boundsRect.contains(x, y)) { + return false; + } + + if (postTest && !postTest(gameObject, x, y)) { + return false; + } + + return true; +} + +export default IsPointInBounds; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/buff/Buff.js b/ui/src/phaser3-rex-plugins/plugins/utils/buff/Buff.js new file mode 100644 index 000000000..41328ea7e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/buff/Buff.js @@ -0,0 +1,77 @@ +class Buff { + constructor() { + this.buffs = {}; + } + + setEnable(key, enable) { + if (enable === undefined) { + enable = true; + } + if (!this.buffs.hasOwnProperty(key)) { + this.buffs[key] = { + enable: true, + value: 0, + type: ADD, + }; + } + this.buffs[key].enable = enable; + return this; + } + + set(key, value) { + this.setEnable(key); + + var valueType = typeof (value); + if (valueType === 'number') { + valueType = ADD; + } else if (valueType === 'string') { + if (value.indexOf('%') !== -1) { + valueType = ADD_BASE_PERCENT; + value = parseFloat(value) / 100; + } else { + valueType = ADD; + value = parseFloat(value); + } + } + var buff = this.buffs[key]; + buff.value = value; + buff.type = valueType; + return this; + } + + remove(key) { + if (this.buffs.hasOwnProperty(key)) { + delete this.buffs[key]; + } + return this; + } + + buff(baseValue) { + var result = baseValue; + var buffs = this.buffs, + value, valueType; + for (var key in buffs) { + value = buffs[key]; + if (!value.enable) { + continue; + } + + valueType = value.type; + value = value.value; + switch (valueType) { + case ADD: + result += value; + break; + case ADD_BASE_PERCENT: + result += baseValue * value; + break; + } + } + return result; + } +} + +const ADD = 0 +const ADD_BASE_PERCENT = 1; + +export default Buff; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/canvas/AddPolygonPath.js b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/AddPolygonPath.js new file mode 100644 index 000000000..f480ec5e3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/AddPolygonPath.js @@ -0,0 +1,18 @@ +var AddPolygonPath = function (context, points) { + context.save(); + context.beginPath(); + + var point = points[0]; + + context.moveTo(point.x, point.y); + + for (var i = 1, cnt = points.length; i < cnt; i++) { + point = points[i]; + context.lineTo(point.x, point.y); + } + + context.closePath(); + context.restore(); +} + +export default AddPolygonPath; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/canvas/AddRoundRectanglePath.js b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/AddRoundRectanglePath.js new file mode 100644 index 000000000..4e7215d94 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/AddRoundRectanglePath.js @@ -0,0 +1,139 @@ +import RoundRectangle from '../../geom/roundrectangle/RoundRectangle.js'; + +const DegToRad = Phaser.Math.DegToRad; + +var AddRoundRectanglePath = function (context, x, y, width, height, radiusConfig, iteration) { + var geom = new RoundRectangle(x, y, width, height, radiusConfig), + minWidth = geom.minWidth, + minHeight = geom.minHeight, + scaleRX = (width >= minWidth) ? 1 : (width / minWidth), + scaleRY = (height >= minHeight) ? 1 : (height / minHeight); + + var cornerRadius = geom.cornerRadius; + var radius, radiusX, radiusY, centerX, centerY; + + context.save(); + context.beginPath(); + + context.translate(x, y); + + // Top-left + radius = cornerRadius.tl; + if (IsArcCorner(radius)) { + radiusX = radius.x * scaleRX; + radiusY = radius.y * scaleRY; + if (IsConvexArc(radius)) { + centerX = radiusX; + centerY = radiusY; + ArcTo(context, centerX, centerY, radiusX, radiusY, 180, 270, false, iteration); + } else { + centerX = 0; + centerY = 0; + ArcTo(context, centerX, centerY, radiusX, radiusY, 90, 0, true, iteration); + } + } else { + context.lineTo(0, 0); + } + + // Top-right + radius = cornerRadius.tr; + if (IsArcCorner(radius)) { + radiusX = radius.x * scaleRX; + radiusY = radius.y * scaleRY; + if (IsConvexArc(radius)) { + centerX = width - radiusX; + centerY = radiusY; + ArcTo(context, centerX, centerY, radiusX, radiusY, 270, 360, false, iteration); + } else { + centerX = width; + centerY = 0; + ArcTo(context, centerX, centerY, radiusX, radiusY, 180, 90, true, iteration); + } + } else { + context.lineTo(width, 0); + } + + // Bottom-right + radius = cornerRadius.br; + if (IsArcCorner(radius)) { + radiusX = radius.x * scaleRX; + radiusY = radius.y * scaleRY; + if (IsConvexArc(radius)) { + centerX = width - radiusX; + centerY = height - radiusY; + ArcTo(context, centerX, centerY, radiusX, radiusY, 0, 90, false, iteration); + } else { + centerX = width; + centerY = height; + ArcTo(context, centerX, centerY, radiusX, radiusY, 270, 180, true, iteration); + } + } else { + context.lineTo(width, height); + } + + // Bottom-left + radius = cornerRadius.bl; + if (IsArcCorner(radius)) { + radiusX = radius.x * scaleRX; + radiusY = radius.y * scaleRY; + if (IsConvexArc(radius)) { + centerX = radiusX; + centerY = height - radiusY; + ArcTo(context, centerX, centerY, radiusX, radiusY, 90, 180, false, iteration); + } else { + centerX = 0; + centerY = height; + ArcTo(context, centerX, centerY, radiusX, radiusY, 360, 270, true, iteration); + } + } else { + context.lineTo(0, height); + } + + context.closePath(); + context.restore(); +} + +var IsConvexArc = function (radius) { + return (!radius.hasOwnProperty('convex')) || // radius does not have convex property + radius.convex; +} + +var IsArcCorner = function (radius) { + return ((radius.x > 0) && (radius.y > 0)); +} + +var ArcTo = function ( + context, + centerX, centerY, + radiusX, radiusY, + startAngle, endAngle, + antiClockWise, + iteration +) { + + // startAngle, endAngle: 0 ~ 360 + if (antiClockWise && (endAngle > startAngle)) { + endAngle -= 360; + } else if (!antiClockWise && (endAngle < startAngle)) { + endAngle += 360; + } + + startAngle = DegToRad(startAngle); + endAngle = DegToRad(endAngle); + + if (iteration == null) { // undefined, or null + context.ellipse(centerX, centerY, radiusX, radiusY, 0, startAngle, endAngle, antiClockWise); + } else { + iteration += 1; + var x, y, angle; + var step = (endAngle - startAngle) / iteration; + for (var i = 0; i <= iteration; i++) { + angle = startAngle + (step * i); + x = centerX + (radiusX * Math.cos(angle)); + y = centerY + (radiusY * Math.sin(angle)); + context.lineTo(x, y); + } + } +} + +export default AddRoundRectanglePath; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawCircle.js b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawCircle.js new file mode 100644 index 000000000..21d8bbbd0 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawCircle.js @@ -0,0 +1,35 @@ +var DrawCircle = function ( + canvas, context, + x, y, + rx, ry, + fillStyle, strokeStyle, lineWidth, + startAngle, endAngle, anticlockwise +) { + + if (startAngle === undefined) { + startAngle = 0; + } + if (endAngle === undefined) { + endAngle = 2 * Math.PI; + } + if (anticlockwise === undefined) { + anticlockwise = false; + } + + context.beginPath(); + + context.ellipse(x, y, rx, ry, 0, startAngle, endAngle, anticlockwise); + + if (fillStyle != null) { + context.fillStyle = fillStyle; + context.fill(); + } + + if (strokeStyle != null) { + context.strokeStyle = strokeStyle; + context.lineWidth = lineWidth; + context.stroke(); + } +} + +export default DrawCircle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawHSVPalette.js b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawHSVPalette.js new file mode 100644 index 000000000..86e2bd9d4 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawHSVPalette.js @@ -0,0 +1,52 @@ +const Color = Phaser.Display.Color; +const Percent = Phaser.Math.Percent; + +var DrawHPalette = function (canvas, context, verticalMode) { + if (verticalMode === undefined) { + verticalMode = false; + } + var width = canvas.width; + var height = canvas.height; + var color = new Color(); + if (verticalMode) { + for (var iy = 0; iy < height; iy++) { + var h = Percent(iy, 0, height); + color.setFromHSV(h, 1, 1); + context.fillStyle = color.rgba; + context.fillRect(0, iy, width, 1); + } + } else { + for (var ix = 0; ix < width; ix++) { + var h = Percent(ix, 0, width); + color.setFromHSV(h, 1, 1); + context.fillStyle = color.rgba; + context.fillRect(ix, 0, 1, height); + } + } +} + +var DrawSVPalette = function (canvas, context, h) { + var width = canvas.width; + var height = canvas.height; + var imgData = context.getImageData(0, 0, width, height); + var data = imgData.data; + var color = new Color(); + for (var iy = 0; iy < height; iy++) { + for (var ix = 0; ix < width; ix++) { + var s = Percent(ix, 0, width); + var v = 1 - Percent(iy, 0, height); + color.setFromHSV(h, s, v); + var i = ((iy * width) + ix) * 4; + data[i] = color.red; + data[i + 1] = color.green; + data[i + 2] = color.blue; + data[i + 3] = 255; + } + } + context.putImageData(imgData, 0, 0); +} + +export { + DrawHPalette, + DrawSVPalette +}; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawPolygon.js b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawPolygon.js new file mode 100644 index 000000000..d95927e7d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawPolygon.js @@ -0,0 +1,31 @@ +import AddPolygonPath from './AddPolygonPath.js'; + +var DrawPolygon = function ( + canvas, context, + points, + fillStyle, + strokeStyle, lineWidth, + lineJoin +) { + + if (lineJoin === undefined) { + lineJoin = 'round'; + } + + AddPolygonPath(context, points); + + context.lineJoin = lineJoin; + + if (fillStyle != null) { + context.fillStyle = fillStyle; + context.fill(); + } + + if (strokeStyle != null) { + context.strokeStyle = strokeStyle; + context.lineWidth = lineWidth; + context.stroke(); + } +} + +export default DrawPolygon \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawRectangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawRectangle.js new file mode 100644 index 000000000..cedeafc0a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawRectangle.js @@ -0,0 +1,36 @@ +var DrawRectangle = function ( + canvas, context, + x, y, + width, height, + fillStyle, strokeStyle, lineWidth, fillColor2, isHorizontalGradient +) { + + context.beginPath(); + + context.rect(x, y, width, height); + + if (fillStyle != null) { + if (fillColor2 != null) { + var grd; + if (isHorizontalGradient) { + grd = context.createLinearGradient(0, 0, width, 0); + } else { + grd = context.createLinearGradient(0, 0, 0, height); + } + grd.addColorStop(0, fillStyle); + grd.addColorStop(1, fillColor2); + fillStyle = grd; + } + + context.fillStyle = fillStyle; + context.fill(); + } + + if (strokeStyle != null) { + context.strokeStyle = strokeStyle; + context.lineWidth = lineWidth; + context.stroke(); + } +} + +export default DrawRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawRoundRectangle.js b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawRoundRectangle.js new file mode 100644 index 000000000..d0b4d99cc --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawRoundRectangle.js @@ -0,0 +1,37 @@ +import AddRoundRectanglePath from './AddRoundRectanglePath.js' + +var DrawRoundRectangle = function ( + canvas, context, + x, y, + width, height, radiusConfig, + fillStyle, strokeStyle, lineWidth, fillColor2, isHorizontalGradient, + iteration +) { + + AddRoundRectanglePath(context, x, y, width, height, radiusConfig, iteration); + + if (fillStyle != null) { + if (fillColor2 != null) { + var grd; + if (isHorizontalGradient) { + grd = context.createLinearGradient(0, 0, width, 0); + } else { + grd = context.createLinearGradient(0, 0, 0, height); + } + grd.addColorStop(0, fillStyle); + grd.addColorStop(1, fillColor2); + fillStyle = grd; + } + + context.fillStyle = fillStyle; + context.fill(); + } + + if ((strokeStyle != null) && (lineWidth > 0)) { + context.strokeStyle = strokeStyle; + context.lineWidth = lineWidth; + context.stroke(); + } +} + +export default DrawRoundRectangle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawText.js b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawText.js new file mode 100644 index 000000000..0f75eae24 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/DrawText.js @@ -0,0 +1,42 @@ +var DrawText = function ( + canvas, context, + x, y, + text, font, + fillStyle, strokeStyle, lineWidth, + textAlign, textBaseline +) { + + if ((lineWidth === undefined) && (strokeStyle != null)) { + lineWidth = 2; + } + + if (textAlign === undefined) { + textAlign = 'start'; + } + + if (textBaseline === undefined) { + textBaseline = 'alphabetic'; + } + + context.font = font; + context.textAlign = textAlign; + context.textBaseline = textBaseline; + + context.fillStyle = fillStyle; + context.strokeStyle = strokeStyle; + + context.lineWidth = lineWidth; + context.lineCap = 'round'; + context.lineJoin = 'round'; + + if ((strokeStyle != null) && (strokeStyle !== 'none') && (lineWidth > 0)) { + context.strokeText(text, x, y); + } + + if ((fillStyle != null) && (fillStyle !== 'none')) { + context.fillText(text, x, y); + } + +} + +export default DrawText; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/canvas/GetStyle.js b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/GetStyle.js new file mode 100644 index 000000000..05143d920 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/GetStyle.js @@ -0,0 +1,31 @@ +const Pad = Phaser.Utils.String.Pad; +var GetStyle = function (style, canvas, context) { + if (style == null) { + return style; + } + + switch (typeof (style)) { + case 'string': return style; + case 'number': return `#${Pad(Math.floor(style).toString(16), 6, '0', 1)}`; + case 'function': return style(canvas, context); + case 'object': + if (style.hasOwnProperty('r')) { + if (style.hasOwnProperty('a')) { // rgba + return `rgba(${style.r},${style.g},${style.b},${style.a})`; + } else { // rgb + return `rgb(${style.r},${style.g},${style.b})`; + } + } else if (style.hasOwnProperty('h')) { + if (style.hasOwnProperty('a')) { // hsla + return `hsla(${style.h},${style.s},${style.l},${style.a})`; + } else { // hsl + return `hsl(${style.h},${style.s},${style.l})`; + } + } else { + return style; // Not a valid input + } + default: return style; + } +} + +export default GetStyle; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/canvas/GetTextSize.js b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/GetTextSize.js new file mode 100644 index 000000000..b5dded00e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/canvas/GetTextSize.js @@ -0,0 +1,27 @@ +const CanvasPool = Phaser.Display.Canvas.CanvasPool; + +var GetTextSize = function (text, font, out) { + if (out === undefined) { + out = {}; + } + + var canvas = CanvasPool.create(); + + var context = canvas.getContext('2d', { willReadFrequently: true }); + context.font = font; + + var metrics = context.measureText(text); + var ascent = metrics.actualBoundingBoxAscent; + var descent = metrics.actualBoundingBoxDescent; + + out.width = metrics.width; + out.height = (ascent + descent); + out.ascent = ascent; + out.descent = descent; + + CanvasPool.remove(canvas); + + return out; +} + +export default GetTextSize \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/color/ColorNameToInteger.js b/ui/src/phaser3-rex-plugins/plugins/utils/color/ColorNameToInteger.js new file mode 100644 index 000000000..0a6f523aa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/color/ColorNameToInteger.js @@ -0,0 +1,28 @@ +const ColorNames = ['AliceBlue', 'AntiqueWhite', 'Aqua', 'Aquamarine', 'Azure', 'Beige', 'Bisque', 'Black', 'BlanchedAlmond', 'Blue', 'BlueViolet', 'Brown', 'BurlyWood', 'CadetBlue', 'Chartreuse', 'Chocolate', 'Coral', 'CornflowerBlue', 'Cornsilk', 'Crimson', 'Cyan', 'DarkBlue', 'DarkCyan', 'DarkGoldenRod', 'DarkGray', 'DarkGrey', 'DarkGreen', 'DarkKhaki', 'DarkMagenta', 'DarkOliveGreen', 'DarkOrange', 'DarkOrchid', 'DarkRed', 'DarkSalmon', 'DarkSeaGreen', 'DarkSlateBlue', 'DarkSlateGray', 'DarkSlateGrey', 'DarkTurquoise', 'DarkViolet', 'DeepPink', 'DeepSkyBlue', 'DimGray', 'DimGrey', 'DodgerBlue', 'FireBrick', 'FloralWhite', 'ForestGreen', 'Fuchsia', 'Gainsboro', 'GhostWhite', 'Gold', 'GoldenRod', 'Gray', 'Grey', 'Green', 'GreenYellow', 'HoneyDew', 'HotPink', 'IndianRed', 'Indigo', 'Ivory', 'Khaki', 'Lavender', 'LavenderBlush', 'LawnGreen', 'LemonChiffon', 'LightBlue', 'LightCoral', 'LightCyan', 'LightGoldenRodYellow', 'LightGray', 'LightGrey', 'LightGreen', 'LightPink', 'LightSalmon', 'LightSeaGreen', 'LightSkyBlue', 'LightSlateGray', 'LightSlateGrey', 'LightSteelBlue', 'LightYellow', 'Lime', 'LimeGreen', 'Linen', 'Magenta', 'Maroon', 'MediumAquaMarine', 'MediumBlue', 'MediumOrchid', 'MediumPurple', 'MediumSeaGreen', 'MediumSlateBlue', 'MediumSpringGreen', 'MediumTurquoise', 'MediumVioletRed', 'MidnightBlue', 'MintCream', 'MistyRose', 'Moccasin', 'NavajoWhite', 'Navy', 'OldLace', 'Olive', 'OliveDrab', 'Orange', 'OrangeRed', 'Orchid', 'PaleGoldenRod', 'PaleGreen', 'PaleTurquoise', 'PaleVioletRed', 'PapayaWhip', 'PeachPuff', 'Peru', 'Pink', 'Plum', 'PowderBlue', 'Purple', 'RebeccaPurple', 'Red', 'RosyBrown', 'RoyalBlue', 'SaddleBrown', 'Salmon', 'SandyBrown', 'SeaGreen', 'SeaShell', 'Sienna', 'Silver', 'SkyBlue', 'SlateBlue', 'SlateGray', 'SlateGrey', 'Snow', 'SpringGreen', 'SteelBlue', 'Tan', 'Teal', 'Thistle', 'Tomato', 'Turquoise', 'Violet', 'Wheat', 'White', 'WhiteSmoke', 'Yellow', 'YellowGreen']; +const ColorValues = [0xf0f8ff, 0xfaebd7, 0x00ffff, 0x7fffd4, 0xf0ffff, 0xf5f5dc, 0xffe4c4, 0x000000, 0xffebcd, 0x0000ff, 0x8a2be2, 0xa52a2a, 0xdeb887, 0x5f9ea0, 0x7fff00, 0xd2691e, 0xff7f50, 0x6495ed, 0xfff8dc, 0xdc143c, 0x00ffff, 0x00008b, 0x008b8b, 0xb8860b, 0xa9a9a9, 0xa9a9a9, 0x006400, 0xbdb76b, 0x8b008b, 0x556b2f, 0xff8c00, 0x9932cc, 0x8b0000, 0xe9967a, 0x8fbc8f, 0x483d8b, 0x2f4f4f, 0x2f4f4f, 0x00ced1, 0x9400d3, 0xff1493, 0x00bfff, 0x696969, 0x696969, 0x1e90ff, 0xb22222, 0xfffaf0, 0x228b22, 0xff00ff, 0xdcdcdc, 0xf8f8ff, 0xffd700, 0xdaa520, 0x808080, 0x808080, 0x008000, 0xadff2f, 0xf0fff0, 0xff69b4, 0xcd5c5c, 0x4b0082, 0xfffff0, 0xf0e68c, 0xe6e6fa, 0xfff0f5, 0x7cfc00, 0xfffacd, 0xadd8e6, 0xf08080, 0xe0ffff, 0xfafad2, 0xd3d3d3, 0xd3d3d3, 0x90ee90, 0xffb6c1, 0xffa07a, 0x20b2aa, 0x87cefa, 0x778899, 0x778899, 0xb0c4de, 0xffffe0, 0x00ff00, 0x32cd32, 0xfaf0e6, 0xff00ff, 0x800000, 0x66cdaa, 0x0000cd, 0xba55d3, 0x9370db, 0x3cb371, 0x7b68ee, 0x00fa9a, 0x48d1cc, 0xc71585, 0x191970, 0xf5fffa, 0xffe4e1, 0xffe4b5, 0xffdead, 0x000080, 0xfdf5e6, 0x808000, 0x6b8e23, 0xffa500, 0xff4500, 0xda70d6, 0xeee8aa, 0x98fb98, 0xafeeee, 0xdb7093, 0xffefd5, 0xffdab9, 0xcd853f, 0xffc0cb, 0xdda0dd, 0xb0e0e6, 0x800080, 0x663399, 0xff0000, 0xbc8f8f, 0x4169e1, 0x8b4513, 0xfa8072, 0xf4a460, 0x2e8b57, 0xfff5ee, 0xa0522d, 0xc0c0c0, 0x87ceeb, 0x6a5acd, 0x708090, 0x708090, 0xfffafa, 0x00ff7f, 0x4682b4, 0xd2b48c, 0x008080, 0xd8bfd8, 0xff6347, 0x40e0d0, 0xee82ee, 0xf5deb3, 0xffffff, 0xf5f5f5, 0xffff00, 0x9acd32]; + +var ColorNameToIntegerDict = {}, name; +for (var i = 0, cnt = ColorNames.length; i < cnt; i++) { + name = ColorNames[i].toLowerCase(); + ColorNameToIntegerDict[name] = ColorValues[i]; +} + +var ColorNameToInteger = function (colorName) { + colorName = colorName.toLowerCase() + if (ColorNameToIntegerDict.hasOwnProperty(colorName)) { + return ColorNameToIntegerDict[colorName]; + } else { + return null; + } +} + +var IntegerToColorName = function (colorValue) { + var idx = ColorValues.indexOf(colorValue); + if (idx === -1) { + return ''; + } else { + return ColorNames[idx]; + } +} + +export { ColorNames, ColorNameToInteger, IntegerToColorName }; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/color/ColorStringToInteger.js b/ui/src/phaser3-rex-plugins/plugins/utils/color/ColorStringToInteger.js new file mode 100644 index 000000000..e6ee23dfb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/color/ColorStringToInteger.js @@ -0,0 +1,18 @@ +import { ColorNameToInteger } from './ColorNameToInteger'; + +var ColorStringToInteger = function (value) { + if (typeof (value) !== 'string') { + return value; + } + + if (value.startsWith('#')) { + value = parseInt(value.substring(1), 16); + } else if (value.startsWith('0x')) { + value = parseInt(value.substring(2), 16); + } else { + value = ColorNameToInteger(value); + } + return value; +} + +export default ColorStringToInteger; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/color/GetHexColorString.js b/ui/src/phaser3-rex-plugins/plugins/utils/color/GetHexColorString.js new file mode 100644 index 000000000..dc208540d --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/color/GetHexColorString.js @@ -0,0 +1,13 @@ +const Pad = Phaser.Utils.String.Pad; + +var GetHexColorString = function (value, prefix) { + if (prefix === undefined) { + prefix = '0x' + } + var colorString = value.toString(16).toUpperCase(); + colorString = Pad(colorString, 6, 0, 1); + colorString = prefix + colorString; + return colorString; +} + +export default GetHexColorString; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/color/GetRGB.js b/ui/src/phaser3-rex-plugins/plugins/utils/color/GetRGB.js new file mode 100644 index 000000000..c160d9f02 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/color/GetRGB.js @@ -0,0 +1,15 @@ +var GetR = function (colorInt) { + return (colorInt >> 16) & 0xff; +} + +var GetG = function (colorInt) { + return (colorInt >> 8) & 0xff; +} + +var GetB = function (colorInt) { + return (colorInt) & 0xff; +} + +export { + GetR, GetG, GetB +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/color/GrayScale.js b/ui/src/phaser3-rex-plugins/plugins/utils/color/GrayScale.js new file mode 100644 index 000000000..8197e7deb --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/color/GrayScale.js @@ -0,0 +1,8 @@ +import { GetR, GetG, GetB } from './GetRGB.js'; + +var GrayScale = function (color) { + var shade = 0.3 * GetR(color) + 0.59 * GetG(color) + 0.11 * GetB(color); + return ((shade & 0xff) << 16) | ((shade & 0xff) << 8) | ((shade & 0xff)); +} + +export default GrayScale; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/color/MixColor.js b/ui/src/phaser3-rex-plugins/plugins/utils/color/MixColor.js new file mode 100644 index 000000000..a6ddd6a13 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/color/MixColor.js @@ -0,0 +1,11 @@ +import Linear from '../math/Linear.js'; +import { GetR, GetG, GetB } from './GetRGB.js'; + +var MixColor = function (color0, color1, t) { + var r = Linear(GetR(color0), GetR(color1), t); + var g = Linear(GetG(color0), GetG(color1), t); + var b = Linear(GetB(color0), GetB(color1), t); + return ((r & 0xff) << 16) | ((g & 0xff) << 8) | ((b & 0xff)); +} + +export default MixColor; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/color/SetColor.js b/ui/src/phaser3-rex-plugins/plugins/utils/color/SetColor.js new file mode 100644 index 000000000..b541c6539 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/color/SetColor.js @@ -0,0 +1,23 @@ +const MaskR = (~(0xff << 16) & 0xffffff); +const MaskG = (~(0xff << 8) & 0xffffff); +const MaskB = (~(0xff) & 0xffffff); + +var SetR = function (colorInt, r) { + return ((r & 0xff) << 16) | (colorInt & MaskR); +} + +var SetG = function (colorInt, g) { + return ((g & 0xff) << 8) | (colorInt & MaskG); +} + +var SetB = function (colorInt, b) { + return (b & 0xff) | (colorInt & MaskB); +} + +var SetRGB = function (colorInt, r, g, b) { + return ((r & 0xff) << 16) | ((g & 0xff) << 8) | ((b & 0xff)); +} + +export { + SetR, SetG, SetB, SetRGB +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/commandhub/CommandHub.js b/ui/src/phaser3-rex-plugins/plugins/utils/commandhub/CommandHub.js new file mode 100644 index 000000000..5e18eccf5 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/commandhub/CommandHub.js @@ -0,0 +1,36 @@ +import ComponentBase from '../componentbase/ComponentBase'; + +class CommandHub extends ComponentBase { + addCommand(name, callback, scope) { + if (this.hasOwnProperty(name)) { + console.warn(`Command '${name}' has been added`); + } + this[name] = callback.bind(scope); + return this; + } + + runCommand(name, ...args) { + if (!this.hasOwnProperty(name)) { + return; + } + return this[name].apply(null, args); + } + + addProperty(name, value) { + if (this.hasOwnProperty(name)) { + console.warn(`Property '${name}' has been added`); + } + this[name] = value; + return this; + } + + getProperty(name, defaultValue) { + if (!this.hasOwnProperty(name)) { + return defaultValue; + } + return this[name]; + } + +} + +export default CommandHub; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/ComponentBase.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/ComponentBase.d.ts new file mode 100644 index 000000000..63b8c0faf --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/ComponentBase.d.ts @@ -0,0 +1,23 @@ +export default ComponentBase; + +declare namespace ComponentBase { + interface IConfig { + eventEmitter?: Phaser.Events.EventEmitter | boolean; + } +} + +declare class ComponentBase extends Phaser.Events.EventEmitter { + constructor( + parent?: Object, + config?: ComponentBase.IConfig + ); + + setParent( + parent?: Object + ): this; + parent: Object; + scene: Phaser.Scene; + game: Phaser.Game; + + destroy(): void; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/ComponentBase.js b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/ComponentBase.js new file mode 100644 index 000000000..663334b8e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/ComponentBase.js @@ -0,0 +1,92 @@ +import EventEmitterMethods from '../eventemitter/EventEmitterMethods.js'; +import GetSceneObject from '../system/GetSceneObject.js'; +import GetGame from '../system/GetGame.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class ComponentBase { + constructor(parent, config) { + this.setParent(parent); // gameObject, scene, or game + + this.isShutdown = false; + + // Event emitter, default is private event emitter + this.setEventEmitter(GetValue(config, 'eventEmitter', true)); + + // Register callback of parent destroy event, also see `shutdown` method + if (this.parent) { + if (this.parent === this.scene) { // parent is a scene + this.scene.sys.events.once('shutdown', this.onEnvDestroy, this); + + } else if (this.parent === this.game) { // parent is game + this.game.events.once('shutdown', this.onEnvDestroy, this); + + } else if (this.parent.once) { // parent is game object or something else + this.parent.once('destroy', this.onParentDestroy, this); + } + + // bob object does not have event emitter + } + + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + // parent might not be shutdown yet + if (this.parent) { + if (this.parent === this.scene) { // parent is a scene + this.scene.sys.events.off('shutdown', this.onEnvDestroy, this); + + } else if (this.parent === this.game) { // parent is game + this.game.events.off('shutdown', this.onEnvDestroy, this); + + } else if (this.parent.once) { // parent is game object or something else + this.parent.off('destroy', this.onParentDestroy, this); + } + + // bob object does not have event emitter + } + + + this.destroyEventEmitter(); + + this.parent = undefined; + this.scene = undefined; + this.game = undefined; + + this.isShutdown = true; + } + + destroy(fromScene) { + this.shutdown(fromScene); + } + + onEnvDestroy() { + this.destroy(true); + } + + onParentDestroy(parent, fromScene) { + this.destroy(fromScene); + } + + setParent(parent) { + this.parent = parent; // gameObject, scene, or game + + this.scene = GetSceneObject(parent); + this.game = GetGame(parent); + + return this; + } + +}; + +Object.assign( + ComponentBase.prototype, + EventEmitterMethods +); + +export default ComponentBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/SceneUpdateTickTask.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/SceneUpdateTickTask.d.ts new file mode 100644 index 000000000..86bf978aa --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/SceneUpdateTickTask.d.ts @@ -0,0 +1,17 @@ +import TickTask from './TickTask'; + +export default SceneUpdateTickTask; + +declare namespace SceneUpdateTickTask { + interface IConfig extends TickTask.IConfig { + tickEventName?: string; + } +} + +declare class SceneUpdateTickTask extends TickTask { + constructor( + parent?: Object, + config?: SceneUpdateTickTask.IConfig + ); + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/SceneUpdateTickTask.js b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/SceneUpdateTickTask.js new file mode 100644 index 000000000..bada04de3 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/SceneUpdateTickTask.js @@ -0,0 +1,50 @@ +import TickTask from './TickTask.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class SceneUpdateTickTask extends TickTask { + constructor(parent, config) { + super(parent, config); + + // scene update : update, preupdate, postupdate, prerender, render + // game update : step, poststep, + + // If this.scene is not available, use game's 'step' event + var defaultEventName = (this.scene) ? 'update' : 'step'; + this.tickEventName = GetValue(config, 'tickEventName', defaultEventName); + this.isSceneTicker = !IsGameUpdateEvent(this.tickEventName); + + } + + startTicking() { + super.startTicking(); + + if (this.isSceneTicker) { + this.scene.sys.events.on(this.tickEventName, this.update, this); + } else { + this.game.events.on(this.tickEventName, this.update, this); + } + + } + + stopTicking() { + super.stopTicking(); + + if (this.isSceneTicker && this.scene) { // Scene might be destoryed + this.scene.sys.events.off(this.tickEventName, this.update, this); + } else if (this.game) { + this.game.events.off(this.tickEventName, this.update, this); + } + } + + // update(time, delta) { + // + // } + +} + +var IsGameUpdateEvent = function (eventName) { + return (eventName === 'step') || (eventName === 'poststep'); +} + +export default SceneUpdateTickTask; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/TickTask.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/TickTask.d.ts new file mode 100644 index 000000000..ac42fe61a --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/TickTask.d.ts @@ -0,0 +1,25 @@ + +import ComponentBase from './ComponentBase'; + +export default TickTask; + +declare namespace TickTask { + interface IConfig extends ComponentBase.IConfig { + tickingMode?: 0 | 'no' | 1 | 'lazy' | 2 | 'always' + } +} + +declare class TickTask extends ComponentBase { + constructor( + parent?: Object, + config?: TickTask.IConfig + ); + + start(): this; + pause(): this; + resume(): this; + stop(): this; + complete(): this; + isRunning: boolean; + isPaused: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/TickTask.js b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/TickTask.js new file mode 100644 index 000000000..f31ecbf80 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/TickTask.js @@ -0,0 +1,115 @@ +import ComponentBase from './ComponentBase.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; + +class TickTask extends ComponentBase { + constructor(parent, config) { + super(parent, config); + + this._isRunning = false; + this.isPaused = false; + this.tickingState = false; + this.setTickingMode(GetValue(config, 'tickingMode', 1)); + // boot() later + } + + // override + boot() { + if ((this.tickingMode === 2) && (!this.tickingState)) { + this.startTicking(); + } + } + + // override + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.stop(); + if (this.tickingState) { + this.stopTicking(); + } + super.shutdown(fromScene); + } + + setTickingMode(mode) { + if (typeof (mode) === 'string') { + mode = TICKINGMODE[mode]; + } + this.tickingMode = mode; + } + + // override + startTicking() { + this.tickingState = true; + } + + // override + stopTicking() { + this.tickingState = false; + } + + get isRunning() { + return this._isRunning; + } + + set isRunning(value) { + if (this._isRunning === value) { + return; + } + + this._isRunning = value; + if ((this.tickingMode === 1) && (value != this.tickingState)) { + if (value) { + this.startTicking(); + } else { + this.stopTicking(); + } + } + } + + start() { + this.isPaused = false; + this.isRunning = true; + return this; + } + + pause() { + // Only can ba paused in running state + if (this.isRunning) { + this.isPaused = true; + this.isRunning = false; + } + return this; + } + + resume() { + // Only can ba resumed in paused state (paused from running state) + if (this.isPaused) { + this.isRunning = true; + } + return this; + } + + stop() { + this.isPaused = false; + this.isRunning = false; + return this; + } + + complete() { + this.isPaused = false; + this.isRunning = false; + this.emit('complete', this.parent, this); + } +} + +const TICKINGMODE = { + 'no': 0, + 'lazy': 1, + 'always': 2 +} + +export default TickTask; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/timerticktask/Timer.js b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/timerticktask/Timer.js new file mode 100644 index 000000000..5ab84039c --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/timerticktask/Timer.js @@ -0,0 +1,187 @@ +const GetValue = Phaser.Utils.Objects.GetValue; +const Clamp = Phaser.Math.Clamp; + +class Timer { + constructor(config) { + this.resetFromJSON(config); + } + + resetFromJSON(o) { + this.state = GetValue(o, 'state', IDLE); + this.timeScale = GetValue(o, 'timeScale', 1); + this.delay = GetValue(o, 'delay', 0); + this.repeat = GetValue(o, 'repeat', 0); + this.repeatCounter = GetValue(o, 'repeatCounter', 0); + this.repeatDelay = GetValue(o, 'repeatDelay', 0); + this.duration = GetValue(o, 'duration', 0); + this.nowTime = GetValue(o, 'nowTime', 0); + this.justRestart = GetValue(o, 'justRestart', false); + } + + toJSON() { + return { + state: this.state, + timeScale: this.timeScale, + delay: this.delay, + repeat: this.repeat, + repeatCounter: this.repeatCounter, + repeatDelay: this.repeatDelay, + duration: this.duration, + nowTime: this.nowTime, + justRestart: this.justRestart, + } + } + + destroy() { + + } + + setTimeScale(timeScale) { + this.timeScale = timeScale; + return this; + } + + setDelay(delay) { + if (delay === undefined) { + delay = 0; + } + this.delay = delay; + return this; + } + + setDuration(duration) { + this.duration = duration; + return this; + } + + setRepeat(repeat) { + this.repeat = repeat; + return this; + } + + setRepeatInfinity() { + this.repeat = -1; + return this; + } + + setRepeatDelay(repeatDelay) { + this.repeatDelay = repeatDelay; + return this; + } + + start() { + this.nowTime = (this.delay > 0) ? -this.delay : 0; + this.state = (this.nowTime >= 0) ? COUNTDOWN : DELAY; + this.repeatCounter = 0; + return this; + } + + stop() { + this.state = IDLE; + return this; + } + + update(time, delta) { + if (this.state === IDLE || this.state === DONE || + delta === 0 || this.timeScale === 0 + ) { + return; + } + + this.nowTime += (delta * this.timeScale); + this.justRestart = false; + if (this.nowTime >= this.duration) { + if ((this.repeat === -1) || (this.repeatCounter < this.repeat)) { + this.repeatCounter++; + this.justRestart = true; + this.nowTime -= this.duration; + if (this.repeatDelay > 0) { + this.nowTime -= this.repeatDelay; + this.state = REPEATDELAY; + } + } else { + this.nowTime = this.duration; + this.state = DONE; + } + } else if (this.nowTime >= 0) { + this.state = COUNTDOWN; + } + } + + get t() { + var t; + switch (this.state) { + case IDLE: + case DELAY: + case REPEATDELAY: + t = 0; + break; + + case COUNTDOWN: + t = this.nowTime / this.duration; + break; + + case DONE: + t = 1; + break; + } + return Clamp(t, 0, 1); + } + + set t(value) { + value = Clamp(value, -1, 1); + if (value < 0) { + this.state = DELAY; + this.nowTime = -this.delay * value; + } else { + this.state = COUNTDOWN; + this.nowTime = this.duration * value; + + if ((value === 1) && (this.repeat !== 0)) { + this.repeatCounter++; + } + } + } + + setT(t) { + this.t = t; + return this; + } + + get isIdle() { + return this.state === IDLE; + } + + get isDelay() { + return this.state === DELAY; + } + + get isCountDown() { + return this.state === COUNTDOWN; + } + + get isRunning() { + return this.state === DELAY || this.state === COUNTDOWN; + } + + get isDone() { + return this.state === DONE; + } + + get isOddIteration() { + return (this.repeatCounter & 1) === 1; + } + + get isEvenIteration() { + return (this.repeatCounter & 1) === 0; + } + +} + +const IDLE = 0; +const DELAY = 1; +const COUNTDOWN = 2; +const REPEATDELAY = 3; +const DONE = -1; + +export default Timer; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/timerticktask/TimerTask.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/timerticktask/TimerTask.d.ts new file mode 100644 index 000000000..e5bf1bc6f --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/timerticktask/TimerTask.d.ts @@ -0,0 +1,16 @@ +import SceneUpdateTickTask from '../SceneUpdateTickTask'; + +export default TimerTask; + +declare namespace TimerTask { + interface IConfig extends SceneUpdateTickTask.IConfig { + } +} + +declare class TimerTask extends SceneUpdateTickTask { + constructor( + parent?: Object, + config?: TimerTask.IConfig + ); + +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/timerticktask/TimerTask.js b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/timerticktask/TimerTask.js new file mode 100644 index 000000000..71965062e --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/timerticktask/TimerTask.js @@ -0,0 +1,43 @@ +import TickTask from '../SceneUpdateTickTask.js'; +import Timer from './Timer.js'; + +class TimerTickTask extends TickTask { + constructor(parent, config) { + super(parent, config); + this.timer = new Timer(); + // boot() later + } + + // override + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + super.shutdown(fromScene); + this.timer.destroy(); + this.timer = undefined; + } + + start() { + this.timer.start(); + super.start(); + return this; + } + + stop() { + this.timer.stop(); + super.stop(); + return this; + } + + complete() { + this.timer.stop(); + super.complete(); + return this; + } + +} + +export default TimerTickTask; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/EaseValueTaskBase.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/EaseValueTaskBase.d.ts new file mode 100644 index 000000000..79796f4e9 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/EaseValueTaskBase.d.ts @@ -0,0 +1,23 @@ +import TimerTask from '../timerticktask/TimerTask.js'; + +export default EaseValueTaskBase; + +declare class EaseValueTaskBase extends TimerTask { + setDelay(time: number): this; + delay: number; + + setDuration(time: number): this; + duration: number; + + setEase(ease: string): this; + ease: string; + + start(): this; + stop(): this; + restart(): this; + + pause(): this; + resume(): this; + complete(): this; + readonly isRunning: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/EaseValueTaskBase.js b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/EaseValueTaskBase.js new file mode 100644 index 000000000..578788397 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/EaseValueTaskBase.js @@ -0,0 +1,135 @@ +import TimerTask from '../timerticktask/TimerTask.js'; + +const GetValue = Phaser.Utils.Objects.GetValue; +const GetAdvancedValue = Phaser.Utils.Objects.GetAdvancedValue; +const GetEaseFunction = Phaser.Tweens.Builders.GetEaseFunction; + +class EaseValueTaskBase extends TimerTask { + resetFromJSON(o) { + this.timer.resetFromJSON(GetValue(o, 'timer')); + this.setEnable(GetValue(o, 'enable', true)); + this.setTarget(GetValue(o, 'target', this.parent)); + this.setDelay(GetAdvancedValue(o, 'delay', 0)); + this.setDuration(GetAdvancedValue(o, 'duration', 1000)); + this.setEase(GetValue(o, 'ease', 'Linear')); + this.setRepeat(GetValue(o, 'repeat', 0)); + + return this; + } + + setEnable(e) { + if (e == undefined) { + e = true; + } + this.enable = e; + return this; + } + + setTarget(target) { + if (target === undefined) { + target = this.parent; + } + this.target = target; + return this; + } + + setDelay(time) { + this.delay = time; + // Assign `this.timer.setRepeat(repeat)` manually + return this; + } + + setDuration(time) { + this.duration = time; + return this; + } + + setRepeat(repeat) { + this.repeat = repeat; + // Assign `this.timer.setRepeat(repeat)` manually + return this; + } + + setRepeatDelay(repeatDelay) { + this.repeatDelay = repeatDelay; + // Assign `this.timer.setRepeatDelay(repeatDelay)` manually + return this; + } + + setEase(ease) { + if (ease === undefined) { + ease = 'Linear'; + } + this.ease = ease; + this.easeFn = GetEaseFunction(ease); + return this; + } + + // Override + start() { + // Ignore start if timer is running, i.e. in DELAY, o RUN state + if (this.timer.isRunning) { + return this; + } + + super.start(); + return this; + } + + restart() { + this.timer.stop(); + this.start.apply(this, arguments); + return this; + } + + stop(toEnd) { + if (toEnd === undefined) { + toEnd = false; + } + + super.stop(); + + if (toEnd) { + this.timer.setT(1); + this.updateGameObject(this.target, this.timer); + this.complete(); + } + + return this; + } + + update(time, delta) { + if ( + (!this.isRunning) || + (!this.enable) || + (!this.parent.active) + ) { + return this; + } + + var target = this.target, + timer = this.timer; + + timer.update(time, delta); + + // isDelay, isCountDown, isDone + if (!timer.isDelay) { + this.updateGameObject(target, timer); + } + + this.emit('update', target, this); + + if (timer.isDone) { + this.complete(); + } + + return this; + } + + // Override + updateGameObject(target, timer) { + + } +} + +export default EaseValueTaskBase; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/TweenTask.d.ts b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/TweenTask.d.ts new file mode 100644 index 000000000..b50f8ef81 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/TweenTask.d.ts @@ -0,0 +1,14 @@ +import ComponentBase from '../ComponentBase'; + +export default TweenTask; + +declare class TweenTask extends ComponentBase { + start(config: Object): this; + stop(): this; + restart(config: Object): this; + + pause(): this; + resume(): this; + complete(): this; + readonly isRunning: boolean; +} \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/TweenTask.js b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/TweenTask.js new file mode 100644 index 000000000..248485709 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/componentbase/tweentask/TweenTask.js @@ -0,0 +1,89 @@ +import ComponentBase from '../ComponentBase.js'; + +class TweenTask extends ComponentBase { + constructor(parent, config) { + if (config === undefined) { + config = {}; + } + /* + eventEmitter: + - false(default value): Use tween's event emitter. + - true: Create a private event emitter. + */ + if (!config.hasOwnProperty('eventEmitter')) { + config.eventEmitter = false; + } + super(parent, config); + } + + shutdown(fromScene) { + // Already shutdown + if (this.isShutdown) { + return; + } + + this.stop(); + super.shutdown(fromScene); + } + + start(tweenConfig) { + if (this.isRunning) { + return this; + } + + this.tween = this.scene.tweens.add(tweenConfig) + .on('complete', this.complete, this); + if (this.getEventEmitter() === false) { + this.setEventEmitter(this.tween); + } + return this; + } + + restart(tweenConfig) { + this.stop().start(tweenConfig); + return this; + } + + stop() { + if (!this.tween) { + return this; + } + + if (this.getEventEmitter() === this.tween) { + this.setEventEmitter(false); + } + this.tween.remove(); + this.tween = undefined; + return this; + } + + pause() { + if (!this.tween) { + return this; + } + this.tween.pause(); + return this; + } + + resume() { + if (!this.tween) { + return this; + } + this.tween.resume(); + return this; + } + + complete() { + this.stop(); + if (this.getEventEmitter()) { + this.emit('complete'); + } + return this; + } + + get isRunning() { + return (!!this.tween); + } +} + +export default TweenTask; \ No newline at end of file diff --git a/ui/src/phaser3-rex-plugins/plugins/utils/dat.gui/dat.gui.min.js b/ui/src/phaser3-rex-plugins/plugins/utils/dat.gui/dat.gui.min.js new file mode 100644 index 000000000..25b984628 --- /dev/null +++ b/ui/src/phaser3-rex-plugins/plugins/utils/dat.gui/dat.gui.min.js @@ -0,0 +1,13 @@ +/** + * dat-gui JavaScript Controller Library + * http://code.google.com/p/dat-gui + * + * Copyright 2011 Data Arts Team, Google Creative Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(e.dat={})}(this,function(e){"use strict";function t(e,t){var n=e.__state.conversionName.toString(),o=Math.round(e.r),i=Math.round(e.g),r=Math.round(e.b),s=e.a,a=Math.round(e.h),l=e.s.toFixed(1),d=e.v.toFixed(1);if(t||"THREE_CHAR_HEX"===n||"SIX_CHAR_HEX"===n){for(var c=e.hex.toString(16);c.length<6;)c="0"+c;return"#"+c}return"CSS_RGB"===n?"rgb("+o+","+i+","+r+")":"CSS_RGBA"===n?"rgba("+o+","+i+","+r+","+s+")":"HEX"===n?"0x"+e.hex.toString(16):"RGB_ARRAY"===n?"["+o+","+i+","+r+"]":"RGBA_ARRAY"===n?"["+o+","+i+","+r+","+s+"]":"RGB_OBJ"===n?"{r:"+o+",g:"+i+",b:"+r+"}":"RGBA_OBJ"===n?"{r:"+o+",g:"+i+",b:"+r+",a:"+s+"}":"HSV_OBJ"===n?"{h:"+a+",s:"+l+",v:"+d+"}":"HSVA_OBJ"===n?"{h:"+a+",s:"+l+",v:"+d+",a:"+s+"}":"unknown format"}function n(e,t,n){Object.defineProperty(e,t,{get:function(){return"RGB"===this.__state.space?this.__state[t]:(I.recalculateRGB(this,t,n),this.__state[t])},set:function(e){"RGB"!==this.__state.space&&(I.recalculateRGB(this,t,n),this.__state.space="RGB"),this.__state[t]=e}})}function o(e,t){Object.defineProperty(e,t,{get:function(){return"HSV"===this.__state.space?this.__state[t]:(I.recalculateHSV(this),this.__state[t])},set:function(e){"HSV"!==this.__state.space&&(I.recalculateHSV(this),this.__state.space="HSV"),this.__state[t]=e}})}function i(e){if("0"===e||S.isUndefined(e))return 0;var t=e.match(U);return S.isNull(t)?0:parseFloat(t[1])}function r(e){var t=e.toString();return t.indexOf(".")>-1?t.length-t.indexOf(".")-1:0}function s(e,t){var n=Math.pow(10,t);return Math.round(e*n)/n}function a(e,t,n,o,i){return o+(e-t)/(n-t)*(i-o)}function l(e,t,n,o){e.style.background="",S.each(ee,function(i){e.style.cssText+="background: "+i+"linear-gradient("+t+", "+n+" 0%, "+o+" 100%); "})}function d(e){e.style.background="",e.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);",e.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);",e.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);",e.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);",e.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}function c(e,t,n){var o=document.createElement("li");return t&&o.appendChild(t),n?e.__ul.insertBefore(o,n):e.__ul.appendChild(o),e.onResize(),o}function u(e){X.unbind(window,"resize",e.__resizeHandler),e.saveToLocalStorageIfPossible&&X.unbind(window,"unload",e.saveToLocalStorageIfPossible)}function _(e,t){var n=e.__preset_select[e.__preset_select.selectedIndex];n.innerHTML=t?n.value+"*":n.value}function h(e,t,n){if(n.__li=t,n.__gui=e,S.extend(n,{options:function(t){if(arguments.length>1){var o=n.__li.nextElementSibling;return n.remove(),f(e,n.object,n.property,{before:o,factoryArgs:[S.toArray(arguments)]})}if(S.isArray(t)||S.isObject(t)){var i=n.__li.nextElementSibling;return n.remove(),f(e,n.object,n.property,{before:i,factoryArgs:[t]})}},name:function(e){return n.__li.firstElementChild.firstElementChild.innerHTML=e,n},listen:function(){return n.__gui.listen(n),n},remove:function(){return n.__gui.remove(n),n}}),n instanceof q){var o=new Q(n.object,n.property,{min:n.__min,max:n.__max,step:n.__step});S.each(["updateDisplay","onChange","onFinishChange","step","min","max"],function(e){var t=n[e],i=o[e];n[e]=o[e]=function(){var e=Array.prototype.slice.call(arguments);return i.apply(o,e),t.apply(n,e)}}),X.addClass(t,"has-slider"),n.domElement.insertBefore(o.domElement,n.domElement.firstElementChild)}else if(n instanceof Q){var i=function(t){if(S.isNumber(n.__min)&&S.isNumber(n.__max)){var o=n.__li.firstElementChild.firstElementChild.innerHTML,i=n.__gui.__listening.indexOf(n)>-1;n.remove();var r=f(e,n.object,n.property,{before:n.__li.nextElementSibling,factoryArgs:[n.__min,n.__max,n.__step]});return r.name(o),i&&r.listen(),r}return t};n.min=S.compose(i,n.min),n.max=S.compose(i,n.max)}else n instanceof K?(X.bind(t,"click",function(){X.fakeEvent(n.__checkbox,"click")}),X.bind(n.__checkbox,"click",function(e){e.stopPropagation()})):n instanceof Z?(X.bind(t,"click",function(){X.fakeEvent(n.__button,"click")}),X.bind(t,"mouseover",function(){X.addClass(n.__button,"hover")}),X.bind(t,"mouseout",function(){X.removeClass(n.__button,"hover")})):n instanceof $&&(X.addClass(t,"color"),n.updateDisplay=S.compose(function(e){return t.style.borderLeftColor=n.__color.toString(),e},n.updateDisplay),n.updateDisplay());n.setValue=S.compose(function(t){return e.getRoot().__preset_select&&n.isModified()&&_(e.getRoot(),!0),t},n.setValue)}function p(e,t){var n=e.getRoot(),o=n.__rememberedObjects.indexOf(t.object);if(-1!==o){var i=n.__rememberedObjectIndecesToControllers[o];if(void 0===i&&(i={},n.__rememberedObjectIndecesToControllers[o]=i),i[t.property]=t,n.load&&n.load.remembered){var r=n.load.remembered,s=void 0;if(r[e.preset])s=r[e.preset];else{if(!r[se])return;s=r[se]}if(s[o]&&void 0!==s[o][t.property]){var a=s[o][t.property];t.initialValue=a,t.setValue(a)}}}}function f(e,t,n,o){if(void 0===t[n])throw new Error('Object "'+t+'" has no property "'+n+'"');var i=void 0;if(o.color)i=new $(t,n);else{var r=[t,n].concat(o.factoryArgs);i=ne.apply(e,r)}o.before instanceof z&&(o.before=o.before.__li),p(e,i),X.addClass(i.domElement,"c");var s=document.createElement("span");X.addClass(s,"property-name"),s.innerHTML=i.property;var a=document.createElement("div");a.appendChild(s),a.appendChild(i.domElement);var l=c(e,a,o.before);return X.addClass(l,he.CLASS_CONTROLLER_ROW),i instanceof $?X.addClass(l,"color"):X.addClass(l,H(i.getValue())),h(e,l,i),e.__controllers.push(i),i}function m(e,t){return document.location.href+"."+t}function g(e,t,n){var o=document.createElement("option");o.innerHTML=t,o.value=t,e.__preset_select.appendChild(o),n&&(e.__preset_select.selectedIndex=e.__preset_select.length-1)}function b(e,t){t.style.display=e.useLocalStorage?"block":"none"}function v(e){var t=e.__save_row=document.createElement("li");X.addClass(e.domElement,"has-save"),e.__ul.insertBefore(t,e.__ul.firstChild),X.addClass(t,"save-row");var n=document.createElement("span");n.innerHTML=" ",X.addClass(n,"button gears");var o=document.createElement("span");o.innerHTML="Save",X.addClass(o,"button"),X.addClass(o,"save");var i=document.createElement("span");i.innerHTML="New",X.addClass(i,"button"),X.addClass(i,"save-as");var r=document.createElement("span");r.innerHTML="Revert",X.addClass(r,"button"),X.addClass(r,"revert");var s=e.__preset_select=document.createElement("select");if(e.load&&e.load.remembered?S.each(e.load.remembered,function(t,n){g(e,n,n===e.preset)}):g(e,se,!1),X.bind(s,"change",function(){for(var t=0;t=0;n--)t=[e[n].apply(this,t)];return t[0]}},each:function(e,t,n){if(e)if(A&&e.forEach&&e.forEach===A)e.forEach(t,n);else if(e.length===e.length+0){var o=void 0,i=void 0;for(o=0,i=e.length;o1?S.toArray(arguments):arguments[0];return S.each(O,function(t){if(t.litmus(e))return S.each(t.conversions,function(t,n){if(T=t.read(e),!1===L&&!1!==T)return L=T,T.conversionName=n,T.conversion=t,S.BREAK}),S.BREAK}),L},B=void 0,N={hsv_to_rgb:function(e,t,n){var o=Math.floor(e/60)%6,i=e/60-Math.floor(e/60),r=n*(1-t),s=n*(1-i*t),a=n*(1-(1-i)*t),l=[[n,a,r],[s,n,r],[r,n,a],[r,s,n],[a,r,n],[n,r,s]][o];return{r:255*l[0],g:255*l[1],b:255*l[2]}},rgb_to_hsv:function(e,t,n){var o=Math.min(e,t,n),i=Math.max(e,t,n),r=i-o,s=void 0,a=void 0;return 0===i?{h:NaN,s:0,v:0}:(a=r/i,s=e===i?(t-n)/r:t===i?2+(n-e)/r:4+(e-t)/r,(s/=6)<0&&(s+=1),{h:360*s,s:a,v:i/255})},rgb_to_hex:function(e,t,n){var o=this.hex_with_component(0,2,e);return o=this.hex_with_component(o,1,t),o=this.hex_with_component(o,0,n)},component_from_hex:function(e,t){return e>>8*t&255},hex_with_component:function(e,t,n){return n<<(B=8*t)|e&~(255<this.__max&&(n=this.__max),void 0!==this.__step&&n%this.__step!=0&&(n=Math.round(n/this.__step)*this.__step),j(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"setValue",this).call(this,n)}},{key:"min",value:function(e){return this.__min=e,this}},{key:"max",value:function(e){return this.__max=e,this}},{key:"step",value:function(e){return this.__step=e,this.__impliedStep=e,this.__precision=r(e),this}}]),t}(),Q=function(e){function t(e,n,o){function i(){l.__onFinishChange&&l.__onFinishChange.call(l,l.getValue())}function r(e){var t=d-e.clientY;l.setValue(l.getValue()+t*l.__impliedStep),d=e.clientY}function s(){X.unbind(window,"mousemove",r),X.unbind(window,"mouseup",s),i()}F(this,t);var a=V(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n,o));a.__truncationSuspended=!1;var l=a,d=void 0;return a.__input=document.createElement("input"),a.__input.setAttribute("type","text"),X.bind(a.__input,"change",function(){var e=parseFloat(l.__input.value);S.isNaN(e)||l.setValue(e)}),X.bind(a.__input,"blur",function(){i()}),X.bind(a.__input,"mousedown",function(e){X.bind(window,"mousemove",r),X.bind(window,"mouseup",s),d=e.clientY}),X.bind(a.__input,"keydown",function(e){13===e.keyCode&&(l.__truncationSuspended=!0,this.blur(),l.__truncationSuspended=!1,i())}),a.updateDisplay(),a.domElement.appendChild(a.__input),a}return D(t,W),P(t,[{key:"updateDisplay",value:function(){return this.__input.value=this.__truncationSuspended?this.getValue():s(this.getValue(),this.__precision),j(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"updateDisplay",this).call(this)}}]),t}(),q=function(e){function t(e,n,o,i,r){function s(e){e.preventDefault();var t=_.__background.getBoundingClientRect();return _.setValue(a(e.clientX,t.left,t.right,_.__min,_.__max)),!1}function l(){X.unbind(window,"mousemove",s),X.unbind(window,"mouseup",l),_.__onFinishChange&&_.__onFinishChange.call(_,_.getValue())}function d(e){var t=e.touches[0].clientX,n=_.__background.getBoundingClientRect();_.setValue(a(t,n.left,n.right,_.__min,_.__max))}function c(){X.unbind(window,"touchmove",d),X.unbind(window,"touchend",c),_.__onFinishChange&&_.__onFinishChange.call(_,_.getValue())}F(this,t);var u=V(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n,{min:o,max:i,step:r})),_=u;return u.__background=document.createElement("div"),u.__foreground=document.createElement("div"),X.bind(u.__background,"mousedown",function(e){document.activeElement.blur(),X.bind(window,"mousemove",s),X.bind(window,"mouseup",l),s(e)}),X.bind(u.__background,"touchstart",function(e){1===e.touches.length&&(X.bind(window,"touchmove",d),X.bind(window,"touchend",c),d(e))}),X.addClass(u.__background,"slider"),X.addClass(u.__foreground,"slider-fg"),u.updateDisplay(),u.__background.appendChild(u.__foreground),u.domElement.appendChild(u.__background),u}return D(t,W),P(t,[{key:"updateDisplay",value:function(){var e=(this.getValue()-this.__min)/(this.__max-this.__min);return this.__foreground.style.width=100*e+"%",j(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"updateDisplay",this).call(this)}}]),t}(),Z=function(e){function t(e,n,o){F(this,t);var i=V(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n)),r=i;return i.__button=document.createElement("div"),i.__button.innerHTML=void 0===o?"Fire":o,X.bind(i.__button,"click",function(e){return e.preventDefault(),r.fire(),!1}),X.addClass(i.__button,"button"),i.domElement.appendChild(i.__button),i}return D(t,z),P(t,[{key:"fire",value:function(){this.__onChange&&this.__onChange.call(this),this.getValue().call(this.object),this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())}}]),t}(),$=function(e){function t(e,n){function o(e){u(e),X.bind(window,"mousemove",u),X.bind(window,"touchmove",u),X.bind(window,"mouseup",r),X.bind(window,"touchend",r)}function i(e){_(e),X.bind(window,"mousemove",_),X.bind(window,"touchmove",_),X.bind(window,"mouseup",s),X.bind(window,"touchend",s)}function r(){X.unbind(window,"mousemove",u),X.unbind(window,"touchmove",u),X.unbind(window,"mouseup",r),X.unbind(window,"touchend",r),c()}function s(){X.unbind(window,"mousemove",_),X.unbind(window,"touchmove",_),X.unbind(window,"mouseup",s),X.unbind(window,"touchend",s),c()}function a(){var e=R(this.value);!1!==e?(p.__color.__state=e,p.setValue(p.__color.toOriginal())):this.value=p.__color.toString()}function c(){p.__onFinishChange&&p.__onFinishChange.call(p,p.__color.toOriginal())}function u(e){-1===e.type.indexOf("touch")&&e.preventDefault();var t=p.__saturation_field.getBoundingClientRect(),n=e.touches&&e.touches[0]||e,o=n.clientX,i=n.clientY,r=(o-t.left)/(t.right-t.left),s=1-(i-t.top)/(t.bottom-t.top);return s>1?s=1:s<0&&(s=0),r>1?r=1:r<0&&(r=0),p.__color.v=s,p.__color.s=r,p.setValue(p.__color.toOriginal()),!1}function _(e){-1===e.type.indexOf("touch")&&e.preventDefault();var t=p.__hue_field.getBoundingClientRect(),n=1-((e.touches&&e.touches[0]||e).clientY-t.top)/(t.bottom-t.top);return n>1?n=1:n<0&&(n=0),p.__color.h=360*n,p.setValue(p.__color.toOriginal()),!1}F(this,t);var h=V(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n));h.__color=new I(h.getValue()),h.__temp=new I(0);var p=h;h.domElement=document.createElement("div"),X.makeSelectable(h.domElement,!1),h.__selector=document.createElement("div"),h.__selector.className="selector",h.__saturation_field=document.createElement("div"),h.__saturation_field.className="saturation-field",h.__field_knob=document.createElement("div"),h.__field_knob.className="field-knob",h.__field_knob_border="2px solid ",h.__hue_knob=document.createElement("div"),h.__hue_knob.className="hue-knob",h.__hue_field=document.createElement("div"),h.__hue_field.className="hue-field",h.__input=document.createElement("input"),h.__input.type="text",h.__input_textShadow="0 1px 1px ",X.bind(h.__input,"keydown",function(e){13===e.keyCode&&a.call(this)}),X.bind(h.__input,"blur",a),X.bind(h.__selector,"mousedown",function(){X.addClass(this,"drag").bind(window,"mouseup",function(){X.removeClass(p.__selector,"drag")})}),X.bind(h.__selector,"touchstart",function(){X.addClass(this,"drag").bind(window,"touchend",function(){X.removeClass(p.__selector,"drag")})});var f=document.createElement("div");return S.extend(h.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"}),S.extend(h.__field_knob.style,{position:"absolute",width:"12px",height:"12px",border:h.__field_knob_border+(h.__color.v<.5?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1}),S.extend(h.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1}),S.extend(h.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"}),S.extend(f.style,{width:"100%",height:"100%",background:"none"}),l(f,"top","rgba(0,0,0,0)","#000"),S.extend(h.__hue_field.style,{width:"15px",height:"100px",border:"1px solid #555",cursor:"ns-resize",position:"absolute",top:"3px",right:"3px"}),d(h.__hue_field),S.extend(h.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:h.__input_textShadow+"rgba(0,0,0,0.7)"}),X.bind(h.__saturation_field,"mousedown",o),X.bind(h.__saturation_field,"touchstart",o),X.bind(h.__field_knob,"mousedown",o),X.bind(h.__field_knob,"touchstart",o),X.bind(h.__hue_field,"mousedown",i),X.bind(h.__hue_field,"touchstart",i),h.__saturation_field.appendChild(f),h.__selector.appendChild(h.__field_knob),h.__selector.appendChild(h.__saturation_field),h.__selector.appendChild(h.__hue_field),h.__hue_field.appendChild(h.__hue_knob),h.domElement.appendChild(h.__input),h.domElement.appendChild(h.__selector),h.updateDisplay(),h}return D(t,z),P(t,[{key:"updateDisplay",value:function(){var e=R(this.getValue());if(!1!==e){var t=!1;S.each(I.COMPONENTS,function(n){if(!S.isUndefined(e[n])&&!S.isUndefined(this.__color.__state[n])&&e[n]!==this.__color.__state[n])return t=!0,{}},this),t&&S.extend(this.__color.__state,e)}S.extend(this.__temp.__state,this.__color.__state),this.__temp.a=1;var n=this.__color.v<.5||this.__color.s>.5?255:0,o=255-n;S.extend(this.__field_knob.style,{marginLeft:100*this.__color.s-7+"px",marginTop:100*(1-this.__color.v)-7+"px",backgroundColor:this.__temp.toHexString(),border:this.__field_knob_border+"rgb("+n+","+n+","+n+")"}),this.__hue_knob.style.marginTop=100*(1-this.__color.h/360)+"px",this.__temp.s=1,this.__temp.v=1,l(this.__saturation_field,"left","#fff",this.__temp.toHexString()),this.__input.value=this.__color.toString(),S.extend(this.__input.style,{backgroundColor:this.__color.toHexString(),color:"rgb("+n+","+n+","+n+")",textShadow:this.__input_textShadow+"rgba("+o+","+o+","+o+",.7)"})}}]),t}(),ee=["-moz-","-o-","-webkit-","-ms-",""],te={load:function(e,t){var n=t||document,o=n.createElement("link");o.type="text/css",o.rel="stylesheet",o.href=e,n.getElementsByTagName("head")[0].appendChild(o)},inject:function(e,t){var n=t||document,o=document.createElement("style");o.type="text/css",o.innerHTML=e;var i=n.getElementsByTagName("head")[0];try{i.appendChild(o)}catch(e){}}},ne=function(e,t){var n=e[t];return S.isArray(arguments[2])||S.isObject(arguments[2])?new Y(e,t,arguments[2]):S.isNumber(n)?S.isNumber(arguments[2])&&S.isNumber(arguments[3])?S.isNumber(arguments[4])?new q(e,t,arguments[2],arguments[3],arguments[4]):new q(e,t,arguments[2],arguments[3]):S.isNumber(arguments[4])?new Q(e,t,{min:arguments[2],max:arguments[3],step:arguments[4]}):new Q(e,t,{min:arguments[2],max:arguments[3]}):S.isString(n)?new J(e,t):S.isFunction(n)?new Z(e,t,""):S.isBoolean(n)?new K(e,t):null},oe=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e){setTimeout(e,1e3/60)},ie=function(){function e(){F(this,e),this.backgroundElement=document.createElement("div"),S.extend(this.backgroundElement.style,{backgroundColor:"rgba(0,0,0,0.8)",top:0,left:0,display:"none",zIndex:"1000",opacity:0,WebkitTransition:"opacity 0.2s linear",transition:"opacity 0.2s linear"}),X.makeFullscreen(this.backgroundElement),this.backgroundElement.style.position="fixed",this.domElement=document.createElement("div"),S.extend(this.domElement.style,{position:"fixed",display:"none",zIndex:"1001",opacity:0,WebkitTransition:"-webkit-transform 0.2s ease-out, opacity 0.2s linear",transition:"transform 0.2s ease-out, opacity 0.2s linear"}),document.body.appendChild(this.backgroundElement),document.body.appendChild(this.domElement);var t=this;X.bind(this.backgroundElement,"click",function(){t.hide()})}return P(e,[{key:"show",value:function(){var e=this;this.backgroundElement.style.display="block",this.domElement.style.display="block",this.domElement.style.opacity=0,this.domElement.style.webkitTransform="scale(1.1)",this.layout(),S.defer(function(){e.backgroundElement.style.opacity=1,e.domElement.style.opacity=1,e.domElement.style.webkitTransform="scale(1)"})}},{key:"hide",value:function(){var e=this,t=function t(){e.domElement.style.display="none",e.backgroundElement.style.display="none",X.unbind(e.domElement,"webkitTransitionEnd",t),X.unbind(e.domElement,"transitionend",t),X.unbind(e.domElement,"oTransitionEnd",t)};X.bind(this.domElement,"webkitTransitionEnd",t),X.bind(this.domElement,"transitionend",t),X.bind(this.domElement,"oTransitionEnd",t),this.backgroundElement.style.opacity=0,this.domElement.style.opacity=0,this.domElement.style.webkitTransform="scale(1.1)"}},{key:"layout",value:function(){this.domElement.style.left=window.innerWidth/2-X.getWidth(this.domElement)/2+"px",this.domElement.style.top=window.innerHeight/2-X.getHeight(this.domElement)/2+"px"}}]),e}(),re=function(e){if(e&&"undefined"!=typeof window){var t=document.createElement("style");return t.setAttribute("type","text/css"),t.innerHTML=e,document.head.appendChild(t),e}}(".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear;border:0;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button.close-top{position:relative}.dg.main .close-button.close-bottom{position:absolute}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-y:visible}.dg.a.has-save>ul.close-top{margin-top:0}.dg.a.has-save>ul.close-bottom{margin-top:27px}.dg.a.has-save>ul.closed{margin-top:0}.dg.a .save-row{top:0;z-index:1002}.dg.a .save-row.close-top{position:relative}.dg.a .save-row.close-bottom{position:fixed}.dg li{-webkit-transition:height .1s ease-out;-o-transition:height .1s ease-out;-moz-transition:height .1s ease-out;transition:height .1s ease-out;-webkit-transition:overflow .1s linear;-o-transition:overflow .1s linear;-moz-transition:overflow .1s linear;transition:overflow .1s linear}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li>*{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px;overflow:hidden}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%;position:relative}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:7px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .cr.color{overflow:visible}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.color{border-left:3px solid}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2FA1D6}.dg .cr.number input[type=text]{color:#2FA1D6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2FA1D6;max-width:100%}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n");te.inject(re);var se="Default",ae=function(){try{return!!window.localStorage}catch(e){return!1}}(),le=void 0,de=!0,ce=void 0,ue=!1,_e=[],he=function e(t){var n=this,o=t||{};this.domElement=document.createElement("div"),this.__ul=document.createElement("ul"),this.domElement.appendChild(this.__ul),X.addClass(this.domElement,"dg"),this.__folders={},this.__controllers=[],this.__rememberedObjects=[],this.__rememberedObjectIndecesToControllers=[],this.__listening=[],o=S.defaults(o,{closeOnTop:!1,autoPlace:!0,width:e.DEFAULT_WIDTH}),o=S.defaults(o,{resizable:o.autoPlace,hideable:o.autoPlace}),S.isUndefined(o.load)?o.load={preset:se}:o.preset&&(o.load.preset=o.preset),S.isUndefined(o.parent)&&o.hideable&&_e.push(this),o.resizable=S.isUndefined(o.parent)&&o.resizable,o.autoPlace&&S.isUndefined(o.scrollable)&&(o.scrollable=!0);var i=ae&&"true"===localStorage.getItem(m(this,"isLocal")),r=void 0,s=void 0;if(Object.defineProperties(this,{parent:{get:function(){return o.parent}},scrollable:{get:function(){return o.scrollable}},autoPlace:{get:function(){return o.autoPlace}},closeOnTop:{get:function(){return o.closeOnTop}},preset:{get:function(){return n.parent?n.getRoot().preset:o.load.preset},set:function(e){n.parent?n.getRoot().preset=e:o.load.preset=e,E(this),n.revert()}},width:{get:function(){return o.width},set:function(e){o.width=e,w(n,e)}},name:{get:function(){return o.name},set:function(e){o.name=e,s&&(s.innerHTML=o.name)}},closed:{get:function(){return o.closed},set:function(t){o.closed=t,o.closed?X.addClass(n.__ul,e.CLASS_CLOSED):X.removeClass(n.__ul,e.CLASS_CLOSED),this.onResize(),n.__closeButton&&(n.__closeButton.innerHTML=t?e.TEXT_OPEN:e.TEXT_CLOSED)}},load:{get:function(){return o.load}},useLocalStorage:{get:function(){return i},set:function(e){ae&&(i=e,e?X.bind(window,"unload",r):X.unbind(window,"unload",r),localStorage.setItem(m(n,"isLocal"),e))}}}),S.isUndefined(o.parent)){if(this.closed=o.closed||!1,X.addClass(this.domElement,e.CLASS_MAIN),X.makeSelectable(this.domElement,!1),ae&&i){n.useLocalStorage=!0;var a=localStorage.getItem(m(this,"gui"));a&&(o.load=JSON.parse(a))}this.__closeButton=document.createElement("div"),this.__closeButton.innerHTML=e.TEXT_CLOSED,X.addClass(this.__closeButton,e.CLASS_CLOSE_BUTTON),o.closeOnTop?(X.addClass(this.__closeButton,e.CLASS_CLOSE_TOP),this.domElement.insertBefore(this.__closeButton,this.domElement.childNodes[0])):(X.addClass(this.__closeButton,e.CLASS_CLOSE_BOTTOM),this.domElement.appendChild(this.__closeButton)),X.bind(this.__closeButton,"click",function(){n.closed=!n.closed})}else{void 0===o.closed&&(o.closed=!0);var l=document.createTextNode(o.name);X.addClass(l,"controller-name"),s=c(n,l);X.addClass(this.__ul,e.CLASS_CLOSED),X.addClass(s,"title"),X.bind(s,"click",function(e){return e.preventDefault(),n.closed=!n.closed,!1}),o.closed||(this.closed=!1)}o.autoPlace&&(S.isUndefined(o.parent)&&(de&&(ce=document.createElement("div"),X.addClass(ce,"dg"),X.addClass(ce,e.CLASS_AUTO_PLACE_CONTAINER),document.body.appendChild(ce),de=!1),ce.appendChild(this.domElement),X.addClass(this.domElement,e.CLASS_AUTO_PLACE)),this.parent||w(n,o.width)),this.__resizeHandler=function(){n.onResizeDebounced()},X.bind(window,"resize",this.__resizeHandler),X.bind(this.__ul,"webkitTransitionEnd",this.__resizeHandler),X.bind(this.__ul,"transitionend",this.__resizeHandler),X.bind(this.__ul,"oTransitionEnd",this.__resizeHandler),this.onResize(),o.resizable&&y(this),r=function(){ae&&"true"===localStorage.getItem(m(n,"isLocal"))&&localStorage.setItem(m(n,"gui"),JSON.stringify(n.getSaveObject()))},this.saveToLocalStorageIfPossible=r,o.parent||function(){var e=n.getRoot();e.width+=1,S.defer(function(){e.width-=1})}()};he.toggleHide=function(){ue=!ue,S.each(_e,function(e){e.domElement.style.display=ue?"none":""})},he.CLASS_AUTO_PLACE="a",he.CLASS_AUTO_PLACE_CONTAINER="ac",he.CLASS_MAIN="main",he.CLASS_CONTROLLER_ROW="cr",he.CLASS_TOO_TALL="taller-than-window",he.CLASS_CLOSED="closed",he.CLASS_CLOSE_BUTTON="close-button",he.CLASS_CLOSE_TOP="close-top",he.CLASS_CLOSE_BOTTOM="close-bottom",he.CLASS_DRAG="drag",he.DEFAULT_WIDTH=245,he.TEXT_CLOSED="Close Controls",he.TEXT_OPEN="Open Controls",he._keydownHandler=function(e){"text"===document.activeElement.type||72!==e.which&&72!==e.keyCode||he.toggleHide()},X.bind(window,"keydown",he._keydownHandler,!1),S.extend(he.prototype,{add:function(e,t){return f(this,e,t,{factoryArgs:Array.prototype.slice.call(arguments,2)})},addColor:function(e,t){return f(this,e,t,{color:!0})},remove:function(e){this.__ul.removeChild(e.__li),this.__controllers.splice(this.__controllers.indexOf(e),1);var t=this;S.defer(function(){t.onResize()})},destroy:function(){if(this.parent)throw new Error("Only the root GUI should be removed with .destroy(). For subfolders, use gui.removeFolder(folder) instead.");this.autoPlace&&ce.removeChild(this.domElement);var e=this;S.each(this.__folders,function(t){e.removeFolder(t)}),X.unbind(window,"keydown",he._keydownHandler,!1),u(this)},addFolder:function(e){if(void 0!==this.__folders[e])throw new Error('You already have a folder in this GUI by the name "'+e+'"');var t={name:e,parent:this};t.autoPlace=this.autoPlace,this.load&&this.load.folders&&this.load.folders[e]&&(t.closed=this.load.folders[e].closed,t.load=this.load.folders[e]);var n=new he(t);this.__folders[e]=n;var o=c(this,n.domElement);return X.addClass(o,"folder"),n},removeFolder:function(e){this.__ul.removeChild(e.domElement.parentElement),delete this.__folders[e.name],this.load&&this.load.folders&&this.load.folders[e.name]&&delete this.load.folders[e.name],u(e);var t=this;S.each(e.__folders,function(t){e.removeFolder(t)}),S.defer(function(){t.onResize()})},open:function(){this.closed=!1},close:function(){this.closed=!0},onResize:function(){var e=this.getRoot();if(e.scrollable){var t=X.getOffset(e.__ul).top,n=0;S.each(e.__ul.childNodes,function(t){e.autoPlace&&t===e.__save_row||(n+=X.getHeight(t))}),window.innerHeight-t-20GUI\'s constructor:\n\n \n\n