Tuesday, November 17, 2015

ProductivityGame Prototype: Roguelike Released!

PGP Roguelike Released!


This is the first release of the Productivity Game Prototype: Roguelike

In the link below I've provided builds for Windows, Linux, OSX and Android.  The Android build is barely usable on my phone because all of the UI elements need to be resized for the smaller screen size.

Download here

More info about using the Productivity Game can be found in this video:


Rename?

 I'm considering changing the name of the project from Productivity Game to Productive Game, but I'm just not sure which is better.  Please let me know what you think in the comments below or send an email to stewart@productivegaming.com

Also, I moved this blog from fragmentalstew.com to productivegaming.com.  Fragmentalstew.com still redirects here, though.  I thought productivegaming indicates better what I'm trying to create.

Also, I added a page for the Habit Engine, a page for the Productivity Game, and I added a lot more info to the About page, about what I'm trying to accomplish.  I encourage you to click the tabs at the top to read more.  I also changed the Template of the website.

Sunday, November 15, 2015

Dev Update #18 Ready for Release, Fixed Slow/Drunken Movement Bug, Found New Bug in Saving/Loading


Fixed Slow/Drunken Movement Bug


I finally fixed the Slow/Drunken movement Bug that has been plaguing me for weeks.  As near as I can tell there wasn't any bugs in my code, but there was some buggy weirdness in the project itself.  So, I created a new project.  Exported all of the assets with dependencies from my old project, copied over the tagmanager.asset from the projectsettings folder, and everything was fixed.  This created a new issue with horizontal lines on the edges of the tiles of the board, but this doesn't seem to show up in builds so it's not really an issue.  With this issue resolved, I should have been ready to release today except I did some more testing and...

Fixed Saving and Loading Bug


During testing for release I noticed that saving and loading was working correctly.  I was pretty certain it had been working right, but after examining the code, I noticed that I had missed some crucial lines of code.  Random.seed was being saved when the program began, but not when a new level was loaded.  When the new level was loaded, Random.seed had a different value, but that value wasn't being saved so restarting the level would give you a different level layout.

I added the needed code, which should have resolved the issue, but it did not.  In order to debug, I decided to use the unmodified version of the Roguelike tutorial.  I made the necessary changes and saving and loading still wasn't working correctly.

Each time a Random value is called, the Random.Seed changes.  As long as all of those calls are in exactly the same order, and you start with the same value for Random.Seed you should get the same Random values every time.  But for some reason, this wasn't happening.

In order to fix it I added this line:
Random.seed = Random.seed;
Let that sink in for a moment...
Yes, I am aware that it makes no sense.  Yes, it really does fix the issue with the Random values being different every time.

So, after making that change in the unmodified project, I made that change in my modified project, and lo and behold it did not work.  I realized that this was being caused by my use of coroutines instead of functions.  In code, the first function has to finish before the rest of the code runs.  With coroutines, the code starts the coroutine and then continues execution.  Each coroutine was making a call to Random.Range at it's beginning.  When the level is loading, those calls to Random.Range are in order, and then the other calls to Random.Range happen when a todo is clicked. Since, when the level is loaded, it skips the while loops that wait for each todo to be checked, that caused the order to be different.

So, I created some bools and added some more while wait loops between the running of the coroutines that setup the level.  After the level is loaded, the finished bool is set to true so that the next coroutine is run and this solved the execution order issue so that levels should finally load exactly the same whether they are being loaded sequentially after each todo or whether they are loading all at once.

Then I realized that I could have just used "yield return StartCoroutine" to call each coroutine, and that would make code execution wait for the coroutine to finish before continuing.

I feel kind of silly for not doing that from the beginning.  Live and learn, I guess.

Ready for Release


And with those last bug fixes, I feel confident in releasing the prototype.   I would like to do a little work on the website and might get a new domain as well.   Stay tuned. 





Monday, November 2, 2015

Dev Update #17 Broken Release

Broken Release

I had the game all finished up.  Then I built and tested the game, and for some reason that I don't understand, all character movement moves very, very slowly.  One move takes about two full seconds, which is far too slow.  I have no idea why this is happening.  Also, there is far too much CPU being used.  It jumped from less than 10% CPU usage to between 40 and 60%.  And it doesn't seem consistent.  Sometimes the build will run with less CPU or it will have lower cpu usage and then jump higher.

Here is a video showing the slow movement in the build:


Even in the editor there seems to be some issues with movement.  The characters move at full speed(as far as I can tell), but the player will weave around and not stay within the tiles.  This seems highly inconsistent, because sometimes the player will move normally with absolutely no changes made.

Here is an example of the weaving when run in the editor:



Again, I don't know why any of this happening.  There doesn't seem to be any bugs in my code that would cause this, and I don't know why movement would be so much slower in the build rather than the editor, or why cpu usage would jump.

This issue with movement and cpu usage is the only thing preventing me from making a release.

If anyone thinks they can help with this issue, or with development of the project, I can give access to the bitbucket project or a link to download an archive.

Monday, October 26, 2015

Dev Update #16 Speed, Tweaked UI, Modified Player Pause, P and M Key, Random Pausing, Build Mode

Changed Button Presses So They Don't Interfere With Typing

I set the "M" key press to only work when the game is paused.  This prevents "M" from pausing the music when typing a todo.  I had to entirely disable the "P" key press for pausing the game, because there was no elegant way to change it using the current code. There's probably some way to do it involving the AddTodo field focus, but it's relatively minor and I'm just going to leave it out for now.

Modified The Way Player is Paused During Level Loading

I was disabling the Player object during board setup, and then reenabling when board setup was finished. I have suspected that having the player game object disabled may interfere somewhat with the movement of the enemies.  It gives me no errors, but just to be sure, I decided to disable just the sprite and then set the Player script to paused.  I wasn't able to access the Player.player.paused(player is a reference to the instance) variable from BoardManager, even though I can access it fine from other scripts.  I get a "null reference error" - this gave me a lot of grief in the past.  I suspect it has something to do with the BoardManager being instantiated, whereas the other scripts that are accessing Player.player.paused are not being instantiated.  They just exist within the scene.

So, instead, I used:

        playerObj = GameObject.Find("Player");
        playerScript = playerObj.GetComponent<Player>();
        playerScript.paused = true;

 It's less elegant than "Player.player.paused = true;", but it works.  I'm not sure if it makes any difference.  The enemies do seem a little more responsive in their attacks, but it's hard to tell. They still seem to move in roughly the same pattern.

Tweaked UI elements

I tweaked a lot of the different UI elements - adding padding and slightly modifying shape.  I changed the font on many UI elements to PressStart2P-Regular, that came with the Unity Rogue 2d Tutorial.  I chose to continue using Arial on the Todos, because Arial looks better at a smaller font size, and I didn't want to make the fonts big enough to look good use the PressStart font.  I made the text darker on the todos and the other UI elements with grey backgrounds

Random Pausing Fixed

I fixed the Inputfield from (seemingly randomly) being selected while you're moving the character.  It turns out is wasn't random at all, it was the UI navigating between UI elements until it hit the input field.  I turned the input field navigation to off, which should solve that.

Speed

I added a speed modal.  It sets each todo to complete 1, 2, 3, or 4 tiles when they are checked.  I would go into more detail about how I did this, but the actual creation of the modal was a bit complex.  It's not really a modal as much as a configuration window that pops up on the right panel.  In order to activate and deactivate the  modal, I had to create a separate script that mostly just keeps track of the "speed" variable, that is never deactivated.

Build Mode

I also added an image and text that says "Build Mode" instead of "Paused" when the level is building.


What's Next

I think I'm going to add some kind of instructions.  I'm debating whether or not I want to implement any kind of deleting or editing of todos before release.



Tuesday, October 20, 2015

Dev Update #15 - Almost Done. Exit Game Button. Confirmation Dialog Boxes

Exit Game Button

10/18/2015 I added an Exit game button using Application.Quit(); and also moved the Right Panel Buttons off of a Scrollview that wasn't doing anything and redid the anchors for the buttons so they would scale correctly.

Confirmation Dialog Boxes (Modal Windows)

10/20/2015   I added some Modal windows for 6 Reset Game and Exit Game.  I started by watching the part 1 of 3 of the Unity tutorial/live session UI TOOLS: MAKING A GENERIC MODAL WINDOW, and then decided I didn't need such a complex solution for just 2 modal windows, so I hacked together some panels and buttons, copied them, and then modified my original Exit Game and Reset Game button scripts.

The ExitGame script looks like this:

using UnityEngine;
using System.Collections;
public class ExitButton : MonoBehaviour
{
    public GameObject exitModal;
    public GameObject resetModal;
     public void ExitGamePressed()
        {
            if (resetModal.activeSelf == false)
            exitModal.SetActive(true);
        }
     public void ExitGame()
     {
         Application.Quit();
   
     }
     public void ClosePanel()
     {
         exitModal.SetActive(false);
     }
}

The ResetGame script is mostly the same except with "Reset" in place of "Exit" and instead of Application.Quit, it calls the ResetGame function from BoardManager.  All the code I added to the two scripts is almost the same between both scripts, which seems wasteful and I'm sure there's a better way, but I'm more concerned with getting it functional. I may rewrite later.

The appropriate functions are then assigned to the appropriate button's onClick events and the appropriate GameObjects are assigned to the scripts.  Both exitModal and ResetModal are set to inactive in the editor.

What's Next

I'm pretty much done.  I've made a build and I'm testing it.  Currently the key press "P" will pause/unpause and "M" will pause/unpause the music which is a problem when typing in todos, so I'll fix that.  I'll readjust a few things in the UI to try to make them look a little more professional.  I don't really like the way the text looks. I might try to make that look a little nicer.  Some graphics on the UI elements would be nice, but I don't think I'm going to touch that for the prototype.

There's always more I could do, but as far as I'm concerned, I've accomplished all my goals.  So once, I've polished things up a little, and done a little testing, I'm going to make Windows, Mac OSX, linux, and maybe Android builds and release them all simultaneously on this website.  That should be very soon.
Running in a Build

Saturday, October 17, 2015

Dev Update #14 Reset Button Fixed(Again), Pausing Works, Window Scaling Fixed, Unused Todos Usable

Reset Button Fixed(Again)

In my attempt to get Pause to work today, I discovered in issue with the Reset Button I should have noticed the first time.

I was using a checkedOff variable in my TodosHandler script to pass a variable to the BoardManager script, that manages the game board.  The TodoScript attached to the TodoButton sets the checkedOff variable in the TodosHandler to true.  Update() in BoardManager waits for the checkedOff variable in TodosHandler to be true, and then it sets the "clicked" variable in BoardManager to true.  This signals a while loop to stop, which then loads the next tile in BoardManager.

Originally, I thought this wasn't working when the game was paused because Update() wasn't running when time.TimeScale was set to 0.  This, however was false, but I didn't know that at the time.  To fix this, I gave the TodoScript direct access to the "clicked" variable in the BoardManager script, and let it set "clicked" directly.  This was changing the variable to true, but it did not trigger the while loop to stop.

The reason for this, I eventually discovered, was that I had multiple instances of BoardManager.  I discovered this by using:
Debug.Log("Parent of this BoardManager is " + transform.parent.gameObject);
Debug.Log("Root parent of this BoardManager is " + transform.root.gameObject);

I'm not entirely sure why my previous fix for the ResetButton worked.  I'm guessing that the change I made allowed the ResetButton script to find only one copy of the BoardManager instance instead of two, or maybe it found the correct instance instead of the wrong one.

So I removed the copy of BoardManager from the ResetButton and changed the ResetButton code to:
        BoardManager.boardManager.ResetBoard();
In order to access the variable directly from TodoScript, I had previously added to my list of variables:
    public static BoardManager boardManager = null; 
and then also added, to the "void Awake();" function:

         boardManager = this;
 This allowed me to access the ResetBoard() function directly as well.

However,  after implementing this code, I came to discover that tiles are still not loaded when the game is paused.  Apparently Update() is still called while Time.timeScale is 0, but
while (!clicked)
                {
                    yield return new WaitForSeconds(0.01f);
                                     
                }
never completes, because time has stopped.

Pausing Works

Since setting Time.timeScale to 0 doesn't work with WaitForSeconds, I instead decided to pause the player by making a "paused" variable in the Player script.  I made that variable accessible by other scripts so that those scripts can pause the player.

The player moves through:

            horizontal = (int)Input.GetAxisRaw("Horizontal");
            vertical = (int)Input.GetAxisRaw("Vertical");
and some other scripts in Update() of the Player script, so I added "if (!paused)" before that.

I then added code to the Pause button script to set Player.player.pause to true or false and I added an "Event Trigger" component to the Todo field.  I then added a Select and Deselect even to the event trigger, added a PausePlayer and UnpausePlayer function to the Pause script, and assigned those to Select and Deselect so that the player is paused when the field is selected and unpaused when the field is deselected.  This keeps the player movement from being effected when typing in todo names and keeps todos from being typed in when the player is moving.

I also added a black image, with an alpha, with the word "Paused" overlayed on top of the game as a visual queue.


I also added a gameActive variable to the boardManager so that the pause scripts can check to see if the game is Active.  The game is by default, paused when the level is loading, so this prevents the scripts from trying to unpause the game when the level is still loading.

Currently music doesn't pause with the game, but it can be paused separately by clicking the music button.  I did this intentionally to give players more control over whether the music is, or is not playing.  The music does start by default when the level is finished loading.

Window Scaling Fixed(sort of)

I (sort of) fixed scaling.  I set the aspect ratio to 16:9 and then set the windows to stretch vertically, with their anchors set to the bound of the panel.  This makes the panels scale correctly.  However, the aspect ratio is stuck at 16:9

Unused Todos

Previously, all todos you did while the game was active would not be counted.  I changed this, so that there is now a counter in BoardManager that keeps track of those unused todos.  There is also a panel that tell you how many unused todos you have, and a button you can click that will use those unused todo points as if they were a todo you were clicking on the left panel.  I could give more technical information, but this post is already a bit long.

What's Next

I may add some kind of confirmation popup for the Reset Game button that asks whether you are sure you want to reset the game, and maybe some hover over tooltips if that's possible, but that's the only thing I can think of right now. 

I'll take a little time to think about whether there is anything else I want to add before release.  I'll also do some testing and make some builds.  Then I will release a working build for everyone to download.





Thursday, October 15, 2015

Dev Update #13 Fixed Reset Game Bug

Fixed Reset Game Bug

I set out to fix the reset game bug, today, and was successful.

After resetting the game, the tiles would continue to load even after the game had started.  I thought this might be caused by having a second instance of the BoardManager script attached to the ResetButton script.  I did that, because assigning the BoardManager script, that was already in the scenne to the ResetButton script wasn't working.  First, I tried uncommenting my previous code to see why it didn't work and it worked perfectly fine.  I don't have a clue why it didn't work the first time, I'm just glad it's working now.

The code looks like this:
boardScript = FindObjectOfType(typeof(BoardManager)) as BoardManager;

Next up: Bug Fixing Pause 

Next up, I need to get some sort of pausing of the game working.  Currently, pausing the game prevents tiles from being loaded when they're checked off.  Currently, to pause the game, I'm using:
if (Time.timeScale == 1)
             {
                 Time.timeScale = 0;
                 AudioListener.pause = true;
             }
             else
             {
                 Time.timeScale = 1;
                 AudioListener.pause = false;
             }
this prevents all of the Update() events from triggering in the game.  The code that triggers the next tile to be loaded looks like this:

    void Update()
    {
        if (TodosHandler.todosHandler.checkedOff)
            clicked = true;
    }

And this is why tiles are not loaded when the game is paused.  I now have to either rewrite my code so that I am not using Update() to wait for a checkedOff event, or I have to rewrite the pause so that the player is turned off.  I know how to do that latter.  I have a vague idea about how to do the former.  I'm not sure which is best.  I was going to work on that today, but then I had personal matters to attend to.

On the Backburner: Window Scaling (yet again)

As it turns out, though I thought I had fixed the window scaling, in the last post, it turns out I had only fixed the gaps when scaling the game window horizontally.  When scaling vertically they reappeared.  I'm not entirely sure how to fix this.  I think I may need to make an actual build of the game, which I haven't done yet, and then see how scaling the window works in the actual build.  I would like to make the game fully scalable in all directions, with a limit on how small the window can get, however I may have the make the game a fixed aspect ratio.   Hopefully I won't have the make the game a fixed resolution/resolutions.

Tuesday, October 13, 2015

Dev Update # 12: Pausing Audio, UI Elements Correctly Scaling


I've decided to add  more pictures and more technical information into my dev updates.  I've also decided to do a write up on my progress/lack thereof immediately after I've made the progress/lack thereof.  Instead of making a lot of dev updates, I will add to them until I feel like I have enough material before posting.

10/12/2015 I found a way to pause and play the music in the game, using "AudioListener.pause" instead of turning on and off the SoundManager object. One benefit to this, is that the music will begin playing where it left off instead of restarting every time. Also, Unity can't find an object when it's not active, and this is why the sound breaks using my current "reset game" code. One downside the AudioListener pauses all of the audio including the music and sound effects. Apparently can "set the ignoreListenerPause property" to true for sound effects, to prevent this, but I'm not entirely sure how to do this yet. A snippet of my code is below. This is the body of a function I have that runs when the music button is pressed, or the "M" button is pressed.
if (AudioListener.pause == false)
        { AudioListener.pause = true; }
        else
        { AudioListener.pause = false; }

10/13/2015  I readjusted some of the UI elements that weren't scaling with everything else, so they would scale correctly.  I changed the anchors to top horizontal stretch(not sure if the top part is important, but I know that adding vertical stretch will generally break everything. see Image 1).  Anchors and pivots with the new UI system are a bit confusing.  I think maybe I'm starting to understand them a little.

I was having trouble getting the left and right panels to scale with the game board in the middle.  I created a panel that mimics the game board, to make it easier.  The game board is always square, 1x1, so as the window scales the game board gets bigger or smaller in the middle.  Initially, the left and right panels stayed the same width, because there was no width scaling which would create large black areas on the left and right of the game board(see image 2), or the panels would overlap the game board.  Every time I changed the anchors, the panels would scale, but they would scale incorrectly.

To fix this, after a LOT of trial and error, I set the inside anchors to the middle of the screen.  Don't ask me why this works or how I thought of trying it.  I have a very vague idea, but I couldn't explain it.
Success!!! UI Elements scale correctly.

Image 1: vertical scaling breaking UI elements, before I fixed it

Image 2: Gaps between Game Board and Panels

Sunday, October 11, 2015

Tech info: ArrayPrefs2



09/28  I added saving and loading of the todoList array today.
I'm using PlayerPrefs to save and load single variables and ArrayPrefs2 to save and load arrays.  Since I'm using Lists, I have to convert the lists to and from arrays when using ArrayPrefs2, but I plan on creating a more robust system later.  Possibly using XML.  Ideally, I would save individual entries in the todoList, instead of the entire array, but saving the whole array every time a todoList item is removed or added may not be too resource intensive.  I'm really not sure.  Right now I'm mainly focused on getting everything running and released.  I'll focus on optimization later.


Dev Update #11 - Continuing work on Productivity Game - Rogue-Like Prototype

Game in mid-level load

Game on Day 2
What works:
-Adding and completing tasks.
-Completing a task adds a tile to the level.
-The level can then be played once all tiles are complete.
-Todo buttons fit the size of their text.
-The level, the number of tiles added to the level, and the remaining todos are all saved when the game is closed and reopened.
-  The actual game portion works

What doesn't work:
I added those buttons along the right side to test some features.  Currently they're all a little broken.  I'm pretty sure I know how to fix the music button to turn music on and off.  The pause button technically works, but tiles are not added to the level when a todo is completed, when paused, which defeats the purpose of the pause.  The reset game button resets the game, but you also have to immediately turn the game off and back on again or it will break all kinds of things.

So, I'm basically just bug fixing right now.

I thought I made at least one post, if not multiple posts since the last one, but they don't appear to exist :/

Wednesday, September 9, 2015

Dev Update #10

2d Roguelike Prototype:
I've completed saving and loading using random.seed, a couple of simple integer variables, and player prefs.  This was actually much easier than I anticipated.

Now I'm back to where I left off with the Habit Engine...The GUI.

I'm not looking forward to this, but I know it's necessary and highly important.  I'm hoping to build the "One GUI to rule them all!" or, in other words, a flexible, customizable GUI I can continue to reuse on all projects.

I may do a more indepth write up of my work so far, eventually, but I wasn't planning on posting until I had the prototype finished.  I basically looked at the GUI once and got a migraine and decided to come back to it.  I plan on starting work on it this week.  Since I have no idea how long the GUI will take me, I decided to post now.   At least the game part is basically finished.

Maybe I'll post a video so I can show its current state.

Tuesday, September 1, 2015

Next Dev Update very soon

I'm planning on posting a new update on the first of every month.  I may make interim posts as well, but new updates should always be posted on the first of the month.

Today's update, however, is late, because it's not finished and I need to leave for work(crappy call center job) at this moment.

I've made some very exciting progress on the 2d rogue prototype and may be able to get it working and published within one to four weeks.  Sequential level loading is working, and I've moved on to saving and loading.

Check back here within the next few days for a more thorough write up.

Thursday, July 30, 2015

Dev Update #9 Productivity Game - Rogue-Like Prototyping

I did a tutorial for Unity for a 2d Roguelike
I decided to modify this Game so that every time you do a task, another tile/part of the level loads and then once all tiles/parts are loaded, you can play the level.

I've made some progress in this.

I used:

while (!clicked)
{
yield return new WaitForSeconds(0.01f);
}
clicked = false;

To make each piece of the level wait for a click until loading the next piece.


(I'm finding it difficult to properly format the code on blogger.)

Then I tried to make the Sound and Player Object wait to load until after the level was loaded, but I've had some difficulty.

First, I thought I would just turn them off in the inspector and then turn them on with a script.  It turns out, accessing a deactivated object is difficult in Unity, because most of the easy ways to access an object require the object to be active.

Second, I thought I would try to create a script that had a reference to the Object, and then I could access the reference from the script.  I spent a long time trying, but could never get it to work.  It might not be possible.

Then I decided to leave the Player Object active and simply toggle the Player Script and and Player SpriteRenderer.  This worked on the first level, but after the next level loaded, there was some code that was trying to use the Play Script, but couldn't because it was deactivated.

I'm not yet sure how I will solve this.

Dev Update #8 UnityUI Tests

I've been streaming some of my work on http://www.twitch.tv/fragmentalstew. Unfortunately the stream is too low quality to see what's going on. Mostly it's me trying things and then them not working. I do make a little progress, though. Also I don't have a webcam or mic hooked up currently so my only means of communication is through wordpad.

 I made a simple mockup of the UI with a prefab of a toggle. I tried to dynamically instantiate a list of those prefabs but I had trouble making the new instantiated toggles children of the canvas. I found a video about it on https://www.youtube.com/watch?v=TRLsmuYMs8Q and downloaded the video using firefox's video downloadhelper addon so I could control playback speed. I used the code that was linked to this video, with a slight modification to use SetParent instead of Parent, to display a list of toggles that are children of the canvas. They scroll correctly and everything. Now I just have to apply this to my own project and I should be able to create a mockup for the new UI. I'm still not entirely sure what everything in the code does, but I'll figure it out.


 Update: I made a draft of this post a while ago but never posted it. After this post, I created 4 separate lists of toggles and connected them to the same scrollbar. They don't scroll evenly. Each list is centered instead of starting at the top and the longer the list, the faster it scrolls, but I moved on to something else which I'll describe in my next post.  I also added rudimentary health/xp/mana bars.

 

Thursday, June 25, 2015

Dev Update #7 And we're back!

Lost the domain for a little while, because the registrar wanted to renew it for a much heftier price that I was willing to pay.  I wanted to switch do a different registrar but never got around to it.

Luckily, this domain was available for registration with google domains for a reasonable price, with free identity protection and email forwarding.

I put off work on the HabitEngine for a while, but I've started back.

I transferred the project to Unity 5.  In the process, I didn't transfer all the folders, so what I thought was a bug was really only a problem I created.  However, in the process of trying to fix the bug I created, I found the solution for the scroll bar not being clickable, so I fixed that.  I also cleaned up my HabitRPG avatar code so it's not clogging up the console with warnings.

I'm working on recreating the GUI using Unity's new UI tools.  There should be many advantages to this.  Hopefully I will be able to create a more dynamic, customizable, efficient UI that runs well on all devices.

Learning the new unity UI is a chore, and I'm working 40 hours a week, so it will probably take me a while.

Here's a picture of The Habit Engine, with old GUI, running in Unity 5, with working scroll bar, and minus the console clogging avatar code.









So, I thought I might get a habitengine domain if it was available, but apparently a Finnish guy named Janne Haonperä, Chief Marketing Officer for http://www.cloudriven.fi/, has bought up all the domains.  I might try to contact him about it.  Janne, if you're reading this, can haz?

Fell free to suggest name changes, in case it has to come to that one day.